diff --git a/test.py b/test.py index fd7f735..d749473 100644 --- a/test.py +++ b/test.py @@ -23,39 +23,44 @@ logger = logging.getLogger('yaboli') logger.setLevel(LEVEL) logger.addHandler(handler) -class TestBot(yaboli.Bot): - DEFAULT_NICK = "testbot" +class TestModule(yaboli.Module): + PING_REPLY = "ModulePong!" + DESCRIPTION = "ModuleDescription" + HELP_GENERAL = "ModuleGeneralHelp" + HELP_SPECIFIC = ["ModuleGeneralHelp"] - def __init__(self): - super().__init__() - self.register_botrulez() - self.register_general("test", self.cmd_test, args=False) - self.register_general("who", self.cmd_who, args=False) - self.register_general("err", self.cmd_err, args=False) +class EchoModule(yaboli.Module): + DEFAULT_NICK = "echo" + DESCRIPTION = "echoes back the input arguments" + HELP_GENERAL = "/me " + DESCRIPTION + HELP_SPECIFIC = [ + "!echo – output the arguments, each in its own line" + #"!fancyecho – same as !echo, but different parser" + ] + + def __init__(self, standalone: bool) -> None: + super().__init__(standalone) + + self.register_general("echo", self.cmd_echo) + #self.register_general("fancyecho", self.cmd_fancyecho) + + async def cmd_echo(self, room, message, args): + if args.has_args(): + lines = [repr(arg) for arg in args.basic()] + await message.reply("\n".join(lines)) + else: + await message.reply("No arguments") + +class TestBot(yaboli.ModuleBot): + DEFAULT_NICK = "testbot" async def started(self): await self.join("test") - async def on_send(self, room, message): - await self.process_commands(room, message, - aliases=["testalias", "aliastest"]) - - async def cmd_test(self, room, message, args): - await message.reply(f"You said {message.content!r}.") - msg1 = await room.send(f"{message.sender.atmention} said something.") - await msg1.reply("Yes, they really did.") - - async def cmd_who(self, room, message, args): - lines = [] - for user in await room.who(): - lines.append(repr(user.nick)) - await message.reply("\n".join(lines)) - - async def cmd_err(self, room, message, args): - await message.reply(str(1/0)) - async def main(): - tc = TestBot() - await tc.run() + tb = TestBot() + tb.register_module("test", TestModule(standalone=False)) + tb.register_module("echo", EchoModule(standalone=False)) + await tb.run() asyncio.run(main()) diff --git a/yaboli/__init__.py b/yaboli/__init__.py index 86a60ff..e749ce5 100644 --- a/yaboli/__init__.py +++ b/yaboli/__init__.py @@ -7,6 +7,7 @@ from .connection import * from .events import * from .exceptions import * from .message import * +from .module import * from .room import * from .session import * from .util import * @@ -19,6 +20,7 @@ __all__ += connection.__all__ __all__ += events.__all__ __all__ += exceptions.__all__ __all__ += message.__all__ +__all__ += module.__all__ __all__ += room.__all__ __all__ += session.__all__ __all__ += util.__all__ diff --git a/yaboli/module.py b/yaboli/module.py new file mode 100644 index 0000000..3fe1baf --- /dev/null +++ b/yaboli/module.py @@ -0,0 +1,160 @@ +import logging +from typing import Dict, List, Optional + +from .bot import Bot +from .command import * +from .message import LiveMessage +from .room import Room +from .session import LiveSession +from .util import * + +logger = logging.getLogger(__name__) + +__all__ = ["Module", "ModuleBot"] + +class Module(Bot): + DESCRIPTION: Optional[str] = None + + def __init__(self, standalone: bool) -> None: + super().__init__() + + self.standalone = standalone + +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." + ] + MODULE_HELP_LIMIT = 5 + + def __init__(self) -> None: + super().__init__() + + 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) + + def register_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 compile_module_overview(self) -> List[str]: + lines = [] + + if self.HELP_PRE is not None: + lines.extend(self.HELP_PRE) + + modules_without_desc: List[str] = [] + for module_name in sorted(self.modules): + module = self.modules[module_name] + + if module.DESCRIPTION is None: + modules_without_desc.append(module_name) + else: + line = f"\t{module_name} — {module.DESCRIPTION}" + lines.append(line) + + if modules_without_desc: + lines.append(", ".join(modules_without_desc)) + + if self.HELP_POST is not None: + lines.extend(self.HELP_POST) + + return lines + + def compile_module_help(self, module_name: str) -> List[str]: + module = self.modules.get(module_name) + if module is None: + return [f"Module {module_name!r} not found."] + + elif module.HELP_SPECIFIC is None: + return [f"Module {module_name!r} has no detailed help message."] + + return module.HELP_SPECIFIC + + # Overwriting the botrulez help function + async def cmd_help_specific(self, + room: Room, + message: LiveMessage, + args: SpecificArgumentData + ) -> None: + if args.has_args(): + if len(args.basic()) > self.MODULE_HELP_LIMIT: + limit = self.MODULE_HELP_LIMIT + text = f"A maximum of {limit} module{plural(limit)} is allowed." + await message.reply(text) + else: + for module_name in args.basic(): + help_lines = self.compile_module_help(module_name) + await message.reply(self.format_help(room, help_lines)) + else: + help_lines = self.compile_module_overview() + await message.reply(self.format_help(room, help_lines)) + + # Sending along all kinds of events + + async def on_snapshot(self, room: Room, messages: List[LiveMessage]) -> None: + await super().on_snapshot(room, messages) + + for module in self.modules.values(): + await module.on_snapshot(room, messages) + + async def on_send(self, room: Room, message: LiveMessage) -> None: + await super().on_send(room, message) + + for module in self.modules.values(): + await module.on_send(room, message) + + async def on_join(self, room: Room, user: LiveSession) -> None: + await super().on_join(room, user) + + for module in self.modules.values(): + await module.on_join(room, user) + + async def on_part(self, room: Room, user: LiveSession) -> None: + await super().on_part(room, user) + + for module in self.modules.values(): + await module.on_part(room, user) + + async def on_nick(self, + room: Room, + user: LiveSession, + from_nick: str, + to_nick: str + ) -> None: + await super().on_nick(room, user, from_nick, to_nick) + + for module in self.modules.values(): + await module.on_nick(room, user, from_nick, to_nick) + + async def on_edit(self, room: Room, message: LiveMessage) -> None: + await super().on_edit(room, message) + + for module in self.modules.values(): + await module.on_edit(room, message) + + async def on_pm(self, + room: Room, + from_id: str, + from_nick: str, + from_room: str, + pm_id: str + ) -> None: + await super().on_pm(room, from_id, from_nick, from_room, pm_id) + + for module in self.modules.values(): + await module.on_pm(room, from_id, from_nick, from_room, pm_id) + + async def on_disconnect(self, room: Room, reason: str) -> None: + await super().on_disconnect(room, reason) + + for module in self.modules.values(): + await module.on_disconnect(room, reason) diff --git a/yaboli/util.py b/yaboli/util.py index 1d2069b..5353ec1 100644 --- a/yaboli/util.py +++ b/yaboli/util.py @@ -1,6 +1,6 @@ import re -__all__ = ["mention", "atmention", "normalize", "similar"] +__all__ = ["mention", "atmention", "normalize", "similar", "plural"] # Name/nick related functions @@ -16,3 +16,15 @@ def normalize(nick: str) -> str: def similar(nick_a: str, nick_b: str) -> bool: return normalize(nick_a) == normalize(nick_b) + +# Other formatting + +def plural( + number: int, + if_plural: str = "s", + if_singular: str = "" + ) -> str: + if number in [1, -1]: + return if_singular + else: + return if_plural