diff --git a/CHANGELOG.md b/CHANGELOG.md index a77a9bd..e8064c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Next version +- change how config files are passed along +- change module system to support config file changes + # 1.0.0 (2019-04-13) - add fancy argument parsing diff --git a/yaboli/__init__.py b/yaboli/__init__.py index a964c61..a9df9c2 100644 --- a/yaboli/__init__.py +++ b/yaboli/__init__.py @@ -1,6 +1,7 @@ import asyncio +import configparser import logging -from typing import Callable +from typing import Callable, Dict from .bot import * from .client import * @@ -48,12 +49,33 @@ def enable_logging(name: str = "yaboli", level: int = logging.INFO) -> None: logger.addHandler(handler) def run( - client: Callable[[str], Client], - config_file: str = "bot.conf" + bot_constructor: BotConstructor, + config_file: str = "bot.conf", ) -> None: + # Load the config file + config = configparser.ConfigParser(allow_no_value=True) + config.read(config_file) + async def _run() -> None: while True: - client_ = client(config_file) - await client_.run() + bot = bot_constructor(config, config_file) + await bot.run() + + asyncio.run(_run()) + +def run_modulebot( + modulebot_constructor: ModuleBotConstructor, + module_constructors: Dict[str, ModuleConstructor], + config_file: str = "bot.conf", + ) -> None: + # Load the config file + config = configparser.ConfigParser(allow_no_value=True) + config.read(config_file) + + async def _run() -> None: + while True: + modulebot = modulebot_constructor(config, config_file, + module_constructors) + await modulebot.run() asyncio.run(_run()) diff --git a/yaboli/bot.py b/yaboli/bot.py index 3006773..c696820 100644 --- a/yaboli/bot.py +++ b/yaboli/bot.py @@ -1,7 +1,7 @@ import configparser import datetime import logging -from typing import List, Optional +from typing import Callable, List, Optional from .client import Client from .command import * @@ -11,7 +11,7 @@ from .util import * logger = logging.getLogger(__name__) -__all__ = ["Bot"] +__all__ = ["Bot", "BotConstructor"] class Bot(Client): ALIASES: List[str] = [] @@ -25,12 +25,13 @@ class Bot(Client): GENERAL_SECTION = "general" ROOMS_SECTION = "rooms" - def __init__(self, config_file: str) -> None: + def __init__(self, + config: configparser.ConfigParser, + config_file: str, + ) -> None: + self.config = config self.config_file = config_file - self.config = configparser.ConfigParser(allow_no_value=True) - self.config.read(self.config_file) - nick = self.config[self.GENERAL_SECTION].get("nick") if nick is None: logger.warn(("'nick' not set in config file. Defaulting to empty" @@ -188,3 +189,5 @@ class Bot(Client): logger.info(f"Restarted in &{room.name} by {message.sender.atmention}") await message.reply(self.RESTART_REPLY) await self.stop() + +BotConstructor = Callable[[configparser.ConfigParser, str], Bot] diff --git a/yaboli/module.py b/yaboli/module.py index 2dc9b0f..ac750bf 100644 --- a/yaboli/module.py +++ b/yaboli/module.py @@ -1,5 +1,6 @@ +import configparser import logging -from typing import Dict, List, Optional +from typing import Callable, Dict, List, Optional from .bot import Bot from .command import * @@ -10,49 +11,77 @@ from .util import * logger = logging.getLogger(__name__) -__all__ = ["Module", "ModuleBot"] +__all__ = ["Module", "ModuleConstructor", "ModuleBot", "ModuleBotConstructor"] class Module(Bot): DESCRIPTION: Optional[str] = None - def __init__(self, config_file: str, standalone: bool) -> None: - super().__init__(config_file) + def __init__(self, + config: configparser.ConfigParser, + config_file: str, + standalone: bool = True, + ) -> None: + super().__init__(config, config_file) self.standalone = standalone +ModuleConstructor = Callable[[configparser.ConfigParser, str, bool], Module] + class ModuleBot(Bot): HELP_PRE: Optional[List[str]] = [ "This bot contains the following modules:" ] HELP_POST: Optional[List[str]] = [ - "" - "Use \"!help {atmention} \" to get more information on a" - " specific module." + "", + "For module-specific help, try \"!help {atmention} \".", ] MODULE_HELP_LIMIT = 5 - def __init__(self, config_file: str) -> None: - super().__init__(config_file) + MODULES_SECTION = "modules" + def __init__(self, + config: configparser.ConfigParser, + config_file: str, + module_constructors: Dict[str, ModuleConstructor], + ) -> None: + super().__init__(config, config_file) + + self.module_constructors = module_constructors self.modules: Dict[str, Module] = {} - self.register_botrulez(help_=False) - self.register_general("help", self.cmd_help_general, args=False) - self.register_specific("help", self.cmd_help_specific, args=True) + # Load initial modules + for module_name in self.config[self.MODULES_SECTION]: + module_constructor = self.module_constructors.get(module_name) + if module_constructor is None: + logger.warn(f"Module {module_name} not found") + continue + # standalone is set to False + module = module_constructor(self.config, self.config_file, False) + self.load_module(module_name, module) - def register_module(self, name: str, module: Module) -> None: + def load_module(self, name: str, module: Module) -> None: if name in self.modules: logger.warn(f"Module {name!r} is already registered, overwriting...") self.modules[name] = module + def unload_module(self, name: str) -> None: + if name in self.modules: + del self.modules[name] + + # Better help messages + def compile_module_overview(self) -> List[str]: lines = [] if self.HELP_PRE is not None: lines.extend(self.HELP_PRE) + any_modules = False + modules_without_desc: List[str] = [] for module_name in sorted(self.modules): + any_modules = True + module = self.modules[module_name] if module.DESCRIPTION is None: @@ -62,7 +91,10 @@ class ModuleBot(Bot): lines.append(line) if modules_without_desc: - lines.append(", ".join(modules_without_desc)) + lines.append("\t" + ", ".join(modules_without_desc)) + + if not any_modules: + lines.append("No modules loaded.") if self.HELP_POST is not None: lines.extend(self.HELP_POST) @@ -79,8 +111,7 @@ class ModuleBot(Bot): return module.HELP_SPECIFIC - # Overwriting the botrulez help function - async def cmd_help_specific(self, + async def cmd_modules_help(self, room: Room, message: LiveMessage, args: SpecificArgumentData @@ -100,6 +131,12 @@ class ModuleBot(Bot): # Sending along all kinds of events + async def on_connected(self, room: Room) -> None: + await super().on_connected(room) + + for module in self.modules.values(): + await module.on_connected(room) + async def on_snapshot(self, room: Room, messages: List[LiveMessage]) -> None: await super().on_snapshot(room, messages) @@ -141,6 +178,18 @@ class ModuleBot(Bot): for module in self.modules.values(): await module.on_edit(room, message) + async def on_login(self, room: Room, account_id: str) -> None: + await super().on_login(room, account_id) + + for module in self.modules.values(): + await module.on_login(room, account_id) + + async def on_logout(self, room: Room) -> None: + await super().on_logout(room) + + for module in self.modules.values(): + await module.on_logout(room) + async def on_pm(self, room: Room, from_id: str, @@ -158,3 +207,8 @@ class ModuleBot(Bot): for module in self.modules.values(): await module.on_disconnect(room, reason) + +ModuleBotConstructor = Callable[ + [configparser.ConfigParser, str, Dict[str, ModuleConstructor]], + Bot +]