diff options
| -rw-r--r-- | bot/exts/holidays/halloween/spookyreact.py | 71 | ||||
| -rw-r--r-- | bot/exts/holidays/holidayreact.py | 140 | 
2 files changed, 140 insertions, 71 deletions
diff --git a/bot/exts/holidays/halloween/spookyreact.py b/bot/exts/holidays/halloween/spookyreact.py deleted file mode 100644 index 99a84c4f..00000000 --- a/bot/exts/holidays/halloween/spookyreact.py +++ /dev/null @@ -1,71 +0,0 @@ -import logging -import re - -import discord -from discord.ext.commands import Cog - -from bot.bot import Bot -from bot.constants import Month -from bot.utils.decorators import in_month - -log = logging.getLogger(__name__) - -SPOOKY_TRIGGERS = { -    "spooky": (r"\bspo{2,}[k|p][i|y](er|est)?\b", "\U0001F47B"), -    "skeleton": (r"\bskeleton\b", "\U0001F480"), -    "doot": (r"\bdo{2,}t\b", "\U0001F480"), -    "pumpkin": (r"\bpumpkin\b", "\U0001F383"), -    "halloween": (r"\bhalloween\b", "\U0001F383"), -    "jack-o-lantern": (r"\bjack-o-lantern\b", "\U0001F383"), -    "danger": (r"\bdanger\b", "\U00002620"), -    "bat": (r"\bbat((wo)?m[ae]n|persons?|people|s)?\b", "\U0001F987"), -} - - -class SpookyReact(Cog): -    """A cog that makes the bot react to message triggers.""" - -    def __init__(self, bot: Bot): -        self.bot = bot - -    @in_month(Month.OCTOBER) -    @Cog.listener() -    async def on_message(self, message: discord.Message) -> None: -        """Triggered when the bot sees a message in October.""" -        for name, trigger in SPOOKY_TRIGGERS.items(): -            trigger_test = re.search(trigger[0], message.content.lower()) -            if trigger_test: -                # Check message for bot replies and/or command invocations -                # Short circuit if they're found, logging is handled in _short_circuit_check -                if await self._short_circuit_check(message): -                    return -                else: -                    await message.add_reaction(trigger[1]) -                    log.info(f"Added {name!r} reaction to message ID: {message.id}") - -    async def _short_circuit_check(self, message: discord.Message) -> bool: -        """ -        Short-circuit helper check. - -        Return True if: -          * author is a bot -          * prefix is not None -        """ -        # Check if message author is a bot -        if message.author.bot: -            log.debug(f"Ignoring reactions on bot message. Message ID: {message.id}") -            return True - -        # Check for command invocation -        # Because on_message doesn't give a full Context object, generate one first -        ctx = await self.bot.get_context(message) -        if ctx.prefix: -            log.debug(f"Ignoring reactions on command invocation. Message ID: {message.id}") -            return True - -        return False - - -async def setup(bot: Bot) -> None: -    """Load the Spooky Reaction Cog.""" -    await bot.add_cog(SpookyReact(bot)) diff --git a/bot/exts/holidays/holidayreact.py b/bot/exts/holidays/holidayreact.py new file mode 100644 index 00000000..2a1347e5 --- /dev/null +++ b/bot/exts/holidays/holidayreact.py @@ -0,0 +1,140 @@ +import logging +import random +import re +from typing import NamedTuple + +import discord +from discord.ext.commands import Cog + +from bot.bot import Bot +from bot.constants import Month +from bot.utils import resolve_current_month +from bot.utils.decorators import in_month + +log = logging.getLogger(__name__) + + +class Trigger(NamedTuple): +    """Representation of regex trigger and corresponding reactions.""" + +    regex: str +    reaction: list[str] + + +class Holiday(NamedTuple): +    """Representation of reaction holidays.""" + +    months: list[Month] +    triggers: dict[str, Trigger] + + +Valentines = Holiday([Month.FEBRUARY], { +    "heart": Trigger(r"\b(love|heart)\b", ["\u2764\uFE0F"]), +    } +) +Easter = Holiday([Month.APRIL], { +    "bunny": Trigger(r"\b(easter|bunny|rabbit)\b", ["\U0001F430", "\U0001F407"]), +    "egg": Trigger(r"\begg\b", ["\U0001F95A"]), +    } +) +EarthDay = Holiday([Month.FEBRUARY], { +    "earth": Trigger(r"\b(earth|planet)\b", ["\U0001F30E", "\U0001F30D", "\U0001F30F"]), +    } +) +Pride = Holiday([Month.JUNE], { +    "pride": Trigger(r"\bpride\b", ["\U0001F3F3\uFE0F\u200D\U0001F308"]), +    } +) +Halloween = Holiday([Month.OCTOBER], { +    "bat": Trigger(r"\bbat((wo)?m[ae]n|persons?|people|s)?\b", ["\U0001F987"]), +    "danger": Trigger(r"\bdanger\b", ["\U00002620"]), +    "doot": Trigger(r"\bdo{2,}t\b", ["\U0001F480"]), +    "halloween": Trigger(r"\bhalloween\b", ["\U0001F383"]), +    "jack-o-lantern": Trigger(r"\bjack-o-lantern\b", ["\U0001F383"]), +    "pumpkin": Trigger(r"\bpumpkin\b", ["\U0001F383"]), +    "skeleton": Trigger(r"\bskeleton\b", ["\U0001F480"]), +    "spooky": Trigger(r"\bspo{2,}[k|p][i|y](er|est)?\b", ["\U0001F47B"]), +    } +) +Hanukkah = Holiday([Month.NOVEMBER, Month.DECEMBER], { +    "menorah": Trigger(r"\b(c?hanukkah|menorah)\b", ["\U0001F54E"]), +    } +) +Christmas = Holiday([Month.DECEMBER], { +    "christmas tree": Trigger(r"\b((christ|x)mas|tree)\b", ["\U0001F384"]), +    "reindeer": Trigger(r"\b(reindeer|caribou|buck|stag)\b", ["\U0001F98C"]), +    "santa": Trigger(r"\bsanta\b", ["\U0001F385"]), +    "snowflake": Trigger(r"\b(snow ?)?flake(?! ?8)\b", ["\u2744\uFE0F"]), +    "snowman": Trigger(r"\bsnow(man|angel)\b", ["\u2603\uFE0F", "\u26C4"]), +    } +) +HOLIDAYS_TO_REACT = [ +    Valentines, Easter, EarthDay, Pride, Halloween, Hanukkah, Christmas +] +# Type (or order) doesn't matter here - set is for de-duplication +MONTHS_TO_REACT = set( +    month for holiday in HOLIDAYS_TO_REACT for month in holiday.months +) + + +class HolidayReact(Cog): +    """A cog that makes the bot react to message triggers.""" + +    def __init__(self, bot: Bot): +        self.bot = bot + +    @in_month(*MONTHS_TO_REACT) +    @Cog.listener() +    async def on_message(self, message: discord.Message) -> None: +        """Triggered when the bot sees a message in a holiday month.""" +        # Check message for bot replies and/or command invocations +        # Short circuit if they're found, logging is handled in _short_circuit_check +        if await self._short_circuit_check(message): +            return + +        for holiday in HOLIDAYS_TO_REACT: +            await self._check_message(message, holiday) + +    async def _check_message(self, message: discord.Message, holiday: Holiday) -> None: +        """ +        Check if message is reactable. + +        React to message if: +          * month is valid +          * message contains reaction regex triggers +        """ +        if resolve_current_month() not in holiday.months: +            return + +        for name, trigger in holiday.triggers.items(): +            trigger_test = re.search(trigger.regex, message.content, flags=re.IGNORECASE) +            if trigger_test: +                await message.add_reaction(random.choice(trigger.reaction)) +                log.info(f"Added {name!r} reaction to message ID: {message.id}") + +    async def _short_circuit_check(self, message: discord.Message) -> bool: +        """ +        Short-circuit helper check. + +        Return True if: +          * author is a bot +          * prefix is not None +        """ +        # Check if message author is a bot +        if message.author.bot: +            log.debug(f"Ignoring reactions on bot message. Message ID: {message.id}") +            return True + +        # Check for command invocation +        # Because on_message doesn't give a full Context object, generate one first +        ctx = await self.bot.get_context(message) +        if ctx.prefix: +            log.debug(f"Ignoring reactions on command invocation. Message ID: {message.id}") +            return True + +        return False + + +async def setup(bot: Bot) -> None: +    """Load the Holiday Reaction Cog.""" +    await bot.add_cog(HolidayReact(bot))  |