Skip to content

Commit d68adea

Browse files
committed
Rewrite Query response class to 306's style
1 parent 765f89f commit d68adea

File tree

8 files changed

+279
-225
lines changed

8 files changed

+279
-225
lines changed

docs/api/basic.rst

Lines changed: 15 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -60,85 +60,23 @@ For Java Server
6060
:inherited-members:
6161
:exclude-members: build
6262

63-
.. module:: mcstatus.querier
64-
65-
.. class:: QueryResponse
66-
:canonical: mcstatus.querier.QueryResponse
67-
68-
The response object for :meth:`JavaServer.query() <mcstatus.server.JavaServer.query>`.
69-
70-
.. class:: Players
71-
:canonical: mcstatus.querier.QueryResponse.Players
72-
73-
Class for storing information about players on the server.
74-
75-
.. attribute:: online
76-
:type: int
77-
:canonical: mcstatus.querier.QueryResponse.Players.online
78-
79-
The number of online players.
80-
81-
.. attribute:: max
82-
:type: int
83-
:canonical: mcstatus.querier.QueryResponse.Players.max
84-
85-
The maximum allowed number of players (server slots).
86-
87-
.. attribute:: names
88-
:type: list[str]
89-
:canonical: mcstatus.querier.QueryResponse.Players.names
90-
91-
The list of online players.
92-
93-
.. class:: Software
94-
:canonical: mcstatus.querier.QueryResponse.Software
95-
96-
Class for storing information about software on the server.
97-
98-
.. attribute:: version
99-
:type: str
100-
:canonical: mcstatus.querier.QueryResponse.Software.version
101-
102-
The version of the software.
103-
104-
.. attribute:: brand
105-
:type: str
106-
:value: "vanilla"
107-
:canonical: mcstatus.querier.QueryResponse.Software.brand
108-
109-
The brand of the software. Like `Paper <https://papermc.io>`_ or `Spigot <https://www.spigotmc.org>`_.
110-
111-
.. attribute:: plugins
112-
:type: list[str]
113-
:canonical: mcstatus.querier.QueryResponse.Software.plugins
114-
115-
The list of plugins. Can be empty if hidden.
116-
117-
.. attribute:: motd
118-
:type: ~mcstatus.motd.Motd
119-
:canonical: mcstatus.querier.QueryResponse.motd
120-
121-
The MOTD of the server. Also known as description.
122-
123-
.. seealso:: :doc:`/api/motd_parsing`.
124-
125-
.. attribute:: map
126-
:type: str
127-
:canonical: mcstatus.querier.QueryResponse.map
128-
129-
The name of the map.
130-
131-
.. attribute:: players
132-
:type: ~QueryResponse.Players
133-
:canonical: mcstatus.querier.QueryResponse.players
134-
135-
The players information.
63+
.. autoclass:: mcstatus.responses.QueryResponse()
64+
:members:
65+
:undoc-members:
66+
:inherited-members:
67+
:exclude-members: build
13668

137-
.. attribute:: software
138-
:type: ~QueryResponse.Software
139-
:canonical: mcstatus.querier.QueryResponse.software
69+
.. autoclass:: mcstatus.responses.QueryPlayers()
70+
:members:
71+
:undoc-members:
72+
:inherited-members:
73+
:exclude-members: build
14074

141-
The software information.
75+
.. autoclass:: mcstatus.responses.QuerySoftware()
76+
:members:
77+
:undoc-members:
78+
:inherited-members:
79+
:exclude-members: build
14280

14381

14482
For Bedrock Servers

docs/examples/code/player_list_from_query_with_fallback_on_status.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
server = JavaServer.lookup("play.hypixel.net")
44
query = server.query()
55

6-
if query.players.names:
7-
print("Players online:", ", ".join(query.players.names))
6+
if query.players.list:
7+
print("Players online:", ", ".join(query.players.list))
88
else:
99
status = server.status()
1010

mcstatus/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def query(server: JavaServer) -> None:
6666
print(f"software: v{response.software.version} {response.software.brand}")
6767
print(f"plugins: {response.software.plugins}")
6868
print(f'motd: "{response.motd}"')
69-
print(f"players: {response.players.online}/{response.players.max} {response.players.names}")
69+
print(f"players: {response.players.online}/{response.players.max} {', '.join(response.players.list)}")
7070

7171

7272
def main() -> None:

mcstatus/querier.py

Lines changed: 35 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,9 @@
33
import random
44
import re
55
import struct
6-
from typing import TYPE_CHECKING
76

8-
from mcstatus.motd import Motd
97
from 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

1511
class 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

Comments
 (0)