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 asyncio import Task
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 .traits import CallbacksHolder, Runnable
from .mc.packet import Packet
from .mc.auth import AuthInterface, AuthException, MojangToken, MicrosoftAuthenticator
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.play.serverbound import PacketKeepAlive as PacketKeepAliveResponse
from .mc.proto.play.clientbound import PacketKeepAlive, PacketSetCompression, PacketKickDisconnect
@ -44,7 +47,7 @@ class MinecraftClient(CallbacksHolder, Runnable):
_authenticator:MicrosoftAuthenticator
_username:str
code:str
code:Optional[str]
dispatcher : Dispatcher
_processing : bool
@ -169,7 +172,65 @@ class MinecraftClient(CallbacksHolder, Runnable):
await self.join_callbacks()
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):
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:
if self.online_mode:
try:
@ -183,11 +244,13 @@ class MinecraftClient(CallbacksHolder, Runnable):
try:
packet_whitelist = self.callback_keys(filter=Packet) if self.options.use_packet_whitelist else set()
await self.dispatcher.connect(
self.host,
self.port,
host=self.host,
port=self.port,
proto=server_data['version']['protocol'],
queue_timeout=self.options.poll_interval,
packet_whitelist=packet_whitelist
)
self.dispatcher.proto = server_data['version']['protocol'] # TODO maybe check if it's supported?
await self._handshake()
if await self._login():
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
await self.dispatcher.write(
PacketSetProtocol(
340, # TODO!!!!
protocolVersion=340,
self.dispatcher.proto,
protocolVersion=self.dispatcher.proto,
serverHost=self.host,
serverPort=self.port,
nextState=2, # play
@ -218,7 +281,7 @@ class MinecraftClient(CallbacksHolder, Runnable):
)
await self.dispatcher.write(
PacketLoginStart(
340,
self.dispatcher.proto,
username=self._authenticator.selectedProfile.name if self.online_mode else self._username
)
)
@ -256,7 +319,7 @@ class MinecraftClient(CallbacksHolder, Runnable):
else:
self._logger.warning("Server gave an offline-mode serverId but still requested Encryption")
encryption_response = PacketEncryptionResponse(
340, # TODO!!!!
self.dispatcher.proto,
sharedSecret=encrypted_secret,
verifyToken=token
)

View file

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

View file

@ -85,7 +85,7 @@ class MicrosoftAuthenticator(AuthInterface):
async def validate(self):
prof = await self.fetch_profile()
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:
"""Authorize Microsoft account"""