added basic server ping, proto is defined per connection, fixes

This commit is contained in:
əlemi 2022-02-22 00:47:47 +01:00
parent dcf5ad1f08
commit 34def8b6cc
No known key found for this signature in database
GPG key ID: BBCBFE5D7244634E
3 changed files with 86 additions and 20 deletions

View file

@ -6,14 +6,17 @@ import uuid
from dataclasses import dataclass from dataclasses import dataclass
from asyncio import Task from asyncio import Task
from enum import Enum from enum import Enum
from time import time
from typing import Dict, List, Callable, Type, Optional, Tuple, AsyncIterator from typing import Dict, List, Callable, Type, Optional, Tuple, AsyncIterator, Any
from .dispatcher import Dispatcher from .dispatcher import Dispatcher
from .traits import CallbacksHolder, Runnable from .traits import CallbacksHolder, Runnable
from .mc.packet import Packet from .mc.packet import Packet
from .mc.auth import AuthInterface, AuthException, MojangToken, MicrosoftAuthenticator from .mc.auth import AuthInterface, AuthException, MojangToken, MicrosoftAuthenticator
from .mc.definitions import Dimension, Difficulty, Gamemode, ConnectionState from .mc.definitions import Dimension, Difficulty, Gamemode, ConnectionState
from .mc.proto.status.serverbound import PacketPing, PacketPingStart
from .mc.proto.status.clientbound import PacketServerInfo, PacketPing as PacketPong
from .mc.proto.handshaking.serverbound import PacketSetProtocol from .mc.proto.handshaking.serverbound import PacketSetProtocol
from .mc.proto.play.serverbound import PacketKeepAlive as PacketKeepAliveResponse from .mc.proto.play.serverbound import PacketKeepAlive as PacketKeepAliveResponse
from .mc.proto.play.clientbound import PacketKeepAlive, PacketSetCompression, PacketKickDisconnect from .mc.proto.play.clientbound import PacketKeepAlive, PacketSetCompression, PacketKickDisconnect
@ -44,7 +47,7 @@ class MinecraftClient(CallbacksHolder, Runnable):
_authenticator:MicrosoftAuthenticator _authenticator:MicrosoftAuthenticator
_username:str _username:str
code:str code:Optional[str]
dispatcher : Dispatcher dispatcher : Dispatcher
_processing : bool _processing : bool
@ -169,7 +172,65 @@ class MinecraftClient(CallbacksHolder, Runnable):
await self.join_callbacks() await self.join_callbacks()
await super().stop(force) await super().stop(force)
async def info(self, host:str="", port:int=0, ping:bool=False) -> Dict[str, Any]:
"""Make a mini connection to asses server status and version"""
await self.dispatcher.connect(
host or self.host,
port or self.port,
)
#Handshake
await self.dispatcher.write(
PacketSetProtocol(
self.dispatcher.proto,
protocolVersion=self.dispatcher.proto,
serverHost=host or self.host,
serverPort=port or self.port,
nextState=ConnectionState.STATUS.value,
)
)
self.dispatcher.state = ConnectionState.STATUS
#Request
await self.dispatcher.write(
PacketPingStart(self.dispatcher.proto) #empty packet
)
#Response
data : Dict[str, Any] = {}
ping_id : int = 0
ping_time : float = 0
async for packet in self.dispatcher.packets():
if isinstance(packet, PacketServerInfo):
data = json.loads(packet.response)
self._logger.debug("Server data : %s", json.dumps(data, indent=2))
if not ping:
break
ping_id = int(time())
ping_time = time()
await self.dispatcher.write(
PacketPing(
self.dispatcher.proto,
time=ping_id,
)
)
if isinstance(packet, PacketPong):
if packet.time == ping_id:
data['ping'] = int(time() - ping_time)
break
await self.dispatcher.disconnect()
return data
async def _client_worker(self): async def _client_worker(self):
try:
self._logger.info("Pinging server")
server_data = await self.info()
self._logger.info(
"Connecting to: %s (%d/%d)",
server_data['version']['name'],
server_data['players']['online'],
server_data['players']['max']
)
except Exception:
self._logger.exception("Exception checking server stats")
return
while self._processing: while self._processing:
if self.online_mode: if self.online_mode:
try: try:
@ -183,11 +244,13 @@ class MinecraftClient(CallbacksHolder, Runnable):
try: try:
packet_whitelist = self.callback_keys(filter=Packet) if self.options.use_packet_whitelist else set() packet_whitelist = self.callback_keys(filter=Packet) if self.options.use_packet_whitelist else set()
await self.dispatcher.connect( await self.dispatcher.connect(
self.host, host=self.host,
self.port, port=self.port,
proto=server_data['version']['protocol'],
queue_timeout=self.options.poll_interval, queue_timeout=self.options.poll_interval,
packet_whitelist=packet_whitelist packet_whitelist=packet_whitelist
) )
self.dispatcher.proto = server_data['version']['protocol'] # TODO maybe check if it's supported?
await self._handshake() await self._handshake()
if await self._login(): if await self._login():
await self._play() await self._play()
@ -209,8 +272,8 @@ class MinecraftClient(CallbacksHolder, Runnable):
async def _handshake(self) -> bool: # TODO make this fancier! poll for version and status first async def _handshake(self) -> bool: # TODO make this fancier! poll for version and status first
await self.dispatcher.write( await self.dispatcher.write(
PacketSetProtocol( PacketSetProtocol(
340, # TODO!!!! self.dispatcher.proto,
protocolVersion=340, protocolVersion=self.dispatcher.proto,
serverHost=self.host, serverHost=self.host,
serverPort=self.port, serverPort=self.port,
nextState=2, # play nextState=2, # play
@ -218,7 +281,7 @@ class MinecraftClient(CallbacksHolder, Runnable):
) )
await self.dispatcher.write( await self.dispatcher.write(
PacketLoginStart( PacketLoginStart(
340, self.dispatcher.proto,
username=self._authenticator.selectedProfile.name if self.online_mode else self._username username=self._authenticator.selectedProfile.name if self.online_mode else self._username
) )
) )
@ -256,7 +319,7 @@ class MinecraftClient(CallbacksHolder, Runnable):
else: else:
self._logger.warning("Server gave an offline-mode serverId but still requested Encryption") self._logger.warning("Server gave an offline-mode serverId but still requested Encryption")
encryption_response = PacketEncryptionResponse( encryption_response = PacketEncryptionResponse(
340, # TODO!!!! self.dispatcher.proto,
sharedSecret=encrypted_secret, sharedSecret=encrypted_secret,
verifyToken=token verifyToken=token
) )

View file

@ -42,8 +42,8 @@ class Dispatcher:
_packet_whitelist : Set[Packet] _packet_whitelist : Set[Packet]
_packet_id_whitelist : Set[int] _packet_id_whitelist : Set[int]
_host : str host : str
_port : int port : int
proto : int proto : int
state : ConnectionState state : ConnectionState
@ -53,9 +53,10 @@ class Dispatcher:
_logger : logging.Logger _logger : logging.Logger
def __init__(self, server:bool = False): def __init__(self, server:bool = False):
self.proto = 757
self._is_server = server self._is_server = server
self._host = "localhost" self.host = "localhost"
self._port = 25565 self.port = 25565
self._prepare() self._prepare()
@property @property
@ -93,13 +94,15 @@ class Dispatcher:
def _prepare(self, def _prepare(self,
host:Optional[str] = None, host:Optional[str] = None,
port:Optional[int] = None, port:Optional[int] = None,
proto:Optional[int] = None,
queue_timeout:int = 1, queue_timeout:int = 1,
queue_size:int = 100, queue_size:int = 100,
packet_whitelist : List[Packet] = None packet_whitelist : List[Packet] = None
): ):
self._host = host or self._host or "localhost" self.proto = proto or self.proto or 757 # TODO not hardcode this?
self._port = port or self._port or 25565 self.host = host or self.host or "localhost"
self._logger = LOGGER.getChild(f"on({self._host}:{self._port})") self.port = port or self.port or 25565
self._logger = LOGGER.getChild(f"on({self.host}:{self.port})")
self._packet_whitelist = set(packet_whitelist) if packet_whitelist else set() self._packet_whitelist = set(packet_whitelist) if packet_whitelist else set()
if self._packet_whitelist: if self._packet_whitelist:
self._packet_whitelist.add(minecraft_protocol.play.clientbound.PacketKeepAlive) self._packet_whitelist.add(minecraft_protocol.play.clientbound.PacketKeepAlive)
@ -108,7 +111,6 @@ class Dispatcher:
self.encryption = False self.encryption = False
self.compression = None self.compression = None
self.state = ConnectionState.HANDSHAKING self.state = ConnectionState.HANDSHAKING
self.proto = 340 # TODO
# This can only happen after we know the connection protocol # This can only happen after we know the connection protocol
self._packet_id_whitelist = set((P(self.proto).id for P in self._packet_whitelist)) if self._packet_whitelist else set() self._packet_id_whitelist = set((P(self.proto).id for P in self._packet_whitelist)) if self._packet_whitelist else set()
@ -123,6 +125,7 @@ class Dispatcher:
async def connect(self, async def connect(self,
host : Optional[str] = None, host : Optional[str] = None,
port : Optional[int] = None, port : Optional[int] = None,
proto : Optional[int] = None,
reader : Optional[StreamReader] = None, reader : Optional[StreamReader] = None,
writer : Optional[StreamWriter] = None, writer : Optional[StreamWriter] = None,
queue_timeout : int = 1, queue_timeout : int = 1,
@ -132,14 +135,14 @@ class Dispatcher:
if self.connected: if self.connected:
raise InvalidState("Dispatcher already connected") raise InvalidState("Dispatcher already connected")
self._prepare(host, port, queue_timeout, queue_size, packet_whitelist) self._prepare(host, port, proto, queue_timeout, queue_size, packet_whitelist)
if reader and writer: if reader and writer:
self._down, self._up = reader, writer self._down, self._up = reader, writer
else: else:
self._down, self._up = await asyncio.open_connection( self._down, self._up = await asyncio.open_connection(
host=self._host, host=self.host,
port=self._port, port=self.port,
) )
self._dispatching = True self._dispatching = True

View file

@ -85,7 +85,7 @@ class MicrosoftAuthenticator(AuthInterface):
async def validate(self): async def validate(self):
prof = await self.fetch_profile() prof = await self.fetch_profile()
self.selectedProfile = GameProfile(id=prof['id'], name=prof['name']) self.selectedProfile = GameProfile(id=prof['id'], name=prof['name'])
logging.info("Session validated : %s", self.selectedProfile) logging.info("Session validated : %s", repr(self.selectedProfile))
async def authenticate(self, code:str="") -> str: async def authenticate(self, code:str="") -> str:
"""Authorize Microsoft account""" """Authorize Microsoft account"""