diff options
-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 | 31 | ||||
-rw-r--r-- | bot/exts/help_channels/_channel.py | 10 | ||||
-rw-r--r-- | bot/exts/help_channels/_cog.py | 24 |
6 files changed, 52 insertions, 35 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..b6b0421cc 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) @@ -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 670a10446..c9e66d944 100644 --- a/bot/exts/help_channels/_channel.py +++ b/bot/exts/help_channels/_channel.py @@ -131,11 +131,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) 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) |