aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar ks129 <[email protected]>2021-02-06 10:41:47 +0200
committerGravatar ks129 <[email protected]>2021-02-06 10:41:47 +0200
commit81af0099ffd552aa5cb2a61de30cf7bd16a013eb (patch)
tree771c3a90bc3e2acd7fb3a972d449d02b2dd7b302
parentMerge 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.py63
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:
"""