reworked initialization

tldr now treepuncher handles initializing everything
This commit is contained in:
əlemi 2022-04-18 19:35:49 +02:00
parent c9f65f56aa
commit af4cf7db90
No known key found for this signature in database
GPG key ID: BBCBFE5D7244634E
2 changed files with 85 additions and 78 deletions

View file

@ -53,16 +53,17 @@ def main():
)
parser.add_argument('name', help='name to use for this client session')
parser.add_argument('server', help='server to connect to')
parser.add_argument('-c', '--code', dest='code', action='store_const', const=True, default=False, help="request new code to login")
parser.add_argument('--client-id', dest='cid', default='c63ef189-23cb-453b-8060-13800b85d2dc', help='client_id of your Azure application')
parser.add_argument('--secret', dest='secret', default='N2e7Q~ybYA0IO39KB1mFD4GmoYzISRaRNyi59', help='client_secret of your Azure application')
parser.add_argument('--redirect-uri', dest='uri', default='https://fantabos.co/msauth', help='redirect_uri of your Azure application')
parser.add_argument('--server', dest='server', default='', help='server to connect to')
parser.add_argument('--debug', dest='_debug', action='store_const', const=True, default=False, help="enable debug logs")
parser.add_argument('--no-packet-filter', dest='use_packet_whitelist', action='store_const', const=False, default=True, help="disable packet whitelist, will decrease performance")
parser.add_argument('--code', dest='code', default='', help='login code for oauth2 flow')
parser.add_argument('--mojang', dest='mojang', action='store_const', const=True, default=False, help="use legacy Mojang authenticator")
parser.add_argument('--addon-path', dest='path', default='', help='path for loading addons')
parser.add_argument('--chat-log', dest='chat_log', action='store_const', const=True, default=False, help="print (colored) chat to terminal")
parser.add_argument('--chat-input', dest='chat_input', action='store_const', const=True, default=False, help="read input from stdin and send it to chat")
parser.add_argument('--debug', dest='_debug', action='store_const', const=True, default=False, help="enable debug logs")
parser.add_argument('--no-packet-filter', dest='use_packet_whitelist', action='store_const', const=False, default=True, help="disable packet whitelist, will decrease performance")
parser.add_argument('--addons', dest='add', nargs='+', type=str, default=[a.__name__ for a in addons], help='specify addons to enable, defaults to all')
# TODO find a better way to specify which addons are enabled
@ -71,18 +72,15 @@ def main():
configure_logging(args.name, level=logging.DEBUG if args._debug else logging.INFO)
setproctitle(f"treepuncher[{args.name}]")
code = None
if args.code:
code = input(f"-> Go to 'https://fantabos.co/msauth?client_id={args.cid}&state=hardcoded', click 'Auth' and login, then copy here the code you received\n--> ")
kwargs = {}
if args.server:
kwargs["server"] = args.server
client = Treepuncher(
args.name,
args.server,
use_packet_whitelist=args.use_packet_whitelist,
login_code=code,
client_id=args.cid,
client_secret=args.secret,
redirect_uri=args.uri
)
enabled_addons = set(a.lower() for a in args.add)

View file

@ -5,40 +5,32 @@ import asyncio
import datetime
import uuid
from typing import List, Dict, Tuple, Union, Optional, Any, Type, get_type_hints
from enum import Enum
from typing import List, Dict, Optional, Any, Type, get_type_hints
from time import time
from dataclasses import dataclass, MISSING
from configparser import ConfigParser
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from aiocraft.client import MinecraftClient
from aiocraft.util import helpers
from aiocraft.mc.packet import Packet
from aiocraft.mc.definitions import ConnectionState
from aiocraft.mc.auth import AuthInterface, MicrosoftAuthenticator, MojangAuthenticator
from aiocraft.mc.proto import PacketSetCompression, PacketKickDisconnect
from aiocraft.mc.proto.play.clientbound import PacketKeepAlive
from aiocraft.mc.proto.play.serverbound import PacketKeepAlive as PacketKeepAliveResponse
from aiocraft.mc.auth import AuthInterface, AuthException, MojangAuthenticator, MicrosoftAuthenticator, OfflineAuthenticator
from .scaffold import Scaffold
from .events import ConnectedEvent, DisconnectedEvent
from .storage import Storage, SystemState
from .notifier import Notifier
from .game import GameState, GameChat, GameInventory, GameTablist, GameWorld
from .traits import CallbacksHolder, Runnable
REMOVE_COLOR_FORMATS = re.compile(r"§[0-9a-z]")
class ConfigObject:
def __getitem__(self, key:str) -> Any:
def __getitem__(self, key: str) -> Any:
return getattr(self, key)
class Addon:
name : str
config : ConfigObject
_client : 'Treepuncher'
name: str
config: ConfigObject
_client: 'Treepuncher'
@dataclass(frozen=True)
class Options(ConfigObject):
@ -48,11 +40,11 @@ class Addon:
def client(self) -> 'Treepuncher':
return self._client
def __init__(self, client:'Treepuncher', *args, **kwargs):
def __init__(self, client: 'Treepuncher', *args, **kwargs):
self._client = client
self.name = type(self).__name__
cfg = self._client.config
opts : Dict[str, Any] = {}
opts: Dict[str, Any] = {}
cfg_clazz = get_type_hints(type(self))['config']
if cfg_clazz is not ConfigObject:
for name, field in cfg_clazz.__dataclass_fields__.items():
@ -65,8 +57,10 @@ class Addon:
else:
opts[name] = field.type(self._client.config[self.name].get(name))
elif default is MISSING:
raise ValueError(f"Missing required value '{name}' of type '{field.type.__name__}' in section '{self.name}'")
else: # not really necessary since it's a dataclass but whatever
raise ValueError(
f"Missing required value '{name}' of type '{field.type.__name__}' in section '{self.name}'"
)
else: # not really necessary since it's a dataclass but whatever
opts[name] = default
self.config = self.Options(**opts)
self.register()
@ -80,35 +74,34 @@ class Addon:
async def cleanup(self):
pass
class Treepuncher(
GameState,
GameChat,
GameInventory,
GameTablist,
GameWorld
GameState,
GameChat,
GameInventory,
GameTablist,
GameWorld
):
name : str
config : ConfigParser
storage : Storage
name: str
config: ConfigParser
storage: Storage
notifier : Notifier
scheduler : AsyncIOScheduler
modules : List[Addon]
ctx : Dict[Any, Any]
notifier: Notifier
scheduler: AsyncIOScheduler
modules: List[Addon]
ctx: Dict[Any, Any]
_processing : bool
_processing: bool
def __init__(
self,
name:str,
server:str,
config_file:str=None,
notifier:Notifier=None,
online_mode:bool=True,
authenticator:Optional[AuthInterface]=None,
name: str,
config_file: str = None,
online_mode: bool = True,
legacy: bool = False,
notifier: Notifier = None,
**kwargs
):
super().__init__(server, online_mode=online_mode, authenticator=authenticator, username=name)
self.ctx = dict()
self.name = name
@ -116,32 +109,48 @@ class Treepuncher(
config_path = config_file or f'{self.name}.ini'
self.config.read(config_path)
authenticator : AuthInterface
def opt(k:str) -> Any:
return kwargs.get(k) or self.config['Treepuncher'].get(k)
if not online_mode:
authenticator = OfflineAuthenticator(self.name)
elif legacy:
authenticator = MojangAuthenticator(
username= opt('username') or name,
password= opt('password')
)
else:
authenticator = MicrosoftAuthenticator(
client_id= opt('client_id'),
client_secret= opt('client_secret'),
redirect_uri= opt('redirect_uri'),
code= opt('code'),
)
self.storage = Storage(self.name)
prev = self.storage.system() # if this isn't 1st time, this won't be None. Load token from there
prev = self.storage.system() # if this isn't 1st time, this won't be None. Load token from there
if prev:
if self.name != prev.name:
self._logger.warning("Saved credentials are not from this session")
self._authenticator.deserialize(json.loads(prev.token))
self._logger.info("Loaded credentials")
self.logger.warning("Saved credentials belong to another session")
authenticator.deserialize(json.loads(prev.token))
self.logger.info("Loaded credentials")
self.modules = []
self.notifier = notifier or Notifier()
tz = datetime.datetime.now(datetime.timezone.utc).astimezone().tzname() # APScheduler will complain if I don't specify a timezone...
self.scheduler = AsyncIOScheduler(timezone=tz)
logging.getLogger('apscheduler.executors.default').setLevel(logging.WARNING) # So it's way less spammy
# tz = datetime.datetime.now(datetime.timezone.utc).astimezone().tzname() # This doesn't work anymore
self.scheduler = AsyncIOScheduler() # TODO APScheduler warns about timezone ugghh
logging.getLogger('apscheduler.executors.default').setLevel(logging.WARNING) # So it's way less spammy
self.scheduler.start(paused=True)
super().__init__(opt('server'), online_mode=online_mode, authenticator=authenticator)
@property
def playerName(self) -> str:
if self.online_mode:
if self._authenticator and self._authenticator.selectedProfile:
return self._authenticator.selectedProfile.name
raise ValueError("Username unknown: client not authenticated")
else:
if self._username:
return self._username
raise ValueError("No username configured for offline mode")
return self.authenticator.selectedProfile.name
async def authenticate(self):
await super().authenticate()
@ -162,9 +171,9 @@ class Treepuncher(
self._processing = True
self._worker = asyncio.get_event_loop().create_task(self._work())
self.scheduler.resume()
self._logger.info("Treepuncher started")
self.logger.info("Treepuncher started")
async def stop(self, force:bool=False):
async def stop(self, force: bool = False):
self._processing = False
self.scheduler.pause()
if self.dispatcher.connected:
@ -176,9 +185,9 @@ class Treepuncher(
await m.cleanup()
await self.notifier.cleanup()
await super().stop()
self._logger.info("Treepuncher stopped")
self.logger.info("Treepuncher stopped")
def install(self, module:Type[Addon]) -> Type[Addon]:
def install(self, module: Type[Addon]) -> Type[Addon]:
self.modules.append(module(self))
return module
@ -186,7 +195,7 @@ class Treepuncher(
try:
server_data = await self.info(host=self.host, port=self.port)
except Exception:
return self._logger.exception("exception while pinging server")
return self.logger.exception("exception while pinging server")
while self._processing:
try:
await self.join(
@ -196,12 +205,12 @@ class Treepuncher(
packet_whitelist=self.callback_keys(filter=Packet),
)
except ConnectionRefusedError:
self._logger.error("Server rejected connection")
self.logger.error("Server rejected connection")
except OSError as e:
self._logger.error("Connection error : %s", str(e))
self.logger.error("Connection error : %s", str(e))
except Exception:
self._logger.exception("Unhandled exception")
self.logger.exception("Unhandled exception")
break
await asyncio.sleep(5) # TODO setting
await asyncio.sleep(5) # TODO setting
if self._processing:
await self.stop(force=True)