aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bot/constants.py1
-rw-r--r--bot/exts/filtering/_filter_lists/antispam.py14
-rw-r--r--bot/exts/filtering/_ui/ui.py40
-rw-r--r--bot/exts/filtering/filtering.py36
-rw-r--r--config-default.yml1
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: