reworked initialization
tldr now treepuncher handles initializing everything
This commit is contained in:
parent
c9f65f56aa
commit
af4cf7db90
2 changed files with 85 additions and 78 deletions
|
@ -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)
|
||||
|
|
|
@ -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,7 +57,9 @@ 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}'")
|
||||
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)
|
||||
|
@ -80,6 +74,7 @@ class Addon:
|
|||
async def cleanup(self):
|
||||
pass
|
||||
|
||||
|
||||
class Treepuncher(
|
||||
GameState,
|
||||
GameChat,
|
||||
|
@ -87,28 +82,26 @@ class Treepuncher(
|
|||
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
|
||||
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)
|
||||
# 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,11 +205,11 @@ 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
|
||||
if self._processing:
|
||||
|
|
Loading…
Reference in a new issue