from functools import partial from typing import Any, Callable, Optional from discord.ext.commands import Cog from discord.ext import commands as cmds from twitchio.ext.commands import Command, Bot from twitchio.ext.commands.meta import CogEvent class LionCog(Cog): # A set of other cogs that this cog depends on depends_on: set['LionCog'] = set() _placeholder_groups_: set[str] _twitch_cmds_: dict[str, Command] _twitch_events_: dict[str, CogEvent] _twitch_events_loaded_: set[Callable] def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) cls._placeholder_groups_ = set() cls._twitch_cmds_ = {} cls._twitch_events_ = {} for base in reversed(cls.__mro__): for elem, value in base.__dict__.items(): if isinstance(value, cmds.HybridGroup) and hasattr(value, '_placeholder_group_'): cls._placeholder_groups_.add(value.name) elif isinstance(value, Command): cls._twitch_cmds_[value.name] = value elif isinstance(value, CogEvent): cls._twitch_events_[value.name] = value def __new__(cls, *args: Any, **kwargs: Any): # Patch to ensure no placeholder groups are in the command list self = super().__new__(cls) self.__cog_commands__ = [ command for command in self.__cog_commands__ if command.name not in cls._placeholder_groups_ ] return self async def _inject(self, bot, *args, **kwargs): if self.depends_on: not_found = {cogname for cogname in self.depends_on if not bot.get_cog(cogname)} raise ValueError(f"Could not load cog '{self.__class__.__name__}', dependencies missing: {not_found}") return await super()._inject(bot, *args, *kwargs) def _load_twitch_methods(self, bot: Bot): for name, command in self._twitch_cmds_.items(): command._instance = self command.cog = self bot.add_command(command) for name, event in self._twitch_events_.items(): callback = partial(event, self) self._twitch_events_loaded_.add(callback) bot.add_event(callback=callback, name=name) def _unload_twitch_methods(self, bot: Bot): for name in self._twitch_cmds_: bot.remove_command(name) for callback in self._twitch_events_loaded_: bot.remove_event(callback=callback) self._twitch_events_loaded_.clear() @classmethod def twitch_event(cls, event: Optional[str] = None): def decorator(func) -> CogEvent: event_name = event or func.__name__ return CogEvent(name=event_name, func=func, module=cls.__module__) return decorator @classmethod def placeholder_group(cls, group: cmds.HybridGroup): group._placeholder_group_ = True return group def crossload_group(self, placeholder_group: cmds.HybridGroup, target_group: cmds.HybridGroup): """ Crossload a placeholder group's commands into the target group """ if not isinstance(placeholder_group, cmds.HybridGroup) or not isinstance(target_group, cmds.HybridGroup): raise ValueError("Placeholder and target groups my be HypridGroups.") if placeholder_group.name not in self._placeholder_groups_: raise ValueError("Placeholder group was not registered! Stopping to avoid duplicates.") if target_group.app_command is None: raise ValueError("Target group has no app_command to crossload into.") for command in placeholder_group.commands: placeholder_group.remove_command(command.name) target_group.remove_command(command.name) acmd = command.app_command._copy_with(parent=target_group.app_command, binding=self) command.app_command = acmd target_group.add_command(command)