diff options
| author | 2021-02-06 10:41:47 +0200 | |
|---|---|---|
| committer | 2021-02-06 10:41:47 +0200 | |
| commit | 81af0099ffd552aa5cb2a61de30cf7bd16a013eb (patch) | |
| tree | 771c3a90bc3e2acd7fb3a972d449d02b2dd7b302 | |
| parent | Merge pull request #1397 from python-discord/feat/1280/deleted-jumplink (diff) | |
Implement showing filterlist entry comment in alerts
Diffstat (limited to '')
| -rw-r--r-- | bot/exts/filters/filtering.py | 63 | 
1 files changed, 40 insertions, 23 deletions
| diff --git a/bot/exts/filters/filtering.py b/bot/exts/filters/filtering.py index 3527bf8bb..6f1374cf4 100644 --- a/bot/exts/filters/filtering.py +++ b/bot/exts/filters/filtering.py @@ -2,7 +2,7 @@ import asyncio  import logging  import re  from datetime import datetime, timedelta -from typing import Any, Dict, List, Mapping, NamedTuple, Optional, Union +from typing import Any, Dict, List, Mapping, NamedTuple, Optional, Tuple, Union  import dateutil  import discord.errors @@ -137,6 +137,10 @@ class Filtering(Cog):          """Fetch items from the filter_list_cache."""          return self.bot.filter_list_cache[f"{list_type.upper()}.{allowed}"].keys() +    def _get_filterlist_value(self, list_type: str, value: Any, *, allowed: bool) -> dict: +        """Fetch one specific value from filter_list_cache.""" +        return self.bot.filter_list_cache[f"{list_type.upper()}.{allowed}"][value] +      @staticmethod      def _expand_spoilers(text: str) -> str:          """Return a string containing all interpretations of a spoilered message.""" @@ -236,7 +240,7 @@ class Filtering(Cog):                  # We also do not need to worry about filters that take the full message,                  # since all we have is an arbitrary string.                  if _filter["enabled"] and _filter["content_only"]: -                    match = await _filter["function"](result) +                    match, reason = await _filter["function"](result)                      if match:                          # If this is a filter (not a watchlist), we set the variable so we know @@ -245,7 +249,7 @@ class Filtering(Cog):                              filter_triggered = True                          stats = self._add_stats(filter_name, match, result) -                        await self._send_log(filter_name, _filter, msg, stats, is_eval=True) +                        await self._send_log(filter_name, _filter, msg, stats, reason, is_eval=True)                          break  # We don't want multiple filters to trigger @@ -267,9 +271,9 @@ class Filtering(Cog):                      # Does the filter only need the message content or the full message?                      if _filter["content_only"]: -                        match = await _filter["function"](msg.content) +                        match, reason = await _filter["function"](msg.content)                      else: -                        match = await _filter["function"](msg) +                        match, reason = await _filter["function"](msg)                      if match:                          is_private = msg.channel.type is discord.ChannelType.private @@ -316,7 +320,7 @@ class Filtering(Cog):                                  log.trace(f"Offensive message {msg.id} will be deleted on {delete_date}")                          stats = self._add_stats(filter_name, match, msg.content) -                        await self._send_log(filter_name, _filter, msg, stats) +                        await self._send_log(filter_name, _filter, msg, stats, reason)                          break  # We don't want multiple filters to trigger @@ -326,6 +330,7 @@ class Filtering(Cog):          _filter: Dict[str, Any],          msg: discord.Message,          stats: Stats, +        reason: Optional[str] = None,          *,          is_eval: bool = False,      ) -> None: @@ -339,6 +344,7 @@ class Filtering(Cog):              ping_everyone = Filter.ping_everyone and _filter.get("ping_everyone", True)          eval_msg = "using !eval " if is_eval else "" +        footer = f"Entry comment: {reason}" if reason else None          message = (              f"The {filter_name} {_filter['type']} was triggered by {format_user(msg.author)} "              f"{channel_str} {eval_msg}with [the following message]({msg.jump_url}):\n\n" @@ -357,6 +363,7 @@ class Filtering(Cog):              channel_id=Channels.mod_alerts,              ping_everyone=ping_everyone,              additional_embeds=stats.additional_embeds, +            footer=footer,          )      def _add_stats(self, name: str, match: FilterMatch, content: str) -> Stats: @@ -381,9 +388,11 @@ class Filtering(Cog):          if name == "filter_invites" and match is not True:              additional_embeds = []              for _, data in match.items(): +                reason = f"\n**Entry comment:**\n{data['reason']}" if data.get('reason') else ""                  embed = discord.Embed(description=(                      f"**Members:**\n{data['members']}\n"                      f"**Active:**\n{data['active']}" +                    f"{reason}"                  ))                  embed.set_author(name=data["name"])                  embed.set_thumbnail(url=data["icon"]) @@ -411,7 +420,7 @@ class Filtering(Cog):              and not msg.author.bot                          # Author not a bot          ) -    async def _has_watch_regex_match(self, text: str) -> Union[bool, re.Match]: +    async def _has_watch_regex_match(self, text: str) -> Tuple[Union[bool, re.Match], Optional[str]]:          """          Return True if `text` matches any regex from `word_watchlist` or `token_watchlist` configs. @@ -429,9 +438,11 @@ class Filtering(Cog):          for pattern in watchlist_patterns:              match = re.search(pattern, text, flags=re.IGNORECASE)              if match: -                return match +                return match, self._get_filterlist_value('filter_token', pattern, allowed=False)['comment'] + +        return False, None -    async def _has_urls(self, text: str) -> bool: +    async def _has_urls(self, text: str) -> Tuple[bool, Optional[str]]:          """Returns True if the text contains one of the blacklisted URLs from the config file."""          if not URL_RE.search(text):              return False @@ -441,20 +452,21 @@ class Filtering(Cog):          for url in domain_blacklist:              if url.lower() in text: -                return True +                return True, self._get_filterlist_value("domain_name", url, allowed=False)["comment"] -        return False +        return False, None      @staticmethod -    async def _has_zalgo(text: str) -> bool: +    async def _has_zalgo(text: str) -> Tuple[bool, None]:          """          Returns True if the text contains zalgo characters.          Zalgo range is \u0300 – \u036F and \u0489. +        Return None as second value for compability with other filters.          """ -        return bool(ZALGO_RE.search(text)) +        return bool(ZALGO_RE.search(text)), None -    async def _has_invites(self, text: str) -> Union[dict, bool]: +    async def _has_invites(self, text: str) -> Tuple[Union[dict, bool], None]:          """          Checks if there's any invites in the text content that aren't in the guild whitelist. @@ -500,6 +512,10 @@ class Filtering(Cog):              )              if invite_not_allowed: +                reason = None +                if guild_id in guild_invite_blacklist: +                    reason = self._get_filterlist_value("guild_invite", guild_id, allowed=False)["comment"] +                  guild_icon_hash = guild["icon"]                  guild_icon = (                      "https://cdn.discordapp.com/icons/" @@ -511,13 +527,14 @@ class Filtering(Cog):                      "id": guild['id'],                      "icon": guild_icon,                      "members": response["approximate_member_count"], -                    "active": response["approximate_presence_count"] +                    "active": response["approximate_presence_count"], +                    "reason": reason                  } -        return invite_data if invite_data else False +        return invite_data if invite_data else False, None      @staticmethod -    async def _has_rich_embed(msg: Message) -> Union[bool, List[discord.Embed]]: +    async def _has_rich_embed(msg: Message) -> Tuple[Union[bool, List[discord.Embed]], None]:          """Determines if `msg` contains any rich embeds not auto-generated from a URL."""          if msg.embeds:              for embed in msg.embeds: @@ -526,24 +543,24 @@ class Filtering(Cog):                      if not embed.url or embed.url not in urls:                          # If `embed.url` does not exist or if `embed.url` is not part of the content                          # of the message, it's unlikely to be an auto-generated embed by Discord. -                        return msg.embeds +                        return msg.embeds, None                      else:                          log.trace(                              "Found a rich embed sent by a regular user account, "                              "but it was likely just an automatic URL embed."                          ) -                        return False -        return False +                        return False, None +        return False, None      @staticmethod -    async def _has_everyone_ping(text: str) -> bool: +    async def _has_everyone_ping(text: str) -> Tuple[bool, None]:          """Determines if `msg` contains an @everyone or @here ping outside of a codeblock."""          # First pass to avoid running re.sub on every message          if not EVERYONE_PING_RE.search(text): -            return False +            return False, None          content_without_codeblocks = CODE_BLOCK_RE.sub("", text) -        return bool(EVERYONE_PING_RE.search(content_without_codeblocks)) +        return bool(EVERYONE_PING_RE.search(content_without_codeblocks)), None      async def notify_member(self, filtered_member: Member, reason: str, channel: TextChannel) -> None:          """ | 
