From 6c0c43bf1aee2217777556aa3025c47f12b9c5c4 Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Thu, 15 May 2025 14:36:39 +0100 Subject: [PATCH 01/10] do some stuff --- cogs/auto_slow_mode.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 cogs/auto_slow_mode.py diff --git a/cogs/auto_slow_mode.py b/cogs/auto_slow_mode.py new file mode 100644 index 00000000..9384421a --- /dev/null +++ b/cogs/auto_slow_mode.py @@ -0,0 +1,42 @@ +"""Module to handle automatic slow mode for Discord channels.""" + +import logging +from typing import TYPE_CHECKING + +import discord + +from utils import TeXBotApplicationContext, TeXBotBaseCog + +if TYPE_CHECKING: + from collections.abc import Sequence + from logging import Logger + from typing import Final + + +__all__: "Sequence[str]" = () + +logger: "Final[Logger]" = logging.getLogger("TeX-Bot") + + +class AutomaticSlowModeBaseCog(TeXBotBaseCog): + """Base class for automatic slow mode functionality.""" + + async def calculate_message_rate(ctx: TeXBotApplicationContext, channel: discord.Channel) -> None: + """Calculate the message rate for a given channel.""" + raise NotImplementedError + + +class AutomaticSlowModeCog(AutomaticSlowModeBaseCog): + """Cog to handle automatic slow mode for Discord channels.""" + + @discord.slash_command( + name="auto_slow_mode", + description="Enable or disable automatic slow mode for a channel.", + ) + async def auto_slow_mode( + self, + ctx: TeXBotApplicationContext, + channel: discord.TextChannel = None, + ) -> None: + """Enable or disable automatic slow mode for a channel.""" + raise NotImplementedError From ca1c6c4298ce7bc81583b3952c404232c1cc0c85 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sun, 18 May 2025 19:31:00 +0000 Subject: [PATCH 02/10] [pre-commit.ci lite] apply automatic fixes --- cogs/auto_slow_mode.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cogs/auto_slow_mode.py b/cogs/auto_slow_mode.py index 9384421a..9d97bb4f 100644 --- a/cogs/auto_slow_mode.py +++ b/cogs/auto_slow_mode.py @@ -21,7 +21,9 @@ class AutomaticSlowModeBaseCog(TeXBotBaseCog): """Base class for automatic slow mode functionality.""" - async def calculate_message_rate(ctx: TeXBotApplicationContext, channel: discord.Channel) -> None: + async def calculate_message_rate( + ctx: TeXBotApplicationContext, channel: discord.Channel + ) -> None: """Calculate the message rate for a given channel.""" raise NotImplementedError From a29067dd205ed5ac95ef2de4f9a97e41d215ae96 Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Sun, 18 May 2025 21:01:10 +0100 Subject: [PATCH 03/10] Fix some stuff --- cogs/__init__.py | 3 +++ cogs/auto_slow_mode.py | 33 ++++++++++++++++++++++----------- config.py | 15 +++++++++++++++ 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/cogs/__init__.py b/cogs/__init__.py index e97071fa..ead2fc2d 100644 --- a/cogs/__init__.py +++ b/cogs/__init__.py @@ -14,6 +14,7 @@ CommitteeHandoverCommandCog, ) from .archive import ArchiveCommandCog +from .auto_slow_mode import AutomaticSlowModeCommandCog from .command_error import CommandErrorCog from .committee_actions_tracking import ( CommitteeActionsTrackingContextCommandsCog, @@ -52,6 +53,7 @@ "AnnualRolesResetCommandCog", "AnnualYearChannelsIncrementCommandCog", "ArchiveCommandCog", + "AutomaticSlowModeCommandCog", "ClearRemindersBacklogTaskCog", "CommandErrorCog", "CommitteeActionsTrackingContextCommandsCog", @@ -92,6 +94,7 @@ def setup(bot: "TeXBot") -> None: AnnualRolesResetCommandCog, AnnualYearChannelsIncrementCommandCog, ArchiveCommandCog, + AutomaticSlowModeCommandCog, ClearRemindersBacklogTaskCog, CommandErrorCog, CommitteeActionsTrackingSlashCommandsCog, diff --git a/cogs/auto_slow_mode.py b/cogs/auto_slow_mode.py index 9d97bb4f..7b25da27 100644 --- a/cogs/auto_slow_mode.py +++ b/cogs/auto_slow_mode.py @@ -5,15 +5,18 @@ import discord -from utils import TeXBotApplicationContext, TeXBotBaseCog +from config import settings +from utils import CommandChecks, TeXBotBaseCog if TYPE_CHECKING: from collections.abc import Sequence from logging import Logger from typing import Final + from utils import TeXBotApplicationContext -__all__: "Sequence[str]" = () + +__all__: "Sequence[str]" = ("AutomaticSlowModeCommandCog",) logger: "Final[Logger]" = logging.getLogger("TeX-Bot") @@ -22,23 +25,31 @@ class AutomaticSlowModeBaseCog(TeXBotBaseCog): """Base class for automatic slow mode functionality.""" async def calculate_message_rate( - ctx: TeXBotApplicationContext, channel: discord.Channel + self, ctx: "TeXBotApplicationContext", channel: discord.TextChannel ) -> None: """Calculate the message rate for a given channel.""" raise NotImplementedError -class AutomaticSlowModeCog(AutomaticSlowModeBaseCog): +class AutomaticSlowModeCommandCog(AutomaticSlowModeBaseCog): """Cog to handle automatic slow mode for Discord channels.""" - @discord.slash_command( - name="auto_slow_mode", - description="Enable or disable automatic slow mode for a channel.", + @discord.slash_command( # type: ignore[misc, no-untyped-call] + name="toggle-auto-slow-mode", + description="Enable or disable automatic slow mode.", ) - async def auto_slow_mode( + @CommandChecks.check_interaction_user_has_committee_role + @CommandChecks.check_interaction_user_in_main_guild + async def toggle_auto_slow_mode( # type: ignore[misc, no-untyped-call] self, - ctx: TeXBotApplicationContext, - channel: discord.TextChannel = None, + ctx: "TeXBotApplicationContext", ) -> None: """Enable or disable automatic slow mode for a channel.""" - raise NotImplementedError + # NOTE: This should be replaced when the settings are refactored in the draft PR. + if settings["AUTO_SLOW_MODE"]: + settings["AUTO_SLOW_MODE"] = False + await ctx.send("Automatic slow mode is now disabled.") + return + + settings["AUTO_SLOW_MODE"] = True + await ctx.send("Automatic slow mode is now enabled.") diff --git a/config.py b/config.py index 985063cf..b4f31709 100644 --- a/config.py +++ b/config.py @@ -724,6 +724,20 @@ def _setup_auto_add_committee_to_threads(cls) -> None: raw_auto_add_committee_to_threads in TRUE_VALUES ) + @classmethod + def _setup_auto_slow_mode(cls) -> None: + raw_auto_slow_mode: str = str( + os.getenv("AUTO_SLOW_MODE", "True"), + ).lower() + + if raw_auto_slow_mode not in TRUE_VALUES | FALSE_VALUES: + INVALID_AUTO_SLOW_MODE_MESSAGE: Final[str] = ( + "AUTO_SLOW_MODE must be a boolean value." + ) + raise ImproperlyConfiguredError(INVALID_AUTO_SLOW_MODE_MESSAGE) + + cls._settings["AUTO_SLOW_MODE"] = raw_auto_slow_mode in TRUE_VALUES + @classmethod def _setup_env_variables(cls) -> None: """ @@ -765,6 +779,7 @@ def _setup_env_variables(cls) -> None: cls._setup_moderation_document_url() cls._setup_strike_performed_manually_warning_location() cls._setup_auto_add_committee_to_threads() + cls._setup_auto_slow_mode() except ImproperlyConfiguredError as improper_config_error: webhook_config_logger.error(improper_config_error.message) # noqa: TRY400 raise improper_config_error from improper_config_error From 2c18202faf915d4663ae50a20d44b991bf282cc4 Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Sun, 18 May 2025 21:05:03 +0100 Subject: [PATCH 04/10] fix mypy error --- cogs/auto_slow_mode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cogs/auto_slow_mode.py b/cogs/auto_slow_mode.py index 7b25da27..69839502 100644 --- a/cogs/auto_slow_mode.py +++ b/cogs/auto_slow_mode.py @@ -40,7 +40,7 @@ class AutomaticSlowModeCommandCog(AutomaticSlowModeBaseCog): ) @CommandChecks.check_interaction_user_has_committee_role @CommandChecks.check_interaction_user_in_main_guild - async def toggle_auto_slow_mode( # type: ignore[misc, no-untyped-call] + async def toggle_auto_slow_mode( # type: ignore[misc] self, ctx: "TeXBotApplicationContext", ) -> None: From 867e775ef57501db5d59fc0a6894d95af31b17b6 Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Sun, 18 May 2025 22:33:56 +0100 Subject: [PATCH 05/10] Implement the task --- cogs/auto_slow_mode.py | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/cogs/auto_slow_mode.py b/cogs/auto_slow_mode.py index 69839502..c9c96519 100644 --- a/cogs/auto_slow_mode.py +++ b/cogs/auto_slow_mode.py @@ -1,36 +1,66 @@ """Module to handle automatic slow mode for Discord channels.""" import logging -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override import discord +from discord.ext import tasks from config import settings from utils import CommandChecks, TeXBotBaseCog +from utils.error_capture_decorators import capture_guild_does_not_exist_error if TYPE_CHECKING: from collections.abc import Sequence from logging import Logger from typing import Final - from utils import TeXBotApplicationContext + from utils import TeXBot, TeXBotApplicationContext __all__: "Sequence[str]" = ("AutomaticSlowModeCommandCog",) + logger: "Final[Logger]" = logging.getLogger("TeX-Bot") class AutomaticSlowModeBaseCog(TeXBotBaseCog): """Base class for automatic slow mode functionality.""" - async def calculate_message_rate( - self, ctx: "TeXBotApplicationContext", channel: discord.TextChannel - ) -> None: + async def calculate_message_rate(self, channel: discord.TextChannel) -> int: """Calculate the message rate for a given channel.""" raise NotImplementedError +class AutomaticSlowModeTaskCog(AutomaticSlowModeBaseCog): + """Task to handle automatic slow mode for Discord channels.""" + + @override + def __init__(self, bot: "TeXBot") -> None: + """Start all task managers when this cog is initialised.""" + if settings["AUTO_SLOW_MODE"]: + _ = self.auto_slow_mode_task.start() + + super().__init__(bot) + + @override + def cog_unload(self) -> None: + """ + Unload-hook that ends all running tasks whenever the cog is unloaded. + + This may be run dynamically or when the bot closes. + """ + self.auto_slow_mode_task.cancel() + + @tasks.loop(seconds=60) + @capture_guild_does_not_exist_error + async def auto_slow_mode_task(self) -> None: + """Task to automatically adjust slow mode in channels.""" + for channel in self.bot.get_all_channels(): + if isinstance(channel, discord.TextChannel): + await self.calculate_message_rate(channel) + + class AutomaticSlowModeCommandCog(AutomaticSlowModeBaseCog): """Cog to handle automatic slow mode for Discord channels.""" From 688627ac84547a3fd8b4415f10b587b298fc7b12 Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Fri, 23 May 2025 07:54:44 +0100 Subject: [PATCH 06/10] Implement message rate method --- cogs/__init__.py | 4 +++- cogs/auto_slow_mode.py | 26 +++++++++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/cogs/__init__.py b/cogs/__init__.py index ead2fc2d..e56fde06 100644 --- a/cogs/__init__.py +++ b/cogs/__init__.py @@ -14,7 +14,7 @@ CommitteeHandoverCommandCog, ) from .archive import ArchiveCommandCog -from .auto_slow_mode import AutomaticSlowModeCommandCog +from .auto_slow_mode import AutomaticSlowModeCommandCog, AutomaticSlowModeTaskCog from .command_error import CommandErrorCog from .committee_actions_tracking import ( CommitteeActionsTrackingContextCommandsCog, @@ -54,6 +54,7 @@ "AnnualYearChannelsIncrementCommandCog", "ArchiveCommandCog", "AutomaticSlowModeCommandCog", + "AutomaticSlowModeTaskCog", "ClearRemindersBacklogTaskCog", "CommandErrorCog", "CommitteeActionsTrackingContextCommandsCog", @@ -95,6 +96,7 @@ def setup(bot: "TeXBot") -> None: AnnualYearChannelsIncrementCommandCog, ArchiveCommandCog, AutomaticSlowModeCommandCog, + AutomaticSlowModeTaskCog, ClearRemindersBacklogTaskCog, CommandErrorCog, CommitteeActionsTrackingSlashCommandsCog, diff --git a/cogs/auto_slow_mode.py b/cogs/auto_slow_mode.py index c9c96519..408b5243 100644 --- a/cogs/auto_slow_mode.py +++ b/cogs/auto_slow_mode.py @@ -18,7 +18,7 @@ from utils import TeXBot, TeXBotApplicationContext -__all__: "Sequence[str]" = ("AutomaticSlowModeCommandCog",) +__all__: "Sequence[str]" = ("AutomaticSlowModeCommandCog", "AutomaticSlowModeTaskCog") logger: "Final[Logger]" = logging.getLogger("TeX-Bot") @@ -28,8 +28,28 @@ class AutomaticSlowModeBaseCog(TeXBotBaseCog): """Base class for automatic slow mode functionality.""" async def calculate_message_rate(self, channel: discord.TextChannel) -> int: - """Calculate the message rate for a given channel.""" - raise NotImplementedError + """ + Calculate the message rate for a given channel. + + Returns the number of messages per minute, rounded to the nearest integer. + This is based on the previous 5 minutes of messages. + """ + from datetime import UTC, datetime, timedelta + + # TODO: Make the time period user configurable. # noqa: FIX002 + + count = len( + [ + message + async for message in channel.history( + after=datetime.now(UTC) - timedelta(minutes=5), + oldest_first=False, + limit=None, + ) + ] + ) + + return round(count / 5) class AutomaticSlowModeTaskCog(AutomaticSlowModeBaseCog): From d0125f0a257acddcccd57c7691d4c754ccdc0703 Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Fri, 23 May 2025 08:03:27 +0100 Subject: [PATCH 07/10] improve disablement message --- cogs/auto_slow_mode.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/cogs/auto_slow_mode.py b/cogs/auto_slow_mode.py index 408b5243..531379c4 100644 --- a/cogs/auto_slow_mode.py +++ b/cogs/auto_slow_mode.py @@ -95,11 +95,21 @@ async def toggle_auto_slow_mode( # type: ignore[misc] ctx: "TeXBotApplicationContext", ) -> None: """Enable or disable automatic slow mode for a channel.""" - # NOTE: This should be replaced when the settings are refactored in the draft PR. + # NOTE: This should be replaced when the settings improved in PR #221 if settings["AUTO_SLOW_MODE"]: settings["AUTO_SLOW_MODE"] = False - await ctx.send("Automatic slow mode is now disabled.") + await ctx.respond( + "Automatic slow mode is now disabled." + "If you would like to keep it disabled, please remember to " + "update the deployment variables as well. " + "If you do not, it will be re-enabled when the bot restarts.", + ) return settings["AUTO_SLOW_MODE"] = True - await ctx.send("Automatic slow mode is now enabled.") + await ctx.respond( + "Automatic slow mode is now disabled." + "If you would like to keep it disabled, please remember to " + "update the deployment variables as well. " + "If you do not, it will be re-enabled when the bot restarts.", + ) From f681a65c01eca73b41a654fe0a53889b02d39818 Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Fri, 23 May 2025 08:03:45 +0100 Subject: [PATCH 08/10] improve disablement message --- cogs/auto_slow_mode.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cogs/auto_slow_mode.py b/cogs/auto_slow_mode.py index 531379c4..f71e19e4 100644 --- a/cogs/auto_slow_mode.py +++ b/cogs/auto_slow_mode.py @@ -108,8 +108,8 @@ async def toggle_auto_slow_mode( # type: ignore[misc] settings["AUTO_SLOW_MODE"] = True await ctx.respond( - "Automatic slow mode is now disabled." - "If you would like to keep it disabled, please remember to " + "Automatic slow mode is now enabled." + "If you would like to keep it enabled, please remember to " "update the deployment variables as well. " - "If you do not, it will be re-enabled when the bot restarts.", + "If you do not, it will be disabled again when the bot restarts.", ) From dce5dff1878cb276d16c2981ac53379367cc7eab Mon Sep 17 00:00:00 2001 From: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Date: Fri, 23 May 2025 08:12:34 +0100 Subject: [PATCH 09/10] basic functionality --- cogs/auto_slow_mode.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/cogs/auto_slow_mode.py b/cogs/auto_slow_mode.py index f71e19e4..2e083d8a 100644 --- a/cogs/auto_slow_mode.py +++ b/cogs/auto_slow_mode.py @@ -49,6 +49,13 @@ async def calculate_message_rate(self, channel: discord.TextChannel) -> int: ] ) + logger.debug( + "Channel: %s | Message count in last 5 minutes: %d | Rate: %d", + channel.name, + count, + round(count / 5), + ) + return round(count / 5) @@ -76,9 +83,23 @@ def cog_unload(self) -> None: @capture_guild_does_not_exist_error async def auto_slow_mode_task(self) -> None: """Task to automatically adjust slow mode in channels.""" + import time + + time.process_time() for channel in self.bot.get_all_channels(): if isinstance(channel, discord.TextChannel): - await self.calculate_message_rate(channel) + message_rate: int = await self.calculate_message_rate(channel) + if message_rate > 5: + await channel.edit( + slowmode_delay=10, reason="TeX-Bot auto slow mode enabled." + ) + await channel.edit(slowmode_delay=0, reason="TeX-Bot auto slow mode disabled.") + logger.debug("Time taken to calculate message rate: %s seconds", time.process_time()) + + @auto_slow_mode_task.before_loop + async def before_tasks(self) -> None: + """Pre-execution hook, preventing any tasks from executing before the bot is ready.""" + await self.bot.wait_until_ready() class AutomaticSlowModeCommandCog(AutomaticSlowModeBaseCog): From 1a6ff7595f5786cee7ca7acea5d30d500764b0bb Mon Sep 17 00:00:00 2001 From: Holly <25277367+Thatsmusic99@users.noreply.github.com> Date: Sun, 15 Jun 2025 13:49:43 +0100 Subject: [PATCH 10/10] Allow committee-elect to update actions (and appear in auto-complete) (#508) Signed-off-by: Holly <25277367+Thatsmusic99@users.noreply.github.com> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: Matty Widdop <18513864+MattyTheHacker@users.noreply.github.com> --- cogs/committee_actions_tracking.py | 52 ++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/cogs/committee_actions_tracking.py b/cogs/committee_actions_tracking.py index 35c1fee9..ccea5bc9 100644 --- a/cogs/committee_actions_tracking.py +++ b/cogs/committee_actions_tracking.py @@ -1,5 +1,6 @@ """Contains cog classes for tracking committee-actions.""" +import contextlib import logging import random from enum import Enum @@ -11,6 +12,7 @@ from db.core.models import AssignedCommitteeAction, DiscordMember from exceptions import ( + CommitteeElectRoleDoesNotExistError, CommitteeRoleDoesNotExistError, InvalidActionDescriptionError, InvalidActionTargetError, @@ -129,11 +131,22 @@ async def autocomplete_get_committee_members( except CommitteeRoleDoesNotExistError: return set() + committee_elect_role: discord.Role | None = None + with contextlib.suppress(CommitteeElectRoleDoesNotExistError): + committee_elect_role = await ctx.bot.committee_elect_role + return { discord.OptionChoice( name=f"{member.display_name} ({member.global_name})", value=str(member.id) ) - for member in committee_role.members + for member in ( + set(committee_role.members) + | ( + set(committee_elect_role.members) + if committee_elect_role is not None + else set() + ) + ) if not member.bot } @@ -281,9 +294,7 @@ async def create( required=True, parameter_name="status", ) - @CommandChecks.check_interaction_user_has_committee_role - @CommandChecks.check_interaction_user_in_main_guild - async def update_status( + async def update_status( # NOTE: Committee role check is not present because non-committee can have actions, and need to be able to list their own actions. self, ctx: "TeXBotApplicationContext", action_id: str, status: str ) -> None: """ @@ -561,9 +572,7 @@ async def action_all_committee( default=None, parameter_name="status", ) - @CommandChecks.check_interaction_user_has_committee_role - @CommandChecks.check_interaction_user_in_main_guild - async def list_user_actions( + async def list_user_actions( # NOTE: Committee role check is not present because non-committee can have actions, and need to be able to list their own actions. self, ctx: "TeXBotApplicationContext", *, @@ -575,15 +584,32 @@ async def list_user_actions( Definition and callback of the "/list" command. Takes in a user and lists out their current actions. + If no user is specified, the user issuing the command will be used. + If a user has the committee role, they can list actions for other users. + If a user does not have the committee role, they can only list their own actions. """ - action_member: discord.Member | discord.User + action_member_id = action_member_id.strip() + + action_member: discord.Member | discord.User = ( + await self.bot.get_member_from_str_id(action_member_id) + if action_member_id + else ctx.user + ) - if action_member_id: - action_member = await self.bot.get_member_from_str_id( - action_member_id, + if action_member != ctx.user and not await self.bot.check_user_has_committee_role( + ctx.user + ): + await ctx.respond( + content="Committee role is required to list actions for other users.", + ephemeral=True, ) - else: - action_member = ctx.user + logger.debug( + "User: %s, tried to list actions for user: %s, " + "but did not have the committee role.", + ctx.user, + action_member, + ) + return user_actions: list[AssignedCommitteeAction]