added basic server ping, proto is defined per connection, fixes
This commit is contained in:
parent
dcf5ad1f08
commit
34def8b6cc
3 changed files with 86 additions and 20 deletions
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"""
|
||||||
|
|
Loading…
Reference in a new issue