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: | 
