diff options
Diffstat (limited to '')
| -rw-r--r-- | bot/exts/filtering/_filter_context.py | 1 | ||||
| -rw-r--r-- | bot/exts/filtering/_filter_lists/token.py | 4 | ||||
| -rw-r--r-- | bot/exts/filtering/_settings_types/actions/remove_context.py | 17 | ||||
| -rw-r--r-- | bot/exts/filtering/filtering.py | 33 | ||||
| -rw-r--r-- | bot/exts/help_channels/_channel.py | 22 | ||||
| -rw-r--r-- | bot/exts/help_channels/_cog.py | 24 | 
6 files changed, 64 insertions, 37 deletions
diff --git a/bot/exts/filtering/_filter_context.py b/bot/exts/filtering/_filter_context.py index 0794a48e4..9d8bbba9a 100644 --- a/bot/exts/filtering/_filter_context.py +++ b/bot/exts/filtering/_filter_context.py @@ -21,6 +21,7 @@ class Event(Enum):      MESSAGE = auto()      MESSAGE_EDIT = auto()      NICKNAME = auto() +    THREAD_NAME = auto()      SNEKBOX = auto() diff --git a/bot/exts/filtering/_filter_lists/token.py b/bot/exts/filtering/_filter_lists/token.py index 0c591ac3b..5bb21cfc5 100644 --- a/bot/exts/filtering/_filter_lists/token.py +++ b/bot/exts/filtering/_filter_lists/token.py @@ -32,7 +32,9 @@ class TokensList(FilterList[TokenFilter]):      def __init__(self, filtering_cog: Filtering):          super().__init__() -        filtering_cog.subscribe(self, Event.MESSAGE, Event.MESSAGE_EDIT, Event.NICKNAME, Event.SNEKBOX) +        filtering_cog.subscribe( +            self, Event.MESSAGE, Event.MESSAGE_EDIT, Event.NICKNAME, Event.THREAD_NAME, Event.SNEKBOX +        )      def get_filter_type(self, content: str) -> type[Filter]:          """Get a subclass of filter matching the filter list and the filter's content.""" diff --git a/bot/exts/filtering/_settings_types/actions/remove_context.py b/bot/exts/filtering/_settings_types/actions/remove_context.py index 5ec2613f4..dc01426d8 100644 --- a/bot/exts/filtering/_settings_types/actions/remove_context.py +++ b/bot/exts/filtering/_settings_types/actions/remove_context.py @@ -1,7 +1,7 @@  from collections import defaultdict  from typing import ClassVar -from discord import Message +from discord import Message, Thread  from discord.errors import HTTPException  from pydis_core.utils import scheduling  from pydis_core.utils.logging import get_logger @@ -52,6 +52,8 @@ class RemoveContext(ActionEntry):              await self._handle_messages(ctx)          elif ctx.event == Event.NICKNAME:              await self._handle_nickname(ctx) +        elif ctx.event == Event.THREAD_NAME: +            await self._handle_thread(ctx)      @staticmethod      async def _handle_messages(ctx: FilterContext) -> None: @@ -106,7 +108,18 @@ class RemoveContext(ActionEntry):              return          await command(FakeContext(ctx.message, alerts_channel, command), ctx.author, None, reason=SUPERSTAR_REASON) -        ctx.action_descriptions.append("superstar") +        ctx.action_descriptions.append("superstarred") + +    @staticmethod +    async def _handle_thread(ctx: FilterContext) -> None: +        """Delete the context thread.""" +        if isinstance(ctx.channel, Thread): +            try: +                await ctx.channel.delete() +            except HTTPException: +                ctx.action_descriptions.append("failed to delete thread") +            else: +                ctx.action_descriptions.append("deleted thread")      def union(self, other: Self) -> Self:          """Combines two actions of the same type. Each type of action is executed once per filter.""" diff --git a/bot/exts/filtering/filtering.py b/bot/exts/filtering/filtering.py index 392428bb0..82006c9db 100644 --- a/bot/exts/filtering/filtering.py +++ b/bot/exts/filtering/filtering.py @@ -12,7 +12,7 @@ from typing import Literal, Optional, get_type_hints  import arrow  import discord  from async_rediscache import RedisCache -from discord import Colour, Embed, HTTPException, Message, MessageType +from discord import Colour, Embed, HTTPException, Message, MessageType, Thread  from discord.ext import commands, tasks  from discord.ext.commands import BadArgument, Cog, Context, command, has_any_role  from pydis_core.site_api import ResponseCodeError @@ -206,6 +206,11 @@ class Filtering(Cog):          """Filter the contents of a sent message."""          if msg.author.bot or msg.webhook_id or msg.type == MessageType.auto_moderation_action:              return +        if msg.type == MessageType.channel_name_change and isinstance(msg.channel, Thread): +            ctx = FilterContext.from_message(Event.THREAD_NAME, msg) +            await self._check_bad_name(ctx) +            return +          self.message_cache.append(msg)          ctx = FilterContext.from_message(Event.MESSAGE, msg, None, self.message_cache) @@ -218,7 +223,7 @@ class Filtering(Cog):          nick_ctx = FilterContext.from_message(Event.NICKNAME, msg)          nick_ctx.content = msg.author.display_name -        await self._check_bad_name(nick_ctx) +        await self._check_bad_display_name(nick_ctx)          await self._maybe_schedule_msg_delete(ctx, result_actions)          self._increment_stats(triggers) @@ -252,6 +257,12 @@ class Filtering(Cog):          ctx = FilterContext(Event.NICKNAME, member, None, member.display_name, None)          await self._check_bad_name(ctx) +    @Cog.listener() +    async def on_thread_create(self, thread: Thread) -> None: +        """Check for bad words in new thread names.""" +        ctx = FilterContext(Event.THREAD_NAME, thread.owner, thread, thread.name, None) +        await self._check_bad_name(ctx) +      async def filter_snekbox_output(          self, stdout: str, files: list[FileAttachment], msg: Message      ) -> tuple[bool, set[str]]: @@ -966,11 +977,17 @@ class Filtering(Cog):          return False      @lock_arg("filtering.check_bad_name", "ctx", attrgetter("author.id")) -    async def _check_bad_name(self, ctx: FilterContext) -> None: +    async def _check_bad_display_name(self, ctx: FilterContext) -> None:          """Check filter triggers in the passed context - a member's display name."""          if await self._recently_alerted_name(ctx.author):              return +        new_ctx = await self._check_bad_name(ctx) +        if new_ctx.send_alert: +            # Update time when alert sent +            await self.name_alerts.set(ctx.author.id, arrow.utcnow().timestamp()) +    async def _check_bad_name(self, ctx: FilterContext) -> FilterContext: +        """Check filter triggers for some given name (thread name, a member's display name)."""          name = ctx.content          normalised_name = unicodedata.normalize("NFKC", name)          cleaned_normalised_name = "".join([c for c in normalised_name if not unicodedata.combining(c)]) @@ -981,13 +998,13 @@ class Filtering(Cog):          new_ctx = ctx.replace(content=" ".join(names_to_check))          result_actions, list_messages, triggers = await self._resolve_action(new_ctx) +        new_ctx = new_ctx.replace(content=ctx.content)  # Alert with the original content.          if result_actions: -            await result_actions.action(ctx) -        if ctx.send_alert: -            await self._send_alert(ctx, list_messages)  # `ctx` has the original content. -            # Update time when alert sent -            await self.name_alerts.set(ctx.author.id, arrow.utcnow().timestamp()) +            await result_actions.action(new_ctx) +        if new_ctx.send_alert: +            await self._send_alert(new_ctx, list_messages)          self._increment_stats(triggers) +        return new_ctx      async def _resolve_list_type_and_name(          self, ctx: Context, list_type: ListType | None = None, list_name: str | None = None, *, exclude: str = "" diff --git a/bot/exts/help_channels/_channel.py b/bot/exts/help_channels/_channel.py index b1a319145..774e9178e 100644 --- a/bot/exts/help_channels/_channel.py +++ b/bot/exts/help_channels/_channel.py @@ -142,11 +142,15 @@ async def help_post_opened(opened_post: discord.Thread, *, reopen: bool = False)      try:          await opened_post.starter_message.pin()      except (discord.HTTPException, AttributeError) as e: -        # Suppress if the message was not found, most likely deleted +        # Suppress if the message or post were not found, most likely deleted          # The message being deleted could be surfaced as an AttributeError on .starter_message,          # or as an exception from the Discord API, depending on timing and cache status. -        if isinstance(e, discord.HTTPException) and e.code != 10008: -            raise e +        # The post being deleting would happen if it had a bad name that would cause the filtering system to delete it. +        if isinstance(e, discord.HTTPException): +            if e.code == 10003:  # Post not found. +                return +            elif e.code != 10008:  # 10008 - Starter message not found. +                raise e      await send_opened_post_message(opened_post) @@ -178,7 +182,11 @@ async def help_post_deleted(deleted_post_event: discord.RawThreadDeleteEvent) ->      _stats.report_post_count()      cached_post = deleted_post_event.thread      if cached_post and not cached_post.archived: -        # If the post is in the bot's cache, and it was not archived before deleting, report a complete session. +        # If the post is in the bot's cache, and it was not archived before deleting, +        # report a complete session and remove the cooldown. +        poster = cached_post.owner +        cooldown_role = cached_post.guild.get_role(constants.Roles.help_cooldown) +        await members.handle_role_change(poster, poster.remove_roles, cooldown_role)          await _stats.report_complete_session(cached_post, _stats.ClosingReason.DELETED) @@ -218,6 +226,12 @@ async def maybe_archive_idle_post(post: discord.Thread, scheduler: scheduling.Sc      If `has_task` is True and rescheduling is required, the extant task to make the post      dormant will first be cancelled.      """ +    try: +        await post.guild.fetch_channel(post.id) +    except discord.HTTPException: +        log.trace(f"Not closing missing post #{post} ({post.id}).") +        return +      if post.locked:          log.trace(f"Not closing already closed post #{post} ({post.id}).")          return diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py index 6c8478a3f..dd6dee9ee 100644 --- a/bot/exts/help_channels/_cog.py +++ b/bot/exts/help_channels/_cog.py @@ -1,7 +1,5 @@  """Contains the Cog that receives discord.py events and defers most actions to other files in the module.""" -import typing as t -  import discord  from discord.ext import commands  from pydis_core.utils import scheduling @@ -13,9 +11,6 @@ from bot.log import get_logger  log = get_logger(__name__) -if t.TYPE_CHECKING: -    from bot.exts.filtering.filtering import Filtering -  class HelpForum(commands.Cog):      """ @@ -62,18 +57,6 @@ class HelpForum(commands.Cog):              self.bot.stats.incr("help.dormant_invoke.staff")          return has_role -    async def post_with_disallowed_title_check(self, post: discord.Thread) -> None: -        """Check if the given post has a bad word, alerting moderators if it does.""" -        filter_cog: Filtering | None = self.bot.get_cog("Filtering") -        if filter_cog and (match := filter_cog.get_name_match(post.name)): -            mod_alerts = self.bot.get_channel(constants.Channels.mod_alerts) -            await mod_alerts.send( -                f"<@&{constants.Roles.moderators}>\n" -                f"<@{post.owner_id}> ({post.owner_id}) opened the post {post.mention} ({post.id}), " -                "which triggered the token filter with its name!\n" -                f"**Match:** {match.group()}" -            ) -      @commands.group(name="help-forum", aliases=("hf",))      async def help_forum_group(self,  ctx: commands.Context) -> None:          """A group of commands that help manage our help forum system.""" @@ -133,7 +116,7 @@ class HelpForum(commands.Cog):      @commands.Cog.listener("on_message")      async def new_post_listener(self, message: discord.Message) -> None: -        """Defer application of new post logic for posts the help forum to the _channel helper.""" +        """Defer application of new post logic for posts in the help forum to the _channel helper."""          if not isinstance(message.channel, discord.Thread):              return          thread = message.channel @@ -145,7 +128,6 @@ class HelpForum(commands.Cog):          if thread.parent_id != self.help_forum_channel.id:              return -        # await self.post_with_disallowed_title_check(thread)  TODO bring this back with the new filtering system          await _channel.help_post_opened(thread)          delay = min(constants.HelpChannels.deleted_idle_minutes, constants.HelpChannels.idle_minutes) * 60 @@ -162,12 +144,10 @@ class HelpForum(commands.Cog):              return          if not before.archived and after.archived:              await _channel.help_post_archived(after) -        if before.name != after.name: -            await self.post_with_disallowed_title_check(after)      @commands.Cog.listener()      async def on_raw_thread_delete(self, deleted_thread_event: discord.RawThreadDeleteEvent) -> None: -        """Defer application of new post logic for posts the help forum to the _channel helper.""" +        """Defer application of deleted post logic for posts in the help forum to the _channel helper."""          if deleted_thread_event.parent_id == self.help_forum_channel.id:              await _channel.help_post_deleted(deleted_thread_event)  |