Repository: python-discord/bot
Issue: Strip spoiler tags for watchlist triggers

With the [recent Spoiler Tag addition](https://support.discordapp.com/hc/en-us/articles/360022320632-Spoiler-Tags-) to Discord, it's now possible to take memes to a whole new level. While the message filters are still operable, it's difficult to read the trigger messages in the mod log, particularly on mobile.

Spoilers are wrapped with `||` (e.g. `||text||`). Let's add a helper method to the modlog cog to strip these from the message content before they are sent.

Changed files:
bot/cogs/moderation/modlog.py, bot/cogs/filtering.py

diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py
index 74538542..6651d38e 100644
--- a/bot/cogs/filtering.py
+++ b/bot/cogs/filtering.py
@@ -6,6 +6,7 @@ import discord.errors
 from dateutil.relativedelta import relativedelta
 from discord import Colour, DMChannel, Member, Message, TextChannel
 from discord.ext.commands import Cog
+from discord.utils import escape_markdown
 
 from bot.bot import Bot
 from bot.cogs.moderation import ModLog
@@ -27,6 +28,7 @@ INVITE_RE = re.compile(
     flags=re.IGNORECASE
 )
 
+SPOILER_RE = re.compile(r"(\\|\\|.+?\\|\\|)", re.DOTALL)
 URL_RE = re.compile(r"(https?://[^\\s]+)", flags=re.IGNORECASE)
 ZALGO_RE = re.compile(r"[\\u0300-\\u036F\\u0489]")
 
@@ -38,6 +40,14 @@ TOKEN_WATCHLIST_PATTERNS = [
 ]
 
 
+def expand_spoilers(text: str) -> str:
+    """Return a string containing all interpretations of a spoilered message."""
+    split_text = SPOILER_RE.split(text)
+    return ''.join(
+        split_text[0::2] + split_text[1::2] + split_text
+    )
+
+
 class Filtering(Cog):
     """Filtering out invites, blacklisting domains, and warning us of certain regular expressions."""
 
@@ -186,8 +196,8 @@ class Filtering(Cog):
                             surroundings = match.string[max(match.start() - 10, 0): match.end() + 10]
                             message_content = (
                                 f"**Match:** '{match[0]}'\\n"
-                                f"**Location:** '...{surroundings}...'\\n"
-                                f"\\n**Original Message:**\\n{msg.content}"
+                                f"**Location:** '...{escape_markdown(surroundings)}...'\\n"
+                                f"\\n**Original Message:**\\n{escape_markdown(msg.content)}"
                             )
                         else:  # Use content of discord Message
                             message_content = msg.content
@@ -244,6 +254,8 @@ class Filtering(Cog):
 
         Only matches words with boundaries before and after the expression.
         """
+        if SPOILER_RE.search(text):
+            text = expand_spoilers(text)
         for regex_pattern in WORD_WATCHLIST_PATTERNS:
             match = regex_pattern.search(text)
             if match:
diff --git a/bot/cogs/moderation/modlog.py b/bot/cogs/moderation/modlog.py
index 5f9bc0c6..c63b4bab 100644
--- a/bot/cogs/moderation/modlog.py
+++ b/bot/cogs/moderation/modlog.py
@@ -12,6 +12,7 @@ from deepdiff import DeepDiff
 from discord import Colour
 from discord.abc import GuildChannel
 from discord.ext.commands import Cog, Context
+from discord.utils import escape_markdown
 
 from bot.bot import Bot
 from bot.constants import Channels, Colours, Emojis, Event, Guild as GuildConstant, Icons, URLs
@@ -389,7 +390,8 @@ class ModLog(Cog, name="ModLog"):
         if member.guild.id != GuildConstant.id:
             return
 
-        message = f"{member} (`{member.id}`)"
+        member_str = escape_markdown(str(member))
+        message = f"{member_str} (`{member.id}`)"
         now = datetime.utcnow()
         difference = abs(relativedelta(now, member.created_at))
 
@@ -415,9 +417,10 @@ class ModLog(Cog, name="ModLog"):
             self._ignored[Event.member_remove].remove(member.id)
             return
 
+        member_str = escape_markdown(str(member))
         await self.send_log_message(
             Icons.sign_out, Colours.soft_red,
-            "User left", f"{member} (`{member.id}`)",
+            "User left", f"{member_str} (`{member.id}`)",
             thumbnail=member.avatar_url_as(static_format="png"),
             channel_id=Channels.user_log
         )
@@ -432,9 +435,10 @@ class ModLog(Cog, name="ModLog"):
             self._ignored[Event.member_unban].remove(member.id)
             return
 
+        member_str = escape_markdown(str(member))
         await self.send_log_message(
             Icons.user_unban, Colour.blurple(),
-            "User unbanned", f"{member} (`{member.id}`)",
+            "User unbanned", f"{member_str} (`{member.id}`)",
             thumbnail=member.avatar_url_as(static_format="png"),
             channel_id=Channels.mod_log
         )
@@ -526,7 +530,8 @@ class ModLog(Cog, name="ModLog"):
         for item in sorted(changes):
             message += f"{Emojis.bullet} {item}\\n"
 
-        message = f"**{after}** (`{after.id}`)\\n{message}"
+        member_str = escape_markdown(str(after))
+        message = f"**{member_str}** (`{after.id}`)\\n{message}"
 
         await self.send_log_message(
             Icons.user_update, Colour.blurple(),
@@ -553,16 +558,17 @@ class ModLog(Cog, name="ModLog"):
         if author.bot:
             return
 
+        author_str = escape_markdown(str(author))
         if channel.category:
             response = (
-                f"**Author:** {author} (`{author.id}`)\\n"
+                f"**Author:** {author_str} (`{author.id}`)\\n"
                 f"**Channel:** {channel.category}/#{channel.name} (`{channel.id}`)\\n"
                 f"**Message ID:** `{message.id}`\\n"
                 "\\n"
             )
         else:
             response = (
-                f"**Author:** {author} (`{author.id}`)\\n"
+                f"**Author:** {author_str} (`{author.id}`)\\n"
                 f"**Channel:** #{channel.name} (`{channel.id}`)\\n"
                 f"**Message ID:** `{message.id}`\\n"
                 "\\n"
@@ -649,6 +655,8 @@ class ModLog(Cog, name="ModLog"):
             return
 
         author = msg_before.author
+        author_str = escape_markdown(str(author))
+
         channel = msg_before.channel
         channel_name = f"{channel.category}/#{channel.name}" if channel.category else f"#{channel.name}"
 
@@ -680,7 +688,7 @@ class ModLog(Cog, name="ModLog"):
                 content_after.append(sub)
 
         response = (
-            f"**Author:** {author} (`{author.id}`)\\n"
+            f"**Author:** {author_str} (`{author.id}`)\\n"
             f"**Channel:** {channel_name} (`{channel.id}`)\\n"
             f"**Message ID:** `{msg_before.id}`\\n"
             "\\n"
@@ -823,8 +831,9 @@ class ModLog(Cog, name="ModLog"):
         if not changes:
             return
 
+        member_str = escape_markdown(str(member))
         message = "\\n".join(f"{Emojis.bullet} {item}" for item in sorted(changes))
-        message = f"**{member}** (`{member.id}`)\\n{message}"
+        message = f"**{member_str}** (`{member.id}`)\\n{message}"
 
         await self.send_log_message(
             icon_url=icon,