# =====IMPORTS===== # Future imports from __future__ import annotations # Built-in imports import asyncio # Own imports from .exceptions import InvalidCommand from .meta import ModuleMeta from .decorators import RegexCommand # Typing imports from typing import TYPE_CHECKING if TYPE_CHECKING: # Built-in imports from typing import List, Dict # Third-party imports from discord.abc import User, Messageable # Own imports from suzybot.frank import Frank class Module(ModuleMeta): """ Base class for modules; all custom modules should inherit from this. """ PREFIX = [] """ Prefix to activate this module. """ NAME = "" """ The name is used in various places, such as the config file and the help function. """ HELP = "" """ Short description of the module to use in the help function. """ def __init__(self, client: Frank, config: Dict = None): """ Args: client: client using this module; used to communicate. config: dict containing the config for this module (Frank client reads this from the config file). """ super().__init__() self._client = client self._config = config self._tasks = [] def pre_start(self): """ Overwrite this function to run code (e.g. add variables...) before starting the daemons. """ pass async def _start(self): """Start up defined daemons for this module.""" self.pre_start() for daemon in self.daemons: # pylint: disable=no-member task = asyncio.create_task(daemon()) self._tasks.append(task) async def stop(self): """ Stop all tasks for this module. """ for task in self._tasks: task.cancel() async def __call__( self, cmd: List[str], author: User, channel: Messageable, mid: int ): """ Execute the command, if found. Args: cmd: list of command arguments; if empty, default command is used author: author of message channel: channel the message was sent in mid: message id """ if cmd: func = next( (func for func in self.commands if func.match(cmd[0])), None ) # Throw error if no function is found if not func: raise InvalidCommand(f"Unknown command: {cmd}") # A RegexCommand can use the prefix, as it's not a fixed string if isinstance(func, RegexCommand): await func( prefix=cmd[0], cmd=cmd[1:], author=author, channel=channel, mid=mid, ) else: await func( cmd=cmd[1:], author=author, channel=channel, mid=mid ) elif self.default: await self.default(author=author, channel=channel, mid=mid) @classmethod def match(cls, prefix: str) -> bool: """ Checks wether the given prefix matches the module. Args: prefix: prefix to check """ # Always return False if there's no PREFIX defined if not cls.PREFIX: return False if isinstance(cls.PREFIX, list): return prefix in cls.PREFIX else: return prefix == cls.PREFIX