Skip to content

Commit

Permalink
Rewrite Query response class to 306's style
Browse files Browse the repository at this point in the history
  • Loading branch information
PerchunPak committed May 14, 2023
1 parent 765f89f commit d68adea
Show file tree
Hide file tree
Showing 8 changed files with 279 additions and 225 deletions.
92 changes: 15 additions & 77 deletions docs/api/basic.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,85 +60,23 @@ For Java Server
:inherited-members:
:exclude-members: build

.. module:: mcstatus.querier

.. class:: QueryResponse
:canonical: mcstatus.querier.QueryResponse

The response object for :meth:`JavaServer.query() <mcstatus.server.JavaServer.query>`.

.. class:: Players
:canonical: mcstatus.querier.QueryResponse.Players

Class for storing information about players on the server.

.. attribute:: online
:type: int
:canonical: mcstatus.querier.QueryResponse.Players.online

The number of online players.

.. attribute:: max
:type: int
:canonical: mcstatus.querier.QueryResponse.Players.max

The maximum allowed number of players (server slots).

.. attribute:: names
:type: list[str]
:canonical: mcstatus.querier.QueryResponse.Players.names

The list of online players.

.. class:: Software
:canonical: mcstatus.querier.QueryResponse.Software

Class for storing information about software on the server.

.. attribute:: version
:type: str
:canonical: mcstatus.querier.QueryResponse.Software.version

The version of the software.

.. attribute:: brand
:type: str
:value: "vanilla"
:canonical: mcstatus.querier.QueryResponse.Software.brand

The brand of the software. Like `Paper <https://papermc.io>`_ or `Spigot <https://www.spigotmc.org>`_.

.. attribute:: plugins
:type: list[str]
:canonical: mcstatus.querier.QueryResponse.Software.plugins

The list of plugins. Can be empty if hidden.

.. attribute:: motd
:type: ~mcstatus.motd.Motd
:canonical: mcstatus.querier.QueryResponse.motd

The MOTD of the server. Also known as description.

.. seealso:: :doc:`/api/motd_parsing`.

.. attribute:: map
:type: str
:canonical: mcstatus.querier.QueryResponse.map

The name of the map.

.. attribute:: players
:type: ~QueryResponse.Players
:canonical: mcstatus.querier.QueryResponse.players

The players information.
.. autoclass:: mcstatus.responses.QueryResponse()
:members:
:undoc-members:
:inherited-members:
:exclude-members: build

.. attribute:: software
:type: ~QueryResponse.Software
:canonical: mcstatus.querier.QueryResponse.software
.. autoclass:: mcstatus.responses.QueryPlayers()
:members:
:undoc-members:
:inherited-members:
:exclude-members: build

The software information.
.. autoclass:: mcstatus.responses.QuerySoftware()
:members:
:undoc-members:
:inherited-members:
:exclude-members: build


For Bedrock Servers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
server = JavaServer.lookup("play.hypixel.net")
query = server.query()

if query.players.names:
print("Players online:", ", ".join(query.players.names))
if query.players.list:
print("Players online:", ", ".join(query.players.list))
else:
status = server.status()

Expand Down
2 changes: 1 addition & 1 deletion mcstatus/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def query(server: JavaServer) -> None:
print(f"software: v{response.software.version} {response.software.brand}")
print(f"plugins: {response.software.plugins}")
print(f'motd: "{response.motd}"')
print(f"players: {response.players.online}/{response.players.max} {response.players.names}")
print(f"players: {response.players.online}/{response.players.max} {', '.join(response.players.list)}")


def main() -> None:
Expand Down
126 changes: 35 additions & 91 deletions mcstatus/querier.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,9 @@
import random
import re
import struct
from typing import TYPE_CHECKING

from mcstatus.motd import Motd
from mcstatus.protocol.connection import Connection, UDPAsyncSocketConnection, UDPSocketConnection

if TYPE_CHECKING:
from typing_extensions import Self
from mcstatus.responses import QueryResponse, RawQueryResponse


class ServerQuerier:
Expand Down Expand Up @@ -60,91 +56,13 @@ def read_query(self) -> QueryResponse:
self.connection.write(request)

response = self._read_packet()
return QueryResponse.from_connection(response)

return QueryResponse.build(*self.transform_connection_to_objects(response))

class AsyncServerQuerier(ServerQuerier):
def __init__(self, connection: UDPAsyncSocketConnection):
# We do this to inform python about self.connection type (it's async)
super().__init__(connection) # type: ignore[arg-type]
self.connection: UDPAsyncSocketConnection

async def _read_packet(self) -> Connection:
packet = Connection()
packet.receive(await self.connection.read(self.connection.remaining()))
packet.read(1 + 4)
return packet
def transform_connection_to_objects(self, response: Connection) -> tuple[RawQueryResponse, list[str]]:
"""Transform the connection object (the result) into dict which is passed to the QueryResponse constructor.
async def handshake(self) -> None:
await self.connection.write(self._create_handshake_packet())

packet = await self._read_packet()
self.challenge = int(packet.read_ascii())

async def read_query(self) -> QueryResponse:
request = self._create_packet()
await self.connection.write(request)

response = await self._read_packet()
return QueryResponse.from_connection(response)


class QueryResponse:
"""Documentation for this class is written by hand, without docstrings.
This is because the class is not supposted to be auto-documented.
Please see https://mcstatus.readthedocs.io/en/latest/api/basic/#mcstatus.querier.QueryResponse
for the actual documentation.
"""

# THIS IS SO UNPYTHONIC
# it's staying just because the tests depend on this structure
class Players:
online: int
max: int
names: list[str]

# TODO: It's a bit weird that we accept str for number parameters, just to convert them in init
def __init__(self, online: str | int, max: str | int, names: list[str]):
self.online = int(online)
self.max = int(max)
self.names = names

class Software:
version: str
brand: str
plugins: list[str]

def __init__(self, version: str, plugins: str):
self.version = version
self.brand = "vanilla"
self.plugins = []

if plugins:
parts = plugins.split(":", 1)
self.brand = parts[0].strip()

if len(parts) == 2:
self.plugins = [s.strip() for s in parts[1].split(";")]

motd: Motd
map: str
players: Players
software: Software

def __init__(self, raw: dict[str, str], players: list[str]):
try:
self.raw = raw
self.motd = Motd.parse(raw["hostname"], bedrock=False)
self.map = raw["map"]
self.players = QueryResponse.Players(raw["numplayers"], raw["maxplayers"], players)
self.software = QueryResponse.Software(raw["version"], raw["plugins"])
except KeyError:
raise ValueError("The provided data is not valid")

@classmethod
def from_connection(cls, response: Connection) -> Self:
:return: A tuple with two elements. First is `raw` answer and second is list of players.
"""
response.read(len("splitnum") + 1 + 1 + 1)
data = {}

Expand All @@ -170,11 +88,37 @@ def from_connection(cls, response: Connection) -> Self:

response.read(len("player_") + 1 + 1)

players = []
players_list = []
while True:
player = response.read_ascii()
if len(player) == 0:
break
players.append(player)
players_list.append(player)

return RawQueryResponse(**data), players_list

return cls(data, players)

class AsyncServerQuerier(ServerQuerier):
def __init__(self, connection: UDPAsyncSocketConnection):
# We do this to inform python about self.connection type (it's async)
super().__init__(connection) # type: ignore[arg-type]
self.connection: UDPAsyncSocketConnection

async def _read_packet(self) -> Connection:
packet = Connection()
packet.receive(await self.connection.read(self.connection.remaining()))
packet.read(1 + 4)
return packet

async def handshake(self) -> None:
await self.connection.write(self._create_handshake_packet())

packet = await self._read_packet()
self.challenge = int(packet.read_ascii())

async def read_query(self) -> QueryResponse:
request = self._create_packet()
await self.connection.write(request)

response = await self._read_packet()
return QueryResponse.build(*self.transform_connection_to_objects(response))
Loading

0 comments on commit d68adea

Please sign in to comment.