diff options
| author | 2022-11-05 15:15:32 +0200 | |
|---|---|---|
| committer | 2022-11-05 15:15:32 +0200 | |
| commit | 7fcec400d3b9d5e28222ee83e5fa0a48038ad4e4 (patch) | |
| tree | 7f90d9c33fac0b3c8bd8f67f34934c442fef6d6d | |
| parent | Edit description on filter add/edit, bug fixes (diff) | |
Bring back auto-infraction reporting
- When a filter is set to infract, it will send a message in #filter-log (See PR #1893)
- Once a week the cog will send a message of all auto-infractions sent in the last 7 days, with a way to trigger it manually (See PR #2267)
| -rw-r--r-- | bot/exts/filtering/_filter_lists/filter_list.py | 23 | ||||
| -rw-r--r-- | bot/exts/filtering/_filters/filter.py | 3 | ||||
| -rw-r--r-- | bot/exts/filtering/filtering.py | 101 |
3 files changed, 122 insertions, 5 deletions
diff --git a/bot/exts/filtering/_filter_lists/filter_list.py b/bot/exts/filtering/_filter_lists/filter_list.py index 9ae45bfaf..fd243a109 100644 --- a/bot/exts/filtering/_filter_lists/filter_list.py +++ b/bot/exts/filtering/_filter_lists/filter_list.py @@ -9,6 +9,7 @@ from functools import reduce from operator import or_ from typing import Any +import arrow from discord.ext.commands import BadArgument from bot.exts.filtering._filter_context import Event, FilterContext @@ -55,6 +56,8 @@ class AtomicList: """ id: int + created_at: arrow.Arrow + updated_at: arrow.Arrow name: str list_type: ListType defaults: Defaults @@ -166,7 +169,15 @@ class FilterList(dict[ListType, AtomicList], typing.Generic[T], FieldRequiring): if new_filter: filters[filter_data["id"]] = new_filter - self[list_type] = AtomicList(list_data["id"], self.name, list_type, defaults, filters) + self[list_type] = AtomicList( + list_data["id"], + arrow.get(list_data["created_at"]), + arrow.get(list_data["updated_at"]), + self.name, + list_type, + defaults, + filters + ) return self[list_type] def add_filter(self, list_type: ListType, filter_data: dict) -> T | None: @@ -255,7 +266,15 @@ class UniquesListBase(FilterList[UniqueFilter]): actions, validations = create_settings(list_data["settings"], keep_empty=True) list_type = ListType(list_data["list_type"]) defaults = Defaults(actions, validations) - new_list = SubscribingAtomicList(list_data["id"], self.name, list_type, defaults, {}) + new_list = SubscribingAtomicList( + list_data["id"], + arrow.get(list_data["created_at"]), + arrow.get(list_data["updated_at"]), + self.name, + list_type, + defaults, + {} + ) self[list_type] = new_list filters = {} diff --git a/bot/exts/filtering/_filters/filter.py b/bot/exts/filtering/_filters/filter.py index 45b571b54..49c163d99 100644 --- a/bot/exts/filtering/_filters/filter.py +++ b/bot/exts/filtering/_filters/filter.py @@ -1,6 +1,7 @@ from abc import abstractmethod from typing import Any +import arrow from pydantic import ValidationError from bot.exts.filtering._filter_context import Event, FilterContext @@ -26,6 +27,8 @@ class Filter(FieldRequiring): self.id = filter_data["id"] self.content = filter_data["content"] self.description = filter_data["description"] + self.created_at = arrow.get(filter_data["created_at"]) + self.updated_at = arrow.get(filter_data["updated_at"]) self.actions, self.validations = create_settings(filter_data["settings"], defaults=defaults) if self.extra_fields_type: self.extra_fields = self.extra_fields_type.parse_raw(filter_data["additional_field"] or "{}") # noqa: P103 diff --git a/bot/exts/filtering/filtering.py b/bot/exts/filtering/filtering.py index a204e53f3..c4c118b6f 100644 --- a/bot/exts/filtering/filtering.py +++ b/bot/exts/filtering/filtering.py @@ -1,3 +1,4 @@ +import datetime import json import operator import re @@ -6,11 +7,12 @@ from functools import partial, reduce from io import BytesIO from typing import Literal, Optional, get_type_hints +import arrow import discord from botcore.site_api import ResponseCodeError from discord import Colour, Embed, HTTPException, Message, MessageType -from discord.ext import commands -from discord.ext.commands import BadArgument, Cog, Context, has_any_role +from discord.ext import commands, tasks +from discord.ext.commands import BadArgument, Cog, Context, command, has_any_role import bot import bot.exts.filtering._ui.filter as filters_ui @@ -22,6 +24,7 @@ from bot.exts.filtering._filter_lists import FilterList, ListType, filter_list_t from bot.exts.filtering._filter_lists.filter_list import AtomicList from bot.exts.filtering._filters.filter import Filter from bot.exts.filtering._settings import ActionSettings +from bot.exts.filtering._settings_types.actions.infraction_and_notification import Infraction from bot.exts.filtering._ui.filter import ( build_filter_repr_dict, description_and_settings_converter, filter_serializable_overrides, populate_embed_from_dict ) @@ -33,11 +36,13 @@ from bot.exts.filtering._ui.ui import ( from bot.exts.filtering._utils import past_tense, repr_equals, starting_value, to_serializable from bot.log import get_logger from bot.pagination import LinePaginator +from bot.utils.channel import is_mod_channel from bot.utils.message_cache import MessageCache log = get_logger(__name__) CACHE_SIZE = 100 +WEEKLY_REPORT_ISO_DAY = 3 # 1=Monday, 7=Sunday class Filtering(Cog): @@ -81,6 +86,7 @@ class Filtering(Cog): log.error(f"Failed to fetch filters webhook with ID `{Webhooks.filters}`.") self.collect_loaded_types(example_list) + self.weekly_auto_infraction_report_task.start() def subscribe(self, filter_list: FilterList, *events: Event) -> None: """ @@ -735,6 +741,14 @@ class Filtering(Cog): ) # endregion + # region: utility commands + + @command(name="filter_report") + async def force_send_weekly_report(self, ctx: Context) -> None: + """Respond with a list of auto-infractions added in the last 7 days.""" + await self.send_weekly_auto_infraction_report(ctx.channel) + + # endregion # region: helper functions def _load_raw_filter_list(self, list_data: dict) -> AtomicList | None: @@ -925,7 +939,30 @@ class Filtering(Cog): return msg @staticmethod + async def _maybe_alert_auto_infraction( + filter_list: FilterList, list_type: ListType, filter_: Filter, old_filter: Filter | None = None + ) -> None: + """If the filter is new and applies an auto-infraction, or was edited to apply a different one, log it.""" + infraction_type = filter_.overrides[0].get("infraction_type") + if not infraction_type: + infraction_type = filter_list[list_type].defaults.actions.get_setting("infraction_type") + if old_filter: + old_infraction_type = old_filter.overrides[0].get("infraction_type") + if not old_infraction_type: + old_infraction_type = filter_list[list_type].defaults.actions.get_setting("infraction_type") + if infraction_type == old_infraction_type: + return + + if infraction_type != Infraction.NONE: + filter_log = bot.instance.get_channel(Channels.filter_log) + if filter_log: + await filter_log.send( + f":warning: Heads up! The new {filter_list[list_type].label} filter " + f"({filter_}) will automatically {infraction_type.name.lower()} users." + ) + async def _post_new_filter( + self, msg: Message, filter_list: FilterList, list_type: ListType, @@ -951,13 +988,14 @@ class Filtering(Cog): response = await bot.instance.api_client.post('bot/filter/filters', json=to_serializable(payload)) new_filter = filter_list.add_filter(list_type, response) if new_filter: + await self._maybe_alert_auto_infraction(filter_list, list_type, new_filter) extra_msg = Filtering._identical_filters_message(content, filter_list, list_type, new_filter) await msg.reply(f"✅ Added filter: {new_filter}" + extra_msg) else: await msg.reply(":x: Could not create the filter. Are you sure it's implemented?") - @staticmethod async def _patch_filter( + self, filter_: Filter, msg: Message, filter_list: FilterList, @@ -991,6 +1029,7 @@ class Filtering(Cog): ) # Return type can be None, but if it's being edited then it's not supposed to be. edited_filter = filter_list.add_filter(list_type, response) + await self._maybe_alert_auto_infraction(filter_list, list_type, edited_filter, filter_) extra_msg = Filtering._identical_filters_message(content, filter_list, list_type, edited_filter) await msg.reply(f"✅ Edited filter: {edited_filter}" + extra_msg) @@ -1078,6 +1117,62 @@ class Filtering(Cog): await LinePaginator.paginate(lines, ctx, embed, max_lines=15, empty=False, reply=True) # endregion + # region: tasks + + @tasks.loop(time=datetime.time(hour=18)) + async def weekly_auto_infraction_report_task(self) -> None: + """Trigger an auto-infraction report to be sent if it is the desired day of the week (WEEKLY_REPORT_ISO_DAY).""" + if arrow.utcnow().isoweekday() != WEEKLY_REPORT_ISO_DAY: + return + + await self.send_weekly_auto_infraction_report() + + async def send_weekly_auto_infraction_report(self, channel: discord.TextChannel | discord.Thread = None) -> None: + """ + Send a list of auto-infractions added in the last 7 days to the specified channel. + + If `channel` is not specified, it is sent to #mod-meta. + """ + seven_days_ago = arrow.utcnow().shift(days=-7) + if not channel: + log.info("Auto-infraction report: the channel to report to is missing.") + channel = self.bot.get_channel(Channels.mod_meta) + elif not is_mod_channel(channel): + # Silently fail if output is going to be a non-mod channel. + log.info(f"Auto-infraction report: the channel {channel} is not a mod channel.") + return + + found_filters = defaultdict(list) + # Extract all auto-infraction filters added in the past 7 days from each filter type + for filter_list in self.filter_lists.values(): + for sublist in filter_list.values(): + default_infraction_type = sublist.defaults.actions.get_setting("infraction_type") + for filter_ in sublist.filters.values(): + if max(filter_.created_at, filter_.updated_at) < seven_days_ago: + continue + infraction_type = filter_.overrides[0].get("infraction_type") + if ( + (infraction_type and infraction_type != Infraction.NONE) + or (not infraction_type and default_infraction_type != Infraction.NONE) + ): + found_filters[sublist.label].append((filter_, infraction_type or default_infraction_type)) + + # Nicely format the output so each filter list type is grouped + lines = [f"**Auto-infraction filters added since {seven_days_ago.format('YYYY-MM-DD')}**"] + for list_label, filters in found_filters.items(): + lines.append("\n".join([f"**{list_label.title()}**"]+[f"{filter_} ({infr})" for filter_, infr in filters])) + + if len(lines) == 1: + lines.append("Nothing to show") + + await channel.send("\n\n".join(lines)) + log.info("Successfully sent auto-infraction report.") + + # endregion + + async def cog_unload(self) -> None: + """Cancel the weekly auto-infraction filter report on cog unload.""" + self.weekly_auto_infraction_report_task.cancel() async def setup(bot: Bot) -> None: |