from __future__ import annotations from dataclasses import asdict, dataclass from typing import Any, TYPE_CHECKING from mcstatus.motd import Motd if TYPE_CHECKING: from typing_extensions import Self from mcstatus.responses._raw import RawQueryResponse __all__ = [ "QueryPlayers", "QueryResponse", "QuerySoftware", ] @dataclass(frozen=True) class QueryResponse: """The response object for :meth:`JavaServer.query() `.""" raw: RawQueryResponse """Raw response from the server. This is :class:`~typing.TypedDict` actually, please see sources to find what is here. """ motd: Motd """The MOTD of the server. Also known as description. .. seealso:: :doc:`/api/motd_parsing`. """ map_name: str """The name of the map. Default is ``world``.""" players: QueryPlayers """The players information.""" software: QuerySoftware """The software information.""" ip: str """The IP address the server is listening/was contacted on.""" port: int """The port the server is listening/was contacted on.""" game_type: str = "SMP" """The game type of the server. Hardcoded to ``SMP`` (survival multiplayer).""" game_id: str = "MINECRAFT" """The game ID of the server. Hardcoded to ``MINECRAFT``.""" @classmethod def build(cls, raw: RawQueryResponse, players_list: list[str]) -> Self: return cls( raw=raw, motd=Motd.parse(raw["hostname"], bedrock=False), map_name=raw["map"], players=QueryPlayers.build(raw, players_list), software=QuerySoftware.build(raw["version"], raw["plugins"]), ip=raw["hostip"], port=int(raw["hostport"]), game_type=raw["gametype"], game_id=raw["game_id"], ) def as_dict(self) -> dict[str, Any]: """Return the dataclass as JSON-serializable :class:`dict`. Do note that this method doesn't return :class:`string ` but :class:`dict`, so you can do some processing on returned value. Difference from :attr:`~mcstatus.responses.JavaStatusResponse.raw` is in that, :attr:`~mcstatus.responses.JavaStatusResponse.raw` returns raw response in the same format as we got it. This method returns the response in a more user-friendly JSON serializable format (for example, :attr:`~mcstatus.responses.BaseStatusResponse.motd` is returned as a :func:`Minecraft string ` and not :class:`dict`). """ as_dict = asdict(self) as_dict["motd"] = self.motd.simplify().to_minecraft() as_dict["players"] = asdict(self.players) as_dict["software"] = asdict(self.software) return as_dict @dataclass(frozen=True) class QueryPlayers: """Class for storing information about players on the server.""" online: int """The number of online players.""" max: int """The maximum allowed number of players (server slots).""" list: list[str] """The list of online players.""" @classmethod def build(cls, raw: RawQueryResponse, players_list: list[str]) -> Self: return cls( online=int(raw["numplayers"]), max=int(raw["maxplayers"]), list=players_list, ) @dataclass(frozen=True) class QuerySoftware: """Class for storing information about software on the server.""" version: str """The version of the software.""" brand: str """The brand of the software. Like `Paper `_ or `Spigot `_.""" plugins: list[str] """The list of plugins. Can be an empty list if hidden.""" @classmethod def build(cls, version: str, plugins: str) -> Self: brand, parsed_plugins = cls._parse_plugins(plugins) return cls( version=version, brand=brand, plugins=parsed_plugins, ) @staticmethod def _parse_plugins(plugins: str) -> tuple[str, list[str]]: """Parse plugins string to list. Returns: :class:`tuple` with two elements. First is brand of server (:attr:`.brand`) and second is a list of :attr:`plugins`. """ brand = "vanilla" parsed_plugins = [] if plugins: parts = plugins.split(":", 1) brand = parts[0].strip() if len(parts) == 2: parsed_plugins = [s.strip() for s in parts[1].split(";")] return brand, parsed_plugins