Various fixes, implemented credentials storage and addons with config vars

This commit is contained in:
əlemi 2022-02-16 04:07:11 +01:00
parent d5934da832
commit d12b4b4f88
No known key found for this signature in database
GPG key ID: BBCBFE5D7244634E
5 changed files with 49 additions and 35 deletions

View file

@ -36,6 +36,8 @@ def main():
for addon in addons: for addon in addons:
help_text += f"\n {addon.__name__} \t{addon.__doc__ or ''}" help_text += f"\n {addon.__name__} \t{addon.__doc__ or ''}"
cfg_clazz = get_type_hints(addon)['config'] cfg_clazz = get_type_hints(addon)['config']
if cfg_clazz is ConfigObject:
continue # it's the superclass type hint
for name, field in cfg_clazz.__dataclass_fields__.items(): for name, field in cfg_clazz.__dataclass_fields__.items():
default = field.default if field.default is not MISSING \ default = field.default if field.default is not MISSING \
else field.default_factory() if field.default_factory is not MISSING \ else field.default_factory() if field.default_factory is not MISSING \
@ -61,6 +63,8 @@ def main():
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('--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('--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('--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
args = parser.parse_args() args = parser.parse_args()
@ -74,15 +78,18 @@ def main():
client = Treepuncher( client = Treepuncher(
args.name, args.name,
args.server, args.server,
use_packet_whitelist=use_packet_whitelist, use_packet_whitelist=args.use_packet_whitelist,
login_code=code, login_code=code,
client_id=args.cid, client_id=args.cid,
client_secret=args.secret, client_secret=args.secret,
redirect_uri=args.uri redirect_uri=args.uri
) )
enabled_addons = set(a.lower() for a in args.add)
for addon in addons: for addon in addons:
client.install(addon) if addon.__name__.lower() in enabled_addons:
logging.info("Installing '%s'", addon.__name__)
client.install(addon)
client.run() client.run()

View file

@ -1,5 +1,6 @@
import asyncio import asyncio
import datetime import datetime
import functools
from aiocraft.client import MinecraftClient from aiocraft.client import MinecraftClient
from aiocraft.mc.definitions import Gamemode, Dimension, Difficulty from aiocraft.mc.definitions import Gamemode, Dimension, Difficulty
@ -27,20 +28,20 @@ class GameState(MinecraftClient):
def on_death(self): def on_death(self):
def decorator(fun): def decorator(fun):
@functool.wraps(fun) @functools.wraps(fun)
async def wrapper(): async def wrapper():
event = DeathEvent() event = DeathEvent()
return await fun(event) return await fun(event)
return self.register(DeathEvent.SENTINEL, fun) return self.register(DeathEvent.SENTINEL, wrapper)
return decorator return decorator
def on_joined_world(self): def on_joined_world(self):
def decorator(fun): def decorator(fun):
@functool.wraps(fun) @functools.wraps(fun)
async def wrapper(): async def wrapper():
event = JoinGameEvent() event = JoinGameEvent()
return await fun(event) return await fun(event)
return self.register(JoinGameEvent.SENTINEL, callback) return self.register(JoinGameEvent.SENTINEL, wrapper)
return decorator return decorator
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View file

@ -1,6 +1,6 @@
from typing import Callable, List from typing import Callable, List
class Notifier: class Notifier: # TODO this should be an Addon too!
_report_functions : List[Callable] _report_functions : List[Callable]
def __init__(self): def __init__(self):
@ -16,9 +16,9 @@ class Notifier:
def notify(self, text, log:bool = False, **kwargs): def notify(self, text, log:bool = False, **kwargs):
print(text) print(text)
async def initialize(self, _client:'Treepuncher'): async def initialize(self):
pass pass
async def cleanup(self, _client:'Treepuncher'): async def cleanup(self):
pass pass

View file

@ -1,3 +1,4 @@
import os
import json import json
import sqlite3 import sqlite3
@ -16,7 +17,10 @@ class Storage:
def __init__(self, name:str): def __init__(self, name:str):
self.name = name self.name = name
init = not os.path.isfile(f"{name}.session")
self.db = sqlite3.connect(f'{name}.session') self.db = sqlite3.connect(f'{name}.session')
if init:
self._init_db()
def __del__(self): def __del__(self):
self.close() self.close()

View file

@ -7,7 +7,8 @@ import uuid
from typing import List, Dict, Tuple, Union, Optional, Any, Type, get_type_hints from typing import List, Dict, Tuple, Union, Optional, Any, Type, get_type_hints
from enum import Enum from enum import Enum
from dataclasses import dataclass from time import time
from dataclasses import dataclass, MISSING
from configparser import ConfigParser from configparser import ConfigParser
from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.schedulers.asyncio import AsyncIOScheduler
@ -27,12 +28,12 @@ class ConfigObject:
class Addon: class Addon:
name : str name : str
config : ConfigObject
_client : 'Treepuncher' _client : 'Treepuncher'
@dataclass(frozen=True) @dataclass(frozen=True)
class Options(ConfigObject): class Options(ConfigObject):
pass pass
config : Options
@property @property
def client(self) -> 'Treepuncher': def client(self) -> 'Treepuncher':
@ -43,21 +44,25 @@ class Addon:
self.name = type(self).__name__ self.name = type(self).__name__
cfg = self._client.config cfg = self._client.config
opts : Dict[str, Any] = {} opts : Dict[str, Any] = {}
for name, clazz in get_type_hints(self.Options).items(): cfg_clazz = get_type_hints(type(self))['config']
default = getattr(self.Options, name, None) if cfg_clazz is not ConfigObject:
if cfg.has_option(self.name, name): for name, field in cfg_clazz.__dataclass_fields__.items():
if clazz is bool: default = field.default if field.default is not MISSING \
opts[name] = self._client.config[self.name].getboolean(name) else field.default_factory() if field.default_factory is not MISSING \
else: else MISSING
opts[name] = clazz(self._client.config[self.name].get(name)) if cfg.has_option(self.name, name):
elif default is None: if field.type is bool:
raise ValueError(f"Missing required value '{name}' of type '{clazz.__name__}' in section '{self.name}'") opts[name] = self._client.config[self.name].getboolean(name)
else: # not really necessary since it's a dataclass but whatever else:
opts[name] = default 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
opts[name] = default
self.config = self.Options(**opts) self.config = self.Options(**opts)
self.register(client) self.register()
def register(self, client:'Treepuncher'): def register(self):
pass pass
async def initialize(self): async def initialize(self):
@ -82,25 +87,26 @@ class Treepuncher(
modules : List[Addon] modules : List[Addon]
ctx : Dict[Any, Any] ctx : Dict[Any, Any]
def __init__(self, name:str, *args, config_file:str=None, **kwargs): def __init__(self, name:str, *args, config_file:str=None, notifier:Notifier=None, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.ctx = dict() self.ctx = dict()
self.name = name self.name = name
self.config = ConfigParser() self.config = ConfigParser()
config_path = config_file or f'config-{self.name}.ini' config_path = config_file or f'{self.name}.ini'
self.config.read(config_path) self.config.read(config_path)
self.storage = Storage(self.name) 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 prev:
if self.name != prev.name: if self.name != prev.name:
self._logger.warning("Saved token session name differs from current") self._logger.warning("Saved credentials are not from this session")
self._authenticator.deserialize(json.loads(prev.token)) self._authenticator.deserialize(json.loads(prev.token))
self._logger.info("Loaded credentials")
self.modules = [] self.modules = []
# self.notifier = notifier or Notifier() self.notifier = notifier or Notifier()
tz = datetime.datetime.now(datetime.timezone.utc).astimezone().tzname() # APScheduler will complain if I don't specify a timezone... tz = datetime.datetime.now(datetime.timezone.utc).astimezone().tzname() # APScheduler will complain if I don't specify a timezone...
self.scheduler = AsyncIOScheduler(timezone=tz) self.scheduler = AsyncIOScheduler(timezone=tz)
logging.getLogger('apscheduler.executors.default').setLevel(logging.WARNING) # So it's way less spammy logging.getLogger('apscheduler.executors.default').setLevel(logging.WARNING) # So it's way less spammy
@ -127,9 +133,9 @@ class Treepuncher(
self.storage._set_state(state) self.storage._set_state(state)
async def start(self): async def start(self):
await self.notifier.initialize(self) await self.notifier.initialize()
for m in self.modules: for m in self.modules:
await m.initialize(self) await m.initialize()
await super().start() await super().start()
self.scheduler.resume() self.scheduler.resume()
@ -138,12 +144,8 @@ class Treepuncher(
await super().stop(force=force) await super().stop(force=force)
for m in self.modules: for m in self.modules:
await m.cleanup() await m.cleanup()
await self.notifier.cleanup(self) await self.notifier.cleanup()
def install(self, module:Type[Addon]) -> Type[Addon]: def install(self, module:Type[Addon]) -> Type[Addon]:
self.modules.append(module(self)) self.modules.append(module(self))
return module return module
async def write(self, packet:Packet, wait:bool=False):
await self.dispatcher.write(packet, wait)