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