From bd4adec3e07e4807d031588ca70b51aeb200be8d Mon Sep 17 00:00:00 2001 From: Thurisatic <99002593+thurisatic@users.noreply.github.com> Date: Sun, 15 Jan 2023 21:24:34 -0500 Subject: De-Halloween-ify reaction Cog: move file, rename internals Additional pending reactions mean putting the reaction Cog in the halloween directory no longer makes sense. Approriate renamings follow. --- bot/exts/holidays/holidayreact.py | 71 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 bot/exts/holidays/holidayreact.py (limited to 'bot/exts/holidays/holidayreact.py') diff --git a/bot/exts/holidays/holidayreact.py b/bot/exts/holidays/holidayreact.py new file mode 100644 index 00000000..f0905f03 --- /dev/null +++ b/bot/exts/holidays/holidayreact.py @@ -0,0 +1,71 @@ +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 HolidayReact(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 a holiday month.""" + 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 Holiday Reaction Cog.""" + await bot.add_cog(HolidayReact(bot)) -- cgit v1.2.3 From 8e8750601fe5b41c773cbc51838bec9b5638074c Mon Sep 17 00:00:00 2001 From: Thurisatic <99002593+thurisatic@users.noreply.github.com> Date: Mon, 16 Jan 2023 01:21:06 -0500 Subject: Add non-Christmas holiday reaction capabilities Triggers for reactions are moved from a dict to to a 2-deep NamedTuple. This is cleaner for multiple reactions, and allows for additional parameters, such as months. Message case checking is altered from `.lower` to `re.IGNORECASE`. Not only does the regex flag short circuit, it doesn't modify the string in question --- bot/exts/holidays/holidayreact.py | 107 +++++++++++++++++++++++++++++++------- 1 file changed, 89 insertions(+), 18 deletions(-) (limited to 'bot/exts/holidays/holidayreact.py') diff --git a/bot/exts/holidays/holidayreact.py b/bot/exts/holidays/holidayreact.py index f0905f03..535e22ec 100644 --- a/bot/exts/holidays/holidayreact.py +++ b/bot/exts/holidays/holidayreact.py @@ -1,25 +1,80 @@ 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__) -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 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"\blove|heart\b", ["\u2764\uFE0F"]), + } +) +Easter = Holiday([Month.APRIL], { + "bunny": Trigger(r"\beaster|bunny|rabbit\b", ["\U0001F430", "\U0001F407"]), + "egg": Trigger(r"\begg\b", ["\U0001F95A"]), + } +) +EarthDay = Holiday([Month.FEBRUARY], { + "earth": Trigger(r"\bearth|planet\b", ["\U0001F30E", "\U0001F30D", "\U0001F30F",]), + } +) +Pride = Holiday([Month.JUNE], { + "pride": Trigger(r"\bpride\b", ["\U0001F3F3\uFE0F\u200D\U0001F308"]), + } +) +Halloween = Holiday([Month.OCTOBER], { + "spooky": Trigger(r"\bspo{2,}[k|p][i|y](er|est)?\b", ["\U0001F47B"]), + "skeleton": Trigger(r"\bskeleton\b", ["\U0001F480"]), + "doot": Trigger(r"\bdo{2,}t\b", ["\U0001F480"]), + "pumpkin": Trigger(r"\bpumpkin\b", ["\U0001F383"]), + "halloween": Trigger(r"\bhalloween\b", ["\U0001F383"]), + "jack-o-lantern": Trigger(r"\bjack-o-lantern\b", ["\U0001F383"]), + "danger": Trigger(r"\bdanger\b", ["\U00002620"]), + "bat": Trigger(r"\bbat((wo)?m[ae]n|persons?|people|s)?\b", ["\U0001F987"]), + } +) +Hanukkah = Holiday([Month.NOVEMBER, Month.DECEMBER], { + "menorah": Trigger(r"\bc?haukkah|menorah\b", ["\U0001F54E"]), + } +) +Christmas = Holiday([Month.DECEMBER], { + "christmas tree": Trigger(r"\b(christ|x)mas|tree\b", ["\U0001F384"]), + "snowflake": Trigger(r"\b(snow ?)?flake(?! ?8)\b", ["\u2744\uFE0F"]), + "santa": Trigger(r"\bsanta\b", ["\U0001F385"]), + "snowman": Trigger(r"\bsnow(man|angel)\b", ["\u2603\uFE0F", "\u26C4"]), + "reindeer": Trigger(r"\breindeer|caribou|buck|stag\b", ["\U0001F98C"]), + } +) +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): @@ -28,20 +83,36 @@ class HolidayReact(Cog): def __init__(self, bot: Bot): self.bot = bot - @in_month(Month.OCTOBER) + @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.""" - for name, trigger in SPOOKY_TRIGGERS.items(): - trigger_test = re.search(trigger[0], message.content.lower()) + # 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: + """ + Checks if message is reactable. + + First checks if month is valid (else return). Then attempts to + match regex triggers to message. Those that succeed result in + reactions applied to the message. + """ + 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: - # 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}") + 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: """ -- cgit v1.2.3 From b1470e9a88232d7b769f6c70f969c8e3e6723b0d Mon Sep 17 00:00:00 2001 From: Thurisatic <99002593+thurisatic@users.noreply.github.com> Date: Tue, 17 Jan 2023 15:59:40 -0500 Subject: Perform small fixes, incl requested: sorting, typo, redundancies, mood - Fixes misspelling of hanukkah regex - Move `_check_message` docstring to imperative mood - Remove redundant `_short_circuit_check` from `_check_message` - Remove redundant comma from `EarthDay`'s reaction list - Sort triggers for Halloween and Christmas --- bot/exts/holidays/holidayreact.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) (limited to 'bot/exts/holidays/holidayreact.py') diff --git a/bot/exts/holidays/holidayreact.py b/bot/exts/holidays/holidayreact.py index 535e22ec..9a8e0526 100644 --- a/bot/exts/holidays/holidayreact.py +++ b/bot/exts/holidays/holidayreact.py @@ -38,7 +38,7 @@ Easter = Holiday([Month.APRIL], { } ) EarthDay = Holiday([Month.FEBRUARY], { - "earth": Trigger(r"\bearth|planet\b", ["\U0001F30E", "\U0001F30D", "\U0001F30F",]), + "earth": Trigger(r"\bearth|planet\b", ["\U0001F30E", "\U0001F30D", "\U0001F30F"]), } ) Pride = Holiday([Month.JUNE], { @@ -46,26 +46,26 @@ Pride = Holiday([Month.JUNE], { } ) Halloween = Holiday([Month.OCTOBER], { - "spooky": Trigger(r"\bspo{2,}[k|p][i|y](er|est)?\b", ["\U0001F47B"]), - "skeleton": Trigger(r"\bskeleton\b", ["\U0001F480"]), + "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"]), - "pumpkin": Trigger(r"\bpumpkin\b", ["\U0001F383"]), "halloween": Trigger(r"\bhalloween\b", ["\U0001F383"]), "jack-o-lantern": Trigger(r"\bjack-o-lantern\b", ["\U0001F383"]), - "danger": Trigger(r"\bdanger\b", ["\U00002620"]), - "bat": Trigger(r"\bbat((wo)?m[ae]n|persons?|people|s)?\b", ["\U0001F987"]), + "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"\bc?haukkah|menorah\b", ["\U0001F54E"]), + "menorah": Trigger(r"\bc?hanukkah|menorah\b", ["\U0001F54E"]), } ) Christmas = Holiday([Month.DECEMBER], { "christmas tree": Trigger(r"\b(christ|x)mas|tree\b", ["\U0001F384"]), - "snowflake": Trigger(r"\b(snow ?)?flake(?! ?8)\b", ["\u2744\uFE0F"]), + "reindeer": Trigger(r"\breindeer|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"]), - "reindeer": Trigger(r"\breindeer|caribou|buck|stag\b", ["\U0001F98C"]), } ) HOLIDAYS_TO_REACT = [ @@ -97,11 +97,11 @@ class HolidayReact(Cog): async def _check_message(self, message: discord.Message, holiday: Holiday) -> None: """ - Checks if message is reactable. + Check if message is reactable. - First checks if month is valid (else return). Then attempts to - match regex triggers to message. Those that succeed result in - reactions applied to the message. + React to message if: + * month is valid + * message contains reaction regex triggers """ if resolve_current_month() not in holiday.months: return @@ -109,8 +109,6 @@ class HolidayReact(Cog): for name, trigger in holiday.triggers.items(): trigger_test = re.search(trigger.regex, message.content, flags=re.IGNORECASE) if trigger_test: - if await self._short_circuit_check(message): - return await message.add_reaction(random.choice(trigger.reaction)) log.info(f"Added {name!r} reaction to message ID: {message.id}") -- cgit v1.2.3 From 28a8c673adac0f166961c1f8ef253d6f1e14eda8 Mon Sep 17 00:00:00 2001 From: Thurisatic <99002593+thurisatic@users.noreply.github.com> Date: Wed, 18 Jan 2023 19:25:48 -0500 Subject: Isolate regex options to capture groups --- bot/exts/holidays/holidayreact.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'bot/exts/holidays/holidayreact.py') diff --git a/bot/exts/holidays/holidayreact.py b/bot/exts/holidays/holidayreact.py index 9a8e0526..2a1347e5 100644 --- a/bot/exts/holidays/holidayreact.py +++ b/bot/exts/holidays/holidayreact.py @@ -29,16 +29,16 @@ class Holiday(NamedTuple): Valentines = Holiday([Month.FEBRUARY], { - "heart": Trigger(r"\blove|heart\b", ["\u2764\uFE0F"]), + "heart": Trigger(r"\b(love|heart)\b", ["\u2764\uFE0F"]), } ) Easter = Holiday([Month.APRIL], { - "bunny": Trigger(r"\beaster|bunny|rabbit\b", ["\U0001F430", "\U0001F407"]), + "bunny": Trigger(r"\b(easter|bunny|rabbit)\b", ["\U0001F430", "\U0001F407"]), "egg": Trigger(r"\begg\b", ["\U0001F95A"]), } ) EarthDay = Holiday([Month.FEBRUARY], { - "earth": Trigger(r"\bearth|planet\b", ["\U0001F30E", "\U0001F30D", "\U0001F30F"]), + "earth": Trigger(r"\b(earth|planet)\b", ["\U0001F30E", "\U0001F30D", "\U0001F30F"]), } ) Pride = Holiday([Month.JUNE], { @@ -57,12 +57,12 @@ Halloween = Holiday([Month.OCTOBER], { } ) Hanukkah = Holiday([Month.NOVEMBER, Month.DECEMBER], { - "menorah": Trigger(r"\bc?hanukkah|menorah\b", ["\U0001F54E"]), + "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"\breindeer|caribou|buck|stag\b", ["\U0001F98C"]), + "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"]), -- cgit v1.2.3