33import random
44import re
55import struct
6- from typing import TYPE_CHECKING
76
8- from mcstatus.motd import Motd
97from mcstatus.protocol.connection import Connection, UDPAsyncSocketConnection, UDPSocketConnection
10-
11- if TYPE_CHECKING:
12- from typing_extensions import Self
8+ from mcstatus.responses import QueryResponse, RawQueryResponse
139
1410
1511class ServerQuerier:
@@ -60,91 +56,13 @@ def read_query(self) -> QueryResponse:
6056 self.connection.write(request)
6157
6258 response = self._read_packet()
63- return QueryResponse.from_connection(response)
64-
59+ return QueryResponse.build(*self.transform_connection_to_objects(response))
6560
66- class AsyncServerQuerier(ServerQuerier):
67- def __init__(self, connection: UDPAsyncSocketConnection):
68- # We do this to inform python about self.connection type (it's async)
69- super().__init__(connection) # type: ignore[arg-type]
70- self.connection: UDPAsyncSocketConnection
71-
72- async def _read_packet(self) -> Connection:
73- packet = Connection()
74- packet.receive(await self.connection.read(self.connection.remaining()))
75- packet.read(1 + 4)
76- return packet
61+ def transform_connection_to_objects(self, response: Connection) -> tuple[RawQueryResponse, list[str]]:
62+ """Transform the connection object (the result) into dict which is passed to the QueryResponse constructor.
7763
78- async def handshake(self) -> None:
79- await self.connection.write(self._create_handshake_packet())
80-
81- packet = await self._read_packet()
82- self.challenge = int(packet.read_ascii())
83-
84- async def read_query(self) -> QueryResponse:
85- request = self._create_packet()
86- await self.connection.write(request)
87-
88- response = await self._read_packet()
89- return QueryResponse.from_connection(response)
90-
91-
92- class QueryResponse:
93- """Documentation for this class is written by hand, without docstrings.
94-
95- This is because the class is not supposted to be auto-documented.
96-
97- Please see https://mcstatus.readthedocs.io/en/latest/api/basic/#mcstatus.querier.QueryResponse
98- for the actual documentation.
99- """
100-
101- # THIS IS SO UNPYTHONIC
102- # it's staying just because the tests depend on this structure
103- class Players:
104- online: int
105- max: int
106- names: list[str]
107-
108- # TODO: It's a bit weird that we accept str for number parameters, just to convert them in init
109- def __init__(self, online: str | int, max: str | int, names: list[str]):
110- self.online = int(online)
111- self.max = int(max)
112- self.names = names
113-
114- class Software:
115- version: str
116- brand: str
117- plugins: list[str]
118-
119- def __init__(self, version: str, plugins: str):
120- self.version = version
121- self.brand = "vanilla"
122- self.plugins = []
123-
124- if plugins:
125- parts = plugins.split(":", 1)
126- self.brand = parts[0].strip()
127-
128- if len(parts) == 2:
129- self.plugins = [s.strip() for s in parts[1].split(";")]
130-
131- motd: Motd
132- map: str
133- players: Players
134- software: Software
135-
136- def __init__(self, raw: dict[str, str], players: list[str]):
137- try:
138- self.raw = raw
139- self.motd = Motd.parse(raw["hostname"], bedrock=False)
140- self.map = raw["map"]
141- self.players = QueryResponse.Players(raw["numplayers"], raw["maxplayers"], players)
142- self.software = QueryResponse.Software(raw["version"], raw["plugins"])
143- except KeyError:
144- raise ValueError("The provided data is not valid")
145-
146- @classmethod
147- def from_connection(cls, response: Connection) -> Self:
64+ :return: A tuple with two elements. First is `raw` answer and second is list of players.
65+ """
14866 response.read(len("splitnum") + 1 + 1 + 1)
14967 data = {}
15068
@@ -170,11 +88,37 @@ def from_connection(cls, response: Connection) -> Self:
17088
17189 response.read(len("player_") + 1 + 1)
17290
173- players = []
91+ players_list = []
17492 while True:
17593 player = response.read_ascii()
17694 if len(player) == 0:
17795 break
178- players.append(player)
96+ players_list.append(player)
97+
98+ return RawQueryResponse(**data), players_list
17999
180- return cls(data, players)
100+
101+ class AsyncServerQuerier(ServerQuerier):
102+ def __init__(self, connection: UDPAsyncSocketConnection):
103+ # We do this to inform python about self.connection type (it's async)
104+ super().__init__(connection) # type: ignore[arg-type]
105+ self.connection: UDPAsyncSocketConnection
106+
107+ async def _read_packet(self) -> Connection:
108+ packet = Connection()
109+ packet.receive(await self.connection.read(self.connection.remaining()))
110+ packet.read(1 + 4)
111+ return packet
112+
113+ async def handshake(self) -> None:
114+ await self.connection.write(self._create_handshake_packet())
115+
116+ packet = await self._read_packet()
117+ self.challenge = int(packet.read_ascii())
118+
119+ async def read_query(self) -> QueryResponse:
120+ request = self._create_packet()
121+ await self.connection.write(request)
122+
123+ response = await self._read_packet()
124+ return QueryResponse.build(*self.transform_connection_to_objects(response))
0 commit comments