Changed code to support older Python versions
This commit is contained in:
parent
eb92d2d36f
commit
582458cdd0
5027 changed files with 794942 additions and 4 deletions
145
venv/lib/python3.11/site-packages/mcstatus/querier.py
Normal file
145
venv/lib/python3.11/site-packages/mcstatus/querier.py
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
import re
|
||||
import struct
|
||||
from abc import abstractmethod
|
||||
from collections.abc import Awaitable
|
||||
from dataclasses import dataclass, field
|
||||
from typing import ClassVar, final
|
||||
|
||||
from mcstatus.protocol.connection import Connection, UDPAsyncSocketConnection, UDPSocketConnection
|
||||
from mcstatus.responses import QueryResponse, RawQueryResponse
|
||||
|
||||
|
||||
@dataclass
|
||||
class _BaseServerQuerier:
|
||||
MAGIC_PREFIX: ClassVar = bytearray.fromhex("FEFD")
|
||||
PADDING: ClassVar = bytearray.fromhex("00000000")
|
||||
PACKET_TYPE_CHALLENGE: ClassVar = 9
|
||||
PACKET_TYPE_QUERY: ClassVar = 0
|
||||
|
||||
connection: UDPSocketConnection | UDPAsyncSocketConnection
|
||||
challenge: int = field(init=False, default=0)
|
||||
|
||||
@staticmethod
|
||||
def _generate_session_id() -> int:
|
||||
# minecraft only supports lower 4 bits
|
||||
return random.randint(0, 2**31) & 0x0F0F0F0F
|
||||
|
||||
def _create_packet(self) -> Connection:
|
||||
packet = Connection()
|
||||
packet.write(self.MAGIC_PREFIX)
|
||||
packet.write(struct.pack("!B", self.PACKET_TYPE_QUERY))
|
||||
packet.write_uint(self._generate_session_id())
|
||||
packet.write_int(self.challenge)
|
||||
packet.write(self.PADDING)
|
||||
return packet
|
||||
|
||||
def _create_handshake_packet(self) -> Connection:
|
||||
packet = Connection()
|
||||
packet.write(self.MAGIC_PREFIX)
|
||||
packet.write(struct.pack("!B", self.PACKET_TYPE_CHALLENGE))
|
||||
packet.write_uint(self._generate_session_id())
|
||||
return packet
|
||||
|
||||
@abstractmethod
|
||||
def _read_packet(self) -> Connection | Awaitable[Connection]:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def handshake(self) -> None | Awaitable[None]:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def read_query(self) -> QueryResponse | Awaitable[QueryResponse]:
|
||||
raise NotImplementedError
|
||||
|
||||
def _parse_response(self, response: Connection) -> tuple[RawQueryResponse, list[str]]:
|
||||
"""Transform the connection object (the result) into dict which is passed to the QueryResponse constructor.
|
||||
|
||||
:return: A tuple with two elements. First is `raw` answer and second is list of players.
|
||||
"""
|
||||
response.read(len("splitnum") + 3)
|
||||
data = {}
|
||||
|
||||
while True:
|
||||
key = response.read_ascii()
|
||||
if key == "hostname": # hostname is actually motd in the query protocol
|
||||
match = re.search(
|
||||
b"(.*?)\x00(hostip|hostport|game_id|gametype|map|maxplayers|numplayers|plugins|version)",
|
||||
response.received,
|
||||
flags=re.DOTALL,
|
||||
)
|
||||
motd = match.group(1) if match else ""
|
||||
# Since the query protocol does not properly support unicode, the motd is still not resolved
|
||||
# correctly; however, this will avoid other parameter parsing errors.
|
||||
data[key] = response.read(len(motd)).decode("ISO-8859-1")
|
||||
response.read(1) # ignore null byte
|
||||
elif len(key) == 0:
|
||||
response.read(1)
|
||||
break
|
||||
else:
|
||||
value = response.read_ascii()
|
||||
data[key] = value
|
||||
|
||||
response.read(len("player_") + 2)
|
||||
|
||||
players_list = []
|
||||
while True:
|
||||
player = response.read_ascii()
|
||||
if len(player) == 0:
|
||||
break
|
||||
players_list.append(player)
|
||||
|
||||
return RawQueryResponse(**data), players_list
|
||||
|
||||
|
||||
@final
|
||||
@dataclass
|
||||
class ServerQuerier(_BaseServerQuerier):
|
||||
connection: UDPSocketConnection # pyright: ignore[reportIncompatibleVariableOverride]
|
||||
|
||||
def _read_packet(self) -> Connection:
|
||||
packet = Connection()
|
||||
packet.receive(self.connection.read(self.connection.remaining()))
|
||||
packet.read(1 + 4)
|
||||
return packet
|
||||
|
||||
def handshake(self) -> None:
|
||||
self.connection.write(self._create_handshake_packet())
|
||||
|
||||
packet = self._read_packet()
|
||||
self.challenge = int(packet.read_ascii())
|
||||
|
||||
def read_query(self) -> QueryResponse:
|
||||
request = self._create_packet()
|
||||
self.connection.write(request)
|
||||
|
||||
response = self._read_packet()
|
||||
return QueryResponse.build(*self._parse_response(response))
|
||||
|
||||
|
||||
@final
|
||||
@dataclass
|
||||
class AsyncServerQuerier(_BaseServerQuerier):
|
||||
connection: UDPAsyncSocketConnection # pyright: ignore[reportIncompatibleVariableOverride]
|
||||
|
||||
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._parse_response(response))
|
||||
Loading…
Add table
Add a link
Reference in a new issue