Improve reading of MOTD and bump mcstatus to 11.0.0 (#95715)

* Improve reading of MOTD, bump mcstatus to 10.0.3 and getmac to 0.9.4

* Revert bump of getmac

* Bump mcstatus to 11.0.0-rc3. Use new MOTD parser.

* Bump mcstatus to 11.0.0
This commit is contained in:
elmurato 2023-07-24 20:23:11 +02:00 committed by GitHub
parent 649568be83
commit 557b6d511b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 44 additions and 39 deletions

View file

@ -6,7 +6,7 @@ from datetime import datetime, timedelta
import logging import logging
from typing import Any from typing import Any
from mcstatus.server import MinecraftServer as MCStatus from mcstatus.server import JavaServer
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, Platform from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, Platform
@ -69,9 +69,6 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
class MinecraftServer: class MinecraftServer:
"""Representation of a Minecraft server.""" """Representation of a Minecraft server."""
# Private constants
_MAX_RETRIES_STATUS = 3
def __init__( def __init__(
self, hass: HomeAssistant, unique_id: str, config_data: Mapping[str, Any] self, hass: HomeAssistant, unique_id: str, config_data: Mapping[str, Any]
) -> None: ) -> None:
@ -88,16 +85,16 @@ class MinecraftServer:
self.srv_record_checked = False self.srv_record_checked = False
# 3rd party library instance # 3rd party library instance
self._mc_status = MCStatus(self.host, self.port) self._server = JavaServer(self.host, self.port)
# Data provided by 3rd party library # Data provided by 3rd party library
self.version = None self.version: str | None = None
self.protocol_version = None self.protocol_version: int | None = None
self.latency_time = None self.latency_time: float | None = None
self.players_online = None self.players_online: int | None = None
self.players_max = None self.players_max: int | None = None
self.players_list: list[str] | None = None self.players_list: list[str] | None = None
self.motd = None self.motd: str | None = None
# Dispatcher signal name # Dispatcher signal name
self.signal_name = f"{SIGNAL_NAME_PREFIX}_{self.unique_id}" self.signal_name = f"{SIGNAL_NAME_PREFIX}_{self.unique_id}"
@ -133,13 +130,11 @@ class MinecraftServer:
# with data extracted out of SRV record. # with data extracted out of SRV record.
self.host = srv_record[CONF_HOST] self.host = srv_record[CONF_HOST]
self.port = srv_record[CONF_PORT] self.port = srv_record[CONF_PORT]
self._mc_status = MCStatus(self.host, self.port) self._server = JavaServer(self.host, self.port)
# Ping the server with a status request. # Ping the server with a status request.
try: try:
await self._hass.async_add_executor_job( await self._server.async_status()
self._mc_status.status, self._MAX_RETRIES_STATUS
)
self.online = True self.online = True
except OSError as error: except OSError as error:
_LOGGER.debug( _LOGGER.debug(
@ -176,9 +171,7 @@ class MinecraftServer:
async def _async_status_request(self) -> None: async def _async_status_request(self) -> None:
"""Request server status and update properties.""" """Request server status and update properties."""
try: try:
status_response = await self._hass.async_add_executor_job( status_response = await self._server.async_status()
self._mc_status.status, self._MAX_RETRIES_STATUS
)
# Got answer to request, update properties. # Got answer to request, update properties.
self.version = status_response.version.name self.version = status_response.version.name
@ -186,7 +179,8 @@ class MinecraftServer:
self.players_online = status_response.players.online self.players_online = status_response.players.online
self.players_max = status_response.players.max self.players_max = status_response.players.max
self.latency_time = status_response.latency self.latency_time = status_response.latency
self.motd = (status_response.description).get("text") self.motd = status_response.motd.to_plain()
self.players_list = [] self.players_list = []
if status_response.players.sample is not None: if status_response.players.sample is not None:
for player in status_response.players.sample: for player in status_response.players.sample:
@ -244,7 +238,7 @@ class MinecraftServerEntity(Entity):
manufacturer=MANUFACTURER, manufacturer=MANUFACTURER,
model=f"Minecraft Server ({self._server.version})", model=f"Minecraft Server ({self._server.version})",
name=self._server.name, name=self._server.name,
sw_version=self._server.protocol_version, sw_version=str(self._server.protocol_version),
) )
self._attr_device_class = device_class self._attr_device_class = device_class
self._extra_state_attributes = None self._extra_state_attributes = None

View file

@ -8,6 +8,7 @@ import voluptuous as vol
from homeassistant.config_entries import ConfigFlow from homeassistant.config_entries import ConfigFlow
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT
from homeassistant.data_entry_flow import FlowResult
from . import MinecraftServer, helpers from . import MinecraftServer, helpers
from .const import DEFAULT_HOST, DEFAULT_NAME, DEFAULT_PORT, DOMAIN from .const import DEFAULT_HOST, DEFAULT_NAME, DEFAULT_PORT, DOMAIN
@ -18,7 +19,7 @@ class MinecraftServerConfigFlow(ConfigFlow, domain=DOMAIN):
VERSION = 1 VERSION = 1
async def async_step_user(self, user_input=None): async def async_step_user(self, user_input=None) -> FlowResult:
"""Handle the initial step.""" """Handle the initial step."""
errors = {} errors = {}
@ -117,7 +118,7 @@ class MinecraftServerConfigFlow(ConfigFlow, domain=DOMAIN):
# form filled with user_input and eventually with errors otherwise). # form filled with user_input and eventually with errors otherwise).
return self._show_config_form(user_input, errors) return self._show_config_form(user_input, errors)
def _show_config_form(self, user_input=None, errors=None): def _show_config_form(self, user_input=None, errors=None) -> FlowResult:
"""Show the setup form to the user.""" """Show the setup form to the user."""
if user_input is None: if user_input is None:
user_input = {} user_input = {}

View file

@ -7,5 +7,5 @@
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["dnspython", "mcstatus"], "loggers": ["dnspython", "mcstatus"],
"quality_scale": "silver", "quality_scale": "silver",
"requirements": ["aiodns==3.0.0", "getmac==0.8.2", "mcstatus==6.0.0"] "requirements": ["aiodns==3.0.0", "getmac==0.8.2", "mcstatus==11.0.0"]
} }

View file

@ -1175,7 +1175,7 @@ maxcube-api==0.4.3
mbddns==0.1.2 mbddns==0.1.2
# homeassistant.components.minecraft_server # homeassistant.components.minecraft_server
mcstatus==6.0.0 mcstatus==11.0.0
# homeassistant.components.meater # homeassistant.components.meater
meater-python==0.0.8 meater-python==0.0.8

View file

@ -898,7 +898,7 @@ maxcube-api==0.4.3
mbddns==0.1.2 mbddns==0.1.2
# homeassistant.components.minecraft_server # homeassistant.components.minecraft_server
mcstatus==6.0.0 mcstatus==11.0.0
# homeassistant.components.meater # homeassistant.components.meater
meater-python==0.0.8 meater-python==0.0.8

View file

@ -4,7 +4,7 @@ import asyncio
from unittest.mock import patch from unittest.mock import patch
import aiodns import aiodns
from mcstatus.pinger import PingResponse from mcstatus.status_response import JavaStatusResponse
from homeassistant.components.minecraft_server.const import ( from homeassistant.components.minecraft_server.const import (
DEFAULT_NAME, DEFAULT_NAME,
@ -22,7 +22,7 @@ from tests.common import MockConfigEntry
class QueryMock: class QueryMock:
"""Mock for result of aiodns.DNSResolver.query.""" """Mock for result of aiodns.DNSResolver.query."""
def __init__(self): def __init__(self) -> None:
"""Set up query result mock.""" """Set up query result mock."""
self.host = "mc.dummyserver.com" self.host = "mc.dummyserver.com"
self.port = 23456 self.port = 23456
@ -31,7 +31,7 @@ class QueryMock:
self.ttl = None self.ttl = None
STATUS_RESPONSE_RAW = { JAVA_STATUS_RESPONSE_RAW = {
"description": {"text": "Dummy Description"}, "description": {"text": "Dummy Description"},
"version": {"name": "Dummy Version", "protocol": 123}, "version": {"name": "Dummy Version", "protocol": 123},
"players": { "players": {
@ -103,8 +103,10 @@ async def test_same_host(hass: HomeAssistant) -> None:
"aiodns.DNSResolver.query", "aiodns.DNSResolver.query",
side_effect=aiodns.error.DNSError, side_effect=aiodns.error.DNSError,
), patch( ), patch(
"mcstatus.server.MinecraftServer.status", "mcstatus.server.JavaServer.async_status",
return_value=PingResponse(STATUS_RESPONSE_RAW), return_value=JavaStatusResponse(
None, None, None, None, JAVA_STATUS_RESPONSE_RAW, None
),
): ):
unique_id = "mc.dummyserver.com-25565" unique_id = "mc.dummyserver.com-25565"
config_data = { config_data = {
@ -158,7 +160,7 @@ async def test_connection_failed(hass: HomeAssistant) -> None:
with patch( with patch(
"aiodns.DNSResolver.query", "aiodns.DNSResolver.query",
side_effect=aiodns.error.DNSError, side_effect=aiodns.error.DNSError,
), patch("mcstatus.server.MinecraftServer.status", side_effect=OSError): ), patch("mcstatus.server.JavaServer.async_status", side_effect=OSError):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT
) )
@ -173,8 +175,10 @@ async def test_connection_succeeded_with_srv_record(hass: HomeAssistant) -> None
"aiodns.DNSResolver.query", "aiodns.DNSResolver.query",
return_value=SRV_RECORDS, return_value=SRV_RECORDS,
), patch( ), patch(
"mcstatus.server.MinecraftServer.status", "mcstatus.server.JavaServer.async_status",
return_value=PingResponse(STATUS_RESPONSE_RAW), return_value=JavaStatusResponse(
None, None, None, None, JAVA_STATUS_RESPONSE_RAW, None
),
): ):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_SRV DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_SRV
@ -192,8 +196,10 @@ async def test_connection_succeeded_with_host(hass: HomeAssistant) -> None:
"aiodns.DNSResolver.query", "aiodns.DNSResolver.query",
side_effect=aiodns.error.DNSError, side_effect=aiodns.error.DNSError,
), patch( ), patch(
"mcstatus.server.MinecraftServer.status", "mcstatus.server.JavaServer.async_status",
return_value=PingResponse(STATUS_RESPONSE_RAW), return_value=JavaStatusResponse(
None, None, None, None, JAVA_STATUS_RESPONSE_RAW, None
),
): ):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT
@ -211,8 +217,10 @@ async def test_connection_succeeded_with_ip4(hass: HomeAssistant) -> None:
"aiodns.DNSResolver.query", "aiodns.DNSResolver.query",
side_effect=aiodns.error.DNSError, side_effect=aiodns.error.DNSError,
), patch( ), patch(
"mcstatus.server.MinecraftServer.status", "mcstatus.server.JavaServer.async_status",
return_value=PingResponse(STATUS_RESPONSE_RAW), return_value=JavaStatusResponse(
None, None, None, None, JAVA_STATUS_RESPONSE_RAW, None
),
): ):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_IPV4 DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_IPV4
@ -230,8 +238,10 @@ async def test_connection_succeeded_with_ip6(hass: HomeAssistant) -> None:
"aiodns.DNSResolver.query", "aiodns.DNSResolver.query",
side_effect=aiodns.error.DNSError, side_effect=aiodns.error.DNSError,
), patch( ), patch(
"mcstatus.server.MinecraftServer.status", "mcstatus.server.JavaServer.async_status",
return_value=PingResponse(STATUS_RESPONSE_RAW), return_value=JavaStatusResponse(
None, None, None, None, JAVA_STATUS_RESPONSE_RAW, None
),
): ):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_IPV6 DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT_IPV6