diff options
| author | 2022-11-26 21:27:17 +0200 | |
|---|---|---|
| committer | 2022-11-27 19:22:32 +0200 | |
| commit | 2def5b6b4da89ae1579f2f16df2a70a69953bf42 (patch) | |
| tree | 1e17d5bfe1be0642776c840bd6cd6067c48f40e8 | |
| parent | Phishing filter add command (diff) | |
Add alert view
The view provides info about the offending user.
This change required changing the cog to generate a fresh webhook on startup so that it's authenticated to the bot.
Only webhooks authenticated to the bot can send message components.
In the future, this view might be used for more advanced usages such as having a button to pardon the user.
| -rw-r--r-- | bot/constants.py | 1 | ||||
| -rw-r--r-- | bot/exts/filtering/_filter_lists/antispam.py | 14 | ||||
| -rw-r--r-- | bot/exts/filtering/_ui/ui.py | 40 | ||||
| -rw-r--r-- | bot/exts/filtering/filtering.py | 36 | ||||
| -rw-r--r-- | config-default.yml | 1 |
5 files changed, 74 insertions, 18 deletions
diff --git a/bot/constants.py b/bot/constants.py index 8a2571a98..1d6ab8e7e 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -455,7 +455,6 @@ class Webhooks(metaclass=YAMLGetter): duck_pond: int incidents: int incidents_archive: int - filters: int class Roles(metaclass=YAMLGetter): diff --git a/bot/exts/filtering/_filter_lists/antispam.py b/bot/exts/filtering/_filter_lists/antispam.py index 41eda9878..e549404c4 100644 --- a/bot/exts/filtering/_filter_lists/antispam.py +++ b/bot/exts/filtering/_filter_lists/antispam.py @@ -10,17 +10,15 @@ from operator import add, or_ import arrow from botcore.utils import scheduling from botcore.utils.logging import get_logger -from discord import HTTPException, Member +from discord import Member -import bot -from bot.constants import Webhooks from bot.exts.filtering._filter_context import FilterContext from bot.exts.filtering._filter_lists.filter_list import ListType, SubscribingAtomicList, UniquesListBase from bot.exts.filtering._filters.antispam import antispam_filter_types from bot.exts.filtering._filters.filter import Filter, UniqueFilter from bot.exts.filtering._settings import ActionSettings from bot.exts.filtering._settings_types.actions.infraction_and_notification import Infraction -from bot.exts.filtering._ui.ui import build_mod_alert +from bot.exts.filtering._ui.ui import AlertView, build_mod_alert if typing.TYPE_CHECKING: from bot.exts.filtering.filtering import Filtering @@ -147,9 +145,9 @@ class DeletionContext: """Post the mod alert.""" if not self.contexts or not self.rules: return - try: - webhook = await bot.instance.fetch_webhook(Webhooks.filters) - except HTTPException: + + webhook = antispam_list.filtering_cog.webhook + if not webhook: return ctx, *other_contexts = self.contexts @@ -182,4 +180,4 @@ class DeletionContext: embed.set_footer( text="The list of actions taken includes actions from additional contexts after deletion began." ) - await webhook.send(username="Anti-Spam", content=ctx.alert_content, embeds=[embed]) + await webhook.send(username="Anti-Spam", content=ctx.alert_content, embeds=[embed], view=AlertView(new_ctx)) diff --git a/bot/exts/filtering/_ui/ui.py b/bot/exts/filtering/_ui/ui.py index ec549725c..e71bab0d1 100644 --- a/bot/exts/filtering/_ui/ui.py +++ b/bot/exts/filtering/_ui/ui.py @@ -20,6 +20,7 @@ import bot from bot.constants import Colours from bot.exts.filtering._filter_context import FilterContext from bot.exts.filtering._filter_lists import FilterList +from bot.exts.filtering._utils import FakeContext from bot.utils.messages import format_channel, format_user, upload_log log = get_logger(__name__) @@ -40,6 +41,8 @@ MAX_MODAL_TITLE_LENGTH = 45 # Max number of items in a select MAX_SELECT_ITEMS = 25 MAX_EMBED_DESCRIPTION = 4080 +# Number of seconds before timeout of the alert view +ALERT_VIEW_TIMEOUT = 3600 SETTINGS_DELIMITER = re.compile(r"\s+(?=\S+=\S+)") SINGLE_SETTING_PATTERN = re.compile(r"[\w/]+=.+") @@ -502,3 +505,40 @@ class DeleteConfirmationView(discord.ui.View): async def cancel(self, interaction: Interaction, button: discord.ui.Button) -> None: """Cancel the filter list deletion.""" await interaction.response.edit_message(content="🚫 Operation canceled.", view=None) + + +class AlertView(discord.ui.View): + """A view providing info about the offending user.""" + + def __init__(self, ctx: FilterContext): + super().__init__(timeout=ALERT_VIEW_TIMEOUT) + self.ctx = ctx + + @discord.ui.button(label="ID") + async def user_id(self, interaction: Interaction, button: discord.ui.Button) -> None: + """Reply with the ID of the offending user.""" + await interaction.response.send_message(self.ctx.author.id, ephemeral=True) + + @discord.ui.button(emoji="👤") + async def user_info(self, interaction: Interaction, button: discord.ui.Button) -> None: + """Send the info embed of the offending user.""" + command = bot.instance.get_command("user") + if not command: + await interaction.response.send_message("The command `user` is not loaded.", ephemeral=True) + return + + await interaction.response.defer() + fake_ctx = FakeContext(interaction.channel, command, author=interaction.user) + await command(fake_ctx, self.ctx.author) + + @discord.ui.button(emoji="⚠") + async def user_infractions(self, interaction: Interaction, button: discord.ui.Button) -> None: + """Send the infractions embed of the offending user.""" + command = bot.instance.get_command("infraction search") + if not command: + await interaction.response.send_message("The command `infraction search` is not loaded.", ephemeral=True) + return + + await interaction.response.defer() + fake_ctx = FakeContext(interaction.channel, command, author=interaction.user) + await command(fake_ctx, self.ctx.author) diff --git a/bot/exts/filtering/filtering.py b/bot/exts/filtering/filtering.py index 673b5487c..2e433aff6 100644 --- a/bot/exts/filtering/filtering.py +++ b/bot/exts/filtering/filtering.py @@ -23,7 +23,8 @@ import bot import bot.exts.filtering._ui.filter as filters_ui from bot import constants from bot.bot import Bot -from bot.constants import Channels, MODERATION_ROLES, Roles, Webhooks +from bot.constants import Channels, Guild, MODERATION_ROLES, Roles +from bot.exts.backend.branding._repository import HEADERS, PARAMS from bot.exts.filtering._filter_context import Event, FilterContext from bot.exts.filtering._filter_lists import FilterList, ListType, filter_list_types, list_type_converter from bot.exts.filtering._filter_lists.filter_list import AtomicList @@ -36,7 +37,7 @@ from bot.exts.filtering._ui.filter import ( from bot.exts.filtering._ui.filter_list import FilterListAddView, FilterListEditView, settings_converter from bot.exts.filtering._ui.search import SearchEditView, search_criteria_converter from bot.exts.filtering._ui.ui import ( - ArgumentCompletionView, DeleteConfirmationView, build_mod_alert, format_response_error + AlertView, ArgumentCompletionView, DeleteConfirmationView, build_mod_alert, format_response_error ) from bot.exts.filtering._utils import past_tense, repr_equals, starting_value, to_serializable from bot.exts.moderation.infraction.infractions import COMP_BAN_DURATION, COMP_BAN_REASON @@ -48,6 +49,7 @@ from bot.utils.message_cache import MessageCache log = get_logger(__name__) +WEBHOOK_ICON_URL = r"https://github.com/python-discord/branding/raw/main/icons/filter/filter_pfp.png" CACHE_SIZE = 100 HOURS_BETWEEN_NICKNAME_ALERTS = 1 OFFENSIVE_MSG_DELETE_TIME = datetime.timedelta(days=7) @@ -70,7 +72,7 @@ class Filtering(Cog): self.filter_lists: dict[str, FilterList] = {} self._subscriptions: defaultdict[Event, list[FilterList]] = defaultdict(list) self.delete_scheduler = scheduling.Scheduler(self.__class__.__name__) - self.webhook = None + self.webhook: discord.Webhook = None self.loaded_settings = {} self.loaded_filters = {} @@ -93,10 +95,8 @@ class Filtering(Cog): if not example_list and loaded_list: example_list = loaded_list - try: - self.webhook = await self.bot.fetch_webhook(Webhooks.filters) - except HTTPException: - log.error(f"Failed to fetch filters webhook with ID `{Webhooks.filters}`.") + # The webhook must be generated by the bot to send messages with components through it. + self.webhook = await self._generate_webhook() self.collect_loaded_types(example_list) await self.schedule_offending_messages_deletion() @@ -861,6 +861,24 @@ class Filtering(Cog): self.filter_lists[list_name] = filter_list_types[list_name](self) return self.filter_lists[list_name].add_list(list_data) + async def _generate_webhook(self) -> discord.Webhook | None: + """Generate a webhook with the filtering avatar.""" + # Download the filtering avatar from the branding repository. + webhook_icon = None + async with self.bot.http_session.get(WEBHOOK_ICON_URL, params=PARAMS, headers=HEADERS) as response: + if response.status == 200: + log.debug("Successfully fetched filtering webhook icon, reading payload.") + webhook_icon = await response.read() + else: + log.warning(f"Failed to fetch filtering webhook icon due to status: {response.status}") + + alerts_channel = self.bot.get_guild(Guild.id).get_channel(Channels.mod_alerts) + try: + return await alerts_channel.create_webhook(name="Filtering System", avatar=webhook_icon) + except HTTPException: + log.error("Failed to create filters webhook.") + return None + async def _resolve_action( self, ctx: FilterContext ) -> tuple[ActionSettings | None, dict[FilterList, list[str]], dict[AtomicList, list[Filter]]]: @@ -895,7 +913,9 @@ class Filtering(Cog): name = f"{ctx.event.name.replace('_', ' ').title()} Filter" embed = await build_mod_alert(ctx, triggered_filters) # There shouldn't be more than 10, but if there are it's not very useful to send them all. - await self.webhook.send(username=name, content=ctx.alert_content, embeds=[embed, *ctx.alert_embeds][:10]) + await self.webhook.send( + username=name, content=ctx.alert_content, embeds=[embed, *ctx.alert_embeds][:10], view=AlertView(ctx) + ) async def _recently_alerted_name(self, member: discord.Member) -> bool: """When it hasn't been `HOURS_BETWEEN_NICKNAME_ALERTS` since last alert, return False, otherwise True.""" diff --git a/config-default.yml b/config-default.yml index 4407177d9..f0e217d6c 100644 --- a/config-default.yml +++ b/config-default.yml @@ -324,7 +324,6 @@ guild: incidents: 816650601844572212 incidents_archive: 720671599790915702 python_news: &PYNEWS_WEBHOOK 704381182279942324 - filters: 926442964463521843 keys: |