From e9c2bcce3e00e88ccff35885e50c4ed3ecbd9e0f Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Wed, 3 Mar 2021 18:03:44 +0530 Subject: Send webhook embed containing information about the message if there is a message link in the incident report --- bot/constants.py | 1 + bot/exts/moderation/incidents.py | 73 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/bot/constants.py b/bot/constants.py index f99913b17..33c911874 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -481,6 +481,7 @@ class Webhooks(metaclass=YAMLGetter): big_brother: int dev_log: int duck_pond: int + incidents: int incidents_archive: int diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index a3d90e3fe..0d63ef34f 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -1,16 +1,18 @@ import asyncio import logging +import re import typing as t from datetime import datetime from enum import Enum import discord +from async_rediscache import RedisCache from discord.ext.commands import Cog from bot.bot import Bot from bot.constants import Channels, Colours, Emojis, Guild, Webhooks from bot.utils import scheduling -from bot.utils.messages import sub_clyde +from bot.utils.messages import format_user, sub_clyde log = logging.getLogger(__name__) @@ -22,6 +24,10 @@ CRAWL_LIMIT = 50 # Seconds for `crawl_task` to sleep after adding reactions to a message CRAWL_SLEEP = 2 +DISCORD_MESSAGE_LINK_RE = re.compile( + r"discord(?:[\.,]|dot)com(?:\/|slash)channels(?:\/|slash)[0-9]{18}(?:\/|slash)[0-9]{18}(?:\/|slash)[0-9]{18}" +) + class Signal(Enum): """ @@ -114,9 +120,9 @@ def is_incident(message: discord.Message) -> bool: """True if `message` qualifies as an incident, False otherwise.""" conditions = ( message.channel.id == Channels.incidents, # Message sent in #incidents - not message.author.bot, # Not by a bot - not message.content.startswith("#"), # Doesn't start with a hash - not message.pinned, # And isn't header + not message.author.bot, # Not by a bot + not message.content.startswith("#"), # Doesn't start with a hash + not message.pinned, # And isn't header ) return all(conditions) @@ -131,6 +137,32 @@ def has_signals(message: discord.Message) -> bool: return ALL_SIGNALS.issubset(own_reactions(message)) +async def make_message_link_embed(incident: discord.Message, message_link: str) -> discord.Embed: + """ + Create an embed representation of discord message link contained in the incident report. + + The Embed would contain the following information --> + Author: @Jason Terror ♦ (736234578745884682) + Channel: Special/#bot-commands (814190307980607493) + Content: This is a very important message! + """ + channel_id = int(message_link.split("/")[3]) + msg_id = int(message_link.split("/")[4]) + + channel = incident.guild.get_channel(channel_id) + message = await channel.fetch_message(msg_id) + + text = message.content + channel = message.channel + description = ( + f"**Author:** {format_user(message.author)}\n" + f"**Channel:** {channel.category}/#{channel.name} (`{channel.id}`)\n" + f"**Content:** {text[:2045] + '...' if len(text) > 2048 else text}\n" + "\n" + ) + return discord.Embed(description=description) + + async def add_signals(incident: discord.Message) -> None: """ Add `Signal` member emoji to `incident` as reactions. @@ -186,6 +218,10 @@ class Incidents(Cog): Please refer to function docstrings for implementation details. """ + # This dictionary maps a incident message to the message link embeds(s) sent by it + # RedisCache[discord.Message.id, List[discord.Message.id]] + message_link_embeds_cache = RedisCache() + def __init__(self, bot: Bot) -> None: """Prepare `event_lock` and schedule `crawl_task` on start-up.""" self.bot = bot @@ -340,6 +376,12 @@ class Incidents(Cog): else: log.trace("Deletion was confirmed") + log.trace("Deleting discord links webhook message.") + webhook_msg_id = await self.message_link_embeds_cache.get(incident.id) + webhook = await self.bot.fetch_webhook(Webhooks.incidents) + await webhook.delete_message(webhook_msg_id) + log.trace("Successfully deleted discord links webhook message.") + async def resolve_message(self, message_id: int) -> t.Optional[discord.Message]: """ Get `discord.Message` for `message_id` from cache, or API. @@ -421,6 +463,29 @@ class Incidents(Cog): async def on_message(self, message: discord.Message) -> None: """Pass `message` to `add_signals` if and only if it satisfies `is_incident`.""" if is_incident(message): + message_links = DISCORD_MESSAGE_LINK_RE.findall(message.content) + if message_links: + + embeds = [] + for message_link in message_links: + embeds.append( + await make_message_link_embed(message, message_link) + ) + + try: + webhook = await self.bot.fetch_webhook(Webhooks.incidents) + webhook_msg = await webhook.send( + embeds=embeds, + username=sub_clyde(message.author.name), + avatar_url=message.author.avatar_url, + wait=True + ) + except Exception: + log.exception(f"Failed to send message link embeds {message.id} to #incidents") + else: + log.trace("Message Link Embeds Sent successfully!") + await self.message_link_embeds_cache.set(message.id, webhook_msg.id) + await add_signals(message) -- cgit v1.2.3 From a2958fb30320b6f956a14a5f4669af527f97a523 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Wed, 3 Mar 2021 18:08:54 +0530 Subject: Add incidents webhook to default config template --- config-default.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config-default.yml b/config-default.yml index d77eacc7e..70e972086 100644 --- a/config-default.yml +++ b/config-default.yml @@ -308,6 +308,7 @@ guild: big_brother: 569133704568373283 dev_log: 680501655111729222 duck_pond: 637821475327311927 + incidents: 816650601844572212 incidents_archive: 720671599790915702 python_news: &PYNEWS_WEBHOOK 704381182279942324 -- cgit v1.2.3 From c32aee329016a8f9d9947d75a52ac26f8d90029c Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Thu, 4 Mar 2021 13:47:53 +0530 Subject: Send multiple webhook messages in case of more than 10 message links --- bot/exts/moderation/incidents.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 0d63ef34f..10a1f5fbd 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -8,6 +8,7 @@ from enum import Enum import discord from async_rediscache import RedisCache from discord.ext.commands import Cog +from more_itertools.recipes import grouper from bot.bot import Bot from bot.constants import Channels, Colours, Emojis, Guild, Webhooks @@ -377,9 +378,14 @@ class Incidents(Cog): log.trace("Deletion was confirmed") log.trace("Deleting discord links webhook message.") - webhook_msg_id = await self.message_link_embeds_cache.get(incident.id) + webhook_msg_ids = await self.message_link_embeds_cache.get(incident.id) + webhook_msg_ids = webhook_msg_ids.split(',') webhook = await self.bot.fetch_webhook(Webhooks.incidents) - await webhook.delete_message(webhook_msg_id) + + for x, msg in enumerate(webhook_msg_ids): + await webhook.delete_message(msg) + log.trace(f"Deleted discord links webhook message{x}/{len(webhook_msg_ids)}") + log.trace("Successfully deleted discord links webhook message.") async def resolve_message(self, message_id: int) -> t.Optional[discord.Message]: @@ -474,17 +480,24 @@ class Incidents(Cog): try: webhook = await self.bot.fetch_webhook(Webhooks.incidents) - webhook_msg = await webhook.send( - embeds=embeds, - username=sub_clyde(message.author.name), - avatar_url=message.author.avatar_url, - wait=True - ) + webhook_embed_list = list(grouper(embeds, 10)) + webhook_msg_ids = [] + + for x, embed in enumerate(webhook_embed_list): + webhook_msg = await webhook.send( + embeds=[x for x in embed if x is not None], + username=sub_clyde(message.author.name), + avatar_url=message.author.avatar_url, + wait=True + ) + webhook_msg_ids.append(webhook_msg.id) + log.trace(f"Message Link Embed {x+1}/{len(webhook_embed_list)} Sent Succesfully") + except Exception: log.exception(f"Failed to send message link embeds {message.id} to #incidents") else: + await self.message_link_embeds_cache.set(message.id, ','.join(map(str, webhook_msg_ids))) log.trace("Message Link Embeds Sent successfully!") - await self.message_link_embeds_cache.set(message.id, webhook_msg.id) await add_signals(message) -- cgit v1.2.3 From 7c4fdbaf49202c1b0b7340f3c53c88da2fb88330 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Thu, 4 Mar 2021 16:44:21 +0530 Subject: Use MessageConverter to find messages --- bot/exts/moderation/incidents.py | 60 +++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 10a1f5fbd..198224b83 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -7,10 +7,10 @@ from enum import Enum import discord from async_rediscache import RedisCache -from discord.ext.commands import Cog +from discord.ext.commands import Cog, Context, MessageConverter from more_itertools.recipes import grouper -from bot.bot import Bot +from bot import bot from bot.constants import Channels, Colours, Emojis, Guild, Webhooks from bot.utils import scheduling from bot.utils.messages import format_user, sub_clyde @@ -26,7 +26,8 @@ CRAWL_LIMIT = 50 CRAWL_SLEEP = 2 DISCORD_MESSAGE_LINK_RE = re.compile( - r"discord(?:[\.,]|dot)com(?:\/|slash)channels(?:\/|slash)[0-9]{18}(?:\/|slash)[0-9]{18}(?:\/|slash)[0-9]{18}" + r"http(?:s):\/\/discord(?:[\.,]|dot)com(?:\/|slash)channels(?:\/|slash)[0-9]{18}(?:\/|slash)[0-9]{18}" + r"(?:\/|slash)[0-9]{18}" ) @@ -138,7 +139,7 @@ def has_signals(message: discord.Message) -> bool: return ALL_SIGNALS.issubset(own_reactions(message)) -async def make_message_link_embed(incident: discord.Message, message_link: str) -> discord.Embed: +async def make_message_link_embed(ctx: Context, message_link: str) -> discord.Embed: """ Create an embed representation of discord message link contained in the incident report. @@ -147,21 +148,28 @@ async def make_message_link_embed(incident: discord.Message, message_link: str) Channel: Special/#bot-commands (814190307980607493) Content: This is a very important message! """ - channel_id = int(message_link.split("/")[3]) - msg_id = int(message_link.split("/")[4]) - - channel = incident.guild.get_channel(channel_id) - message = await channel.fetch_message(msg_id) - - text = message.content - channel = message.channel - description = ( - f"**Author:** {format_user(message.author)}\n" - f"**Channel:** {channel.category}/#{channel.name} (`{channel.id}`)\n" - f"**Content:** {text[:2045] + '...' if len(text) > 2048 else text}\n" - "\n" - ) - return discord.Embed(description=description) + embed = discord.Embed() + + try: + message_convert_object = MessageConverter() + message = await message_convert_object.convert(ctx, message_link) + + except Exception as e: + embed.title = f"{e}" + embed.colour = Colours.soft_red + + else: + text = message.content + channel = message.channel + + embed.description = ( + f"**Author:** {format_user(message.author)}\n" + f"**Channel:** {channel.category}/#{channel.name} (`{channel.id}`)\n" + f"**Content:** {text[:2045] + '...' if len(text) > 2048 else text}\n" + "\n" + ) + + return embed async def add_signals(incident: discord.Message) -> None: @@ -223,7 +231,7 @@ class Incidents(Cog): # RedisCache[discord.Message.id, List[discord.Message.id]] message_link_embeds_cache = RedisCache() - def __init__(self, bot: Bot) -> None: + def __init__(self, bot: bot.Bot) -> None: """Prepare `event_lock` and schedule `crawl_task` on start-up.""" self.bot = bot @@ -470,13 +478,13 @@ class Incidents(Cog): """Pass `message` to `add_signals` if and only if it satisfies `is_incident`.""" if is_incident(message): message_links = DISCORD_MESSAGE_LINK_RE.findall(message.content) + print(message_links) if message_links: embeds = [] for message_link in message_links: - embeds.append( - await make_message_link_embed(message, message_link) - ) + ctx = await self.bot.get_context(message) + embeds.append(await make_message_link_embed(ctx, message_link)) try: webhook = await self.bot.fetch_webhook(Webhooks.incidents) @@ -491,17 +499,19 @@ class Incidents(Cog): wait=True ) webhook_msg_ids.append(webhook_msg.id) - log.trace(f"Message Link Embed {x+1}/{len(webhook_embed_list)} Sent Succesfully") + log.trace(f"Message Link Embed {x + 1}/{len(webhook_embed_list)} Sent Succesfully") except Exception: log.exception(f"Failed to send message link embeds {message.id} to #incidents") + else: await self.message_link_embeds_cache.set(message.id, ','.join(map(str, webhook_msg_ids))) log.trace("Message Link Embeds Sent successfully!") + log.trace(f"Skipping discord message link detection on {message.id}: message doesn't qualify.") await add_signals(message) -def setup(bot: Bot) -> None: +def setup(bot: bot.Bot) -> None: """Load the Incidents cog.""" bot.add_cog(Incidents(bot)) -- cgit v1.2.3 From d52127300289d1e054c931cc3493e239f914cf27 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Thu, 4 Mar 2021 16:46:47 +0530 Subject: Use str() when checking for message.content --- bot/exts/moderation/incidents.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 198224b83..9ee1407d4 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -477,7 +477,7 @@ class Incidents(Cog): async def on_message(self, message: discord.Message) -> None: """Pass `message` to `add_signals` if and only if it satisfies `is_incident`.""" if is_incident(message): - message_links = DISCORD_MESSAGE_LINK_RE.findall(message.content) + message_links = DISCORD_MESSAGE_LINK_RE.findall(str(message.content)) print(message_links) if message_links: -- cgit v1.2.3 From a6c609fcc745b9cb99ec1fcfc365b1f364e6ff31 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Fri, 5 Mar 2021 07:56:19 +0530 Subject: Fix tests according to the changes done to incidents.py --- bot/exts/moderation/incidents.py | 1 - tests/bot/exts/moderation/test_incidents.py | 21 +++++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 9ee1407d4..813b717a8 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -478,7 +478,6 @@ class Incidents(Cog): """Pass `message` to `add_signals` if and only if it satisfies `is_incident`.""" if is_incident(message): message_links = DISCORD_MESSAGE_LINK_RE.findall(str(message.content)) - print(message_links) if message_links: embeds = [] diff --git a/tests/bot/exts/moderation/test_incidents.py b/tests/bot/exts/moderation/test_incidents.py index cbf7f7bcf..3c991dacc 100644 --- a/tests/bot/exts/moderation/test_incidents.py +++ b/tests/bot/exts/moderation/test_incidents.py @@ -7,6 +7,7 @@ from unittest.mock import AsyncMock, MagicMock, call, patch import aiohttp import discord +from async_rediscache import RedisSession from bot.constants import Colours from bot.exts.moderation import incidents @@ -22,6 +23,22 @@ from tests.helpers import ( MockUser, ) +redis_session = None +redis_loop = asyncio.get_event_loop() + + +def setUpModule(): # noqa: N802 + """Create and connect to the fakeredis session.""" + global redis_session + redis_session = RedisSession(use_fakeredis=True) + redis_loop.run_until_complete(redis_session.connect()) + + +def tearDownModule(): # noqa: N802 + """Close the fakeredis session.""" + if redis_session: + redis_loop.run_until_complete(redis_session.close()) + class MockAsyncIterable: """ @@ -513,7 +530,7 @@ class TestProcessEvent(TestIncidents): with patch("bot.exts.moderation.incidents.Incidents.make_confirmation_task", mock_task): await self.cog_instance.process_event( reaction=incidents.Signal.ACTIONED.value, - incident=MockMessage(), + incident=MockMessage(id=123), member=MockMember(roles=[MockRole(id=1)]) ) @@ -533,7 +550,7 @@ class TestProcessEvent(TestIncidents): with patch("bot.exts.moderation.incidents.Incidents.make_confirmation_task", mock_task): await self.cog_instance.process_event( reaction=incidents.Signal.ACTIONED.value, - incident=MockMessage(), + incident=MockMessage(id=123), member=MockMember(roles=[MockRole(id=1)]) ) except asyncio.TimeoutError: -- cgit v1.2.3 From e113b17f68452573b1b236f7577120dd3783f6da Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Fri, 5 Mar 2021 11:28:46 +0530 Subject: Rollback to changes which aren't required --- bot/exts/moderation/incidents.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 813b717a8..201c6d1ca 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -10,7 +10,7 @@ from async_rediscache import RedisCache from discord.ext.commands import Cog, Context, MessageConverter from more_itertools.recipes import grouper -from bot import bot +from bot.bot import Bot from bot.constants import Channels, Colours, Emojis, Guild, Webhooks from bot.utils import scheduling from bot.utils.messages import format_user, sub_clyde @@ -122,9 +122,9 @@ def is_incident(message: discord.Message) -> bool: """True if `message` qualifies as an incident, False otherwise.""" conditions = ( message.channel.id == Channels.incidents, # Message sent in #incidents - not message.author.bot, # Not by a bot - not message.content.startswith("#"), # Doesn't start with a hash - not message.pinned, # And isn't header + not message.author.bot, # Not by a bot + not message.content.startswith("#"), # Doesn't start with a hash + not message.pinned, # And isn't header ) return all(conditions) @@ -231,7 +231,7 @@ class Incidents(Cog): # RedisCache[discord.Message.id, List[discord.Message.id]] message_link_embeds_cache = RedisCache() - def __init__(self, bot: bot.Bot) -> None: + def __init__(self, bot: Bot) -> None: """Prepare `event_lock` and schedule `crawl_task` on start-up.""" self.bot = bot @@ -511,6 +511,6 @@ class Incidents(Cog): await add_signals(message) -def setup(bot: bot.Bot) -> None: +def setup(bot: Bot) -> None: """Load the Incidents cog.""" bot.add_cog(Incidents(bot)) -- cgit v1.2.3 From 93f25e91808b9ed83e0201e26c8abf8841caf10c Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Fri, 5 Mar 2021 11:48:26 +0530 Subject: If message content more than 500 characters shorten it done to 300 characters --- bot/exts/moderation/incidents.py | 58 +++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 201c6d1ca..1f1f20d6c 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -122,9 +122,9 @@ def is_incident(message: discord.Message) -> bool: """True if `message` qualifies as an incident, False otherwise.""" conditions = ( message.channel.id == Channels.incidents, # Message sent in #incidents - not message.author.bot, # Not by a bot - not message.content.startswith("#"), # Doesn't start with a hash - not message.pinned, # And isn't header + not message.author.bot, # Not by a bot + not message.content.startswith("#"), # Doesn't start with a hash + not message.pinned, # And isn't header ) return all(conditions) @@ -165,7 +165,7 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> discord.Em embed.description = ( f"**Author:** {format_user(message.author)}\n" f"**Channel:** {channel.category}/#{channel.name} (`{channel.id}`)\n" - f"**Content:** {text[:2045] + '...' if len(text) > 2048 else text}\n" + f"**Content:** {text[:300] + '...' if len(text) > 500 else text}\n" "\n" ) @@ -485,31 +485,41 @@ class Incidents(Cog): ctx = await self.bot.get_context(message) embeds.append(await make_message_link_embed(ctx, message_link)) - try: - webhook = await self.bot.fetch_webhook(Webhooks.incidents) - webhook_embed_list = list(grouper(embeds, 10)) - webhook_msg_ids = [] + webhook = await self.bot.fetch_webhook(Webhooks.incidents) + webhook_embed_list = list(grouper(embeds, 10)) - for x, embed in enumerate(webhook_embed_list): - webhook_msg = await webhook.send( - embeds=[x for x in embed if x is not None], - username=sub_clyde(message.author.name), - avatar_url=message.author.avatar_url, - wait=True - ) - webhook_msg_ids.append(webhook_msg.id) - log.trace(f"Message Link Embed {x + 1}/{len(webhook_embed_list)} Sent Succesfully") - - except Exception: - log.exception(f"Failed to send message link embeds {message.id} to #incidents") - - else: - await self.message_link_embeds_cache.set(message.id, ','.join(map(str, webhook_msg_ids))) - log.trace("Message Link Embeds Sent successfully!") + await self.send_webhooks(webhook_embed_list, message, webhook) log.trace(f"Skipping discord message link detection on {message.id}: message doesn't qualify.") await add_signals(message) + async def send_webhooks( + self, + webhook_embed_list: t.List, + message: discord.Message, + webhook: discord.Webhook + ) -> t.List[int]: + webhook_msg_ids = [] + try: + for x, embed in enumerate(webhook_embed_list): + webhook_msg = await webhook.send( + embeds=[x for x in embed if x is not None], + username=sub_clyde(message.author.name), + avatar_url=message.author.avatar_url, + wait=True + ) + webhook_msg_ids.append(webhook_msg.id) + log.trace(f"Message Link Embed {x + 1}/{len(webhook_embed_list)} Sent Succesfully") + + except Exception: + log.exception(f"Failed to send message link embeds {message.id} to #incidents") + + else: + await self.message_link_embeds_cache.set(message.id, ','.join(map(str, webhook_msg_ids))) + log.trace("Message Link Embeds Sent successfully!") + + return webhook_msg_ids + def setup(bot: Bot) -> None: """Load the Incidents cog.""" -- cgit v1.2.3 From aaf62d36ba2bcd2593756f19534547f740b57f16 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Fri, 5 Mar 2021 12:32:26 +0530 Subject: Add a docstring to 'send_webhooks' function --- bot/exts/moderation/incidents.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 1f1f20d6c..8304df174 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -499,6 +499,15 @@ class Incidents(Cog): message: discord.Message, webhook: discord.Webhook ) -> t.List[int]: + """ + Send Message Link Embeds to #incidents channel. + + Uses the `webhook` passed in as parameter to send the embeds + in `webhook_embed_list` parameter. + + After sending each webhook it maps the `message.id` to the + `webhook_msg_ids` IDs in the async rediscache. + """ webhook_msg_ids = [] try: for x, embed in enumerate(webhook_embed_list): -- cgit v1.2.3 From 39116e698f48468bec19d03e946c271e0083ccf4 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sat, 6 Mar 2021 16:48:23 +0530 Subject: Update regex to support all message links i.e. support for 'app', 'canary', 'ptb' --- bot/exts/moderation/incidents.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 8304df174..dabdaed2c 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -26,8 +26,9 @@ CRAWL_LIMIT = 50 CRAWL_SLEEP = 2 DISCORD_MESSAGE_LINK_RE = re.compile( - r"http(?:s):\/\/discord(?:[\.,]|dot)com(?:\/|slash)channels(?:\/|slash)[0-9]{18}(?:\/|slash)[0-9]{18}" - r"(?:\/|slash)[0-9]{18}" + r'(https?:\/\/(?:(ptb|canary|www)\.)?discord(?:app)?\.com\/channels\/' + r'[0-9]{15,21}' + r'\/[0-9]{15,21}\/[0-9]{15,21})' ) @@ -483,7 +484,7 @@ class Incidents(Cog): embeds = [] for message_link in message_links: ctx = await self.bot.get_context(message) - embeds.append(await make_message_link_embed(ctx, message_link)) + embeds.append(await make_message_link_embed(ctx, message_link[0])) webhook = await self.bot.fetch_webhook(Webhooks.incidents) webhook_embed_list = list(grouper(embeds, 10)) -- cgit v1.2.3 From 10a5909e39b3dcda901a5b50b4ef9327cbd62226 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sat, 6 Mar 2021 16:54:33 +0530 Subject: Run webhook message deletion if webhook_msg_id var is True --- bot/exts/moderation/incidents.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index dabdaed2c..7f8a34a01 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -388,12 +388,14 @@ class Incidents(Cog): log.trace("Deleting discord links webhook message.") webhook_msg_ids = await self.message_link_embeds_cache.get(incident.id) - webhook_msg_ids = webhook_msg_ids.split(',') - webhook = await self.bot.fetch_webhook(Webhooks.incidents) - for x, msg in enumerate(webhook_msg_ids): - await webhook.delete_message(msg) - log.trace(f"Deleted discord links webhook message{x}/{len(webhook_msg_ids)}") + if webhook_msg_ids: + webhook_msg_ids = webhook_msg_ids.split(',') + webhook = await self.bot.fetch_webhook(Webhooks.incidents) + + for x, msg in enumerate(webhook_msg_ids): + await webhook.delete_message(msg) + log.trace(f"Deleted discord links webhook message{x}/{len(webhook_msg_ids)}") log.trace("Successfully deleted discord links webhook message.") -- cgit v1.2.3 From 2f8c63f88d1d5345be8b64eeda8fbc098c057a74 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sat, 6 Mar 2021 17:10:27 +0530 Subject: Modify tests to support redis cache, done with the help @SebastiaanZ --- tests/bot/exts/moderation/test_incidents.py | 32 ++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/bot/exts/moderation/test_incidents.py b/tests/bot/exts/moderation/test_incidents.py index 3c991dacc..239f86e6f 100644 --- a/tests/bot/exts/moderation/test_incidents.py +++ b/tests/bot/exts/moderation/test_incidents.py @@ -23,22 +23,6 @@ from tests.helpers import ( MockUser, ) -redis_session = None -redis_loop = asyncio.get_event_loop() - - -def setUpModule(): # noqa: N802 - """Create and connect to the fakeredis session.""" - global redis_session - redis_session = RedisSession(use_fakeredis=True) - redis_loop.run_until_complete(redis_session.connect()) - - -def tearDownModule(): # noqa: N802 - """Close the fakeredis session.""" - if redis_session: - redis_loop.run_until_complete(redis_session.close()) - class MockAsyncIterable: """ @@ -300,6 +284,22 @@ class TestIncidents(unittest.IsolatedAsyncioTestCase): the instance as they wish. """ + session = None + + async def flush(self): + """Flush everything from the database to prevent carry-overs between tests.""" + with await self.session.pool as connection: + await connection.flushall() + + async def asyncSetUp(self): + self.session = RedisSession(use_fakeredis=True) + await self.session.connect() + await self.flush() + + async def asyncTearDown(self): + if self.session: + await self.session.close() + def setUp(self): """ Prepare a fresh `Incidents` instance for each test. -- cgit v1.2.3 From bb516c51bb811c51b0781d1898ac8e3d578fd4f7 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sat, 6 Mar 2021 17:17:29 +0530 Subject: Allign comments to maintain readability --- bot/exts/moderation/incidents.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 7f8a34a01..be6708b83 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -123,9 +123,9 @@ def is_incident(message: discord.Message) -> bool: """True if `message` qualifies as an incident, False otherwise.""" conditions = ( message.channel.id == Channels.incidents, # Message sent in #incidents - not message.author.bot, # Not by a bot - not message.content.startswith("#"), # Doesn't start with a hash - not message.pinned, # And isn't header + not message.author.bot, # Not by a bot + not message.content.startswith("#"), # Doesn't start with a hash + not message.pinned, # And isn't header ) return all(conditions) -- cgit v1.2.3 From 6f3210dde67b1bcfa1c7c9c96c86f76d36af69f1 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sat, 6 Mar 2021 17:28:32 +0530 Subject: Ignore N802 in 'asyncSetUp' and 'asyncTearDown' function in test_incidents.py --- tests/bot/exts/moderation/test_incidents.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/bot/exts/moderation/test_incidents.py b/tests/bot/exts/moderation/test_incidents.py index 239f86e6f..c015951b3 100644 --- a/tests/bot/exts/moderation/test_incidents.py +++ b/tests/bot/exts/moderation/test_incidents.py @@ -291,12 +291,12 @@ class TestIncidents(unittest.IsolatedAsyncioTestCase): with await self.session.pool as connection: await connection.flushall() - async def asyncSetUp(self): + async def asyncSetUp(self): # noqa: N802 self.session = RedisSession(use_fakeredis=True) await self.session.connect() await self.flush() - async def asyncTearDown(self): + async def asyncTearDown(self): # noqa: N802 if self.session: await self.session.close() -- cgit v1.2.3 From 9ad28b96db7fd0ebc3b0ee8b1d853de494077944 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sat, 13 Mar 2021 06:37:13 +0530 Subject: Run black code formatter. --- bot/exts/moderation/incidents.py | 74 ++++++++++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 21 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index be6708b83..6a2c8c4b0 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -26,9 +26,9 @@ CRAWL_LIMIT = 50 CRAWL_SLEEP = 2 DISCORD_MESSAGE_LINK_RE = re.compile( - r'(https?:\/\/(?:(ptb|canary|www)\.)?discord(?:app)?\.com\/channels\/' - r'[0-9]{15,21}' - r'\/[0-9]{15,21}\/[0-9]{15,21})' + r"(https?:\/\/(?:(ptb|canary|www)\.)?discord(?:app)?\.com\/channels\/" + r"[0-9]{15,21}" + r"\/[0-9]{15,21}\/[0-9]{15,21})" ) @@ -72,7 +72,11 @@ async def download_file(attachment: discord.Attachment) -> t.Optional[discord.Fi log.exception("Failed to download attachment") -async def make_embed(incident: discord.Message, outcome: Signal, actioned_by: discord.Member) -> FileEmbed: +async def make_embed( + incident: discord.Message, + outcome: Signal, + actioned_by: discord.Member +) -> FileEmbed: """ Create an embed representation of `incident` for the #incidents-archive channel. @@ -110,9 +114,13 @@ async def make_embed(incident: discord.Message, outcome: Signal, actioned_by: di file = await download_file(attachment) if file is not None: - embed.set_image(url=f"attachment://{attachment.filename}") # Embed displays the attached file + embed.set_image( + url=f"attachment://{attachment.filename}" + ) # Embed displays the attached file else: - embed.set_author(name="[Failed to relay attachment]", url=attachment.proxy_url) # Embed links the file + embed.set_author( + name="[Failed to relay attachment]", url=attachment.proxy_url + ) # Embed links the file else: file = None @@ -182,7 +190,9 @@ async def add_signals(incident: discord.Message) -> None: existing_reacts = own_reactions(incident) for signal_emoji in Signal: - if signal_emoji.value in existing_reacts: # This would not raise, but it is a superfluous API call + if ( + signal_emoji.value in existing_reacts + ): # This would not raise, but it is a superfluous API call log.trace(f"Skipping emoji as it's already been placed: {signal_emoji}") else: log.trace(f"Adding reaction: {signal_emoji}") @@ -270,7 +280,12 @@ class Incidents(Cog): log.debug("Crawl task finished!") - async def archive(self, incident: discord.Message, outcome: Signal, actioned_by: discord.Member) -> bool: + async def archive( + self, + incident: discord.Message, + outcome: Signal, + actioned_by: discord.Member + ) -> bool: """ Relay an embed representation of `incident` to the #incidents-archive channel. @@ -291,7 +306,9 @@ class Incidents(Cog): not all information was relayed, return False. This signals that the original message is not safe to be deleted, as we will lose some information. """ - log.info(f"Archiving incident: {incident.id} (outcome: {outcome}, actioned by: {actioned_by})") + log.info( + f"Archiving incident: {incident.id} (outcome: {outcome}, actioned by: {actioned_by})" + ) embed, attachment_file = await make_embed(incident, outcome, actioned_by) try: @@ -316,7 +333,9 @@ class Incidents(Cog): If `timeout` passes, this will raise `asyncio.TimeoutError`, signaling that we haven't been able to confirm that the message was deleted. """ - log.trace(f"Confirmation task will wait {timeout=} seconds for {incident.id=} to be deleted") + log.trace( + f"Confirmation task will wait {timeout=} seconds for {incident.id=} to be deleted" + ) def check(payload: discord.RawReactionActionEvent) -> bool: return payload.message_id == incident.id @@ -324,7 +343,12 @@ class Incidents(Cog): coroutine = self.bot.wait_for(event="raw_message_delete", check=check, timeout=timeout) return scheduling.create_task(coroutine, event_loop=self.bot.loop) - async def process_event(self, reaction: str, incident: discord.Message, member: discord.Member) -> None: + async def process_event( + self, + reaction: str, + incident: discord.Message, + member: discord.Member + ) -> None: """ Process a `reaction_add` event in #incidents. @@ -366,7 +390,9 @@ class Incidents(Cog): relay_successful = await self.archive(incident, signal, actioned_by=member) if not relay_successful: - log.trace("Original message will not be deleted as we failed to relay it to the archive") + log.trace( + "Original message will not be deleted as we failed to relay it to the archive" + ) return timeout = 5 # Seconds @@ -390,7 +416,7 @@ class Incidents(Cog): webhook_msg_ids = await self.message_link_embeds_cache.get(incident.id) if webhook_msg_ids: - webhook_msg_ids = webhook_msg_ids.split(',') + webhook_msg_ids = webhook_msg_ids.split(",") webhook = await self.bot.fetch_webhook(Webhooks.incidents) for x, msg in enumerate(webhook_msg_ids): @@ -458,7 +484,9 @@ class Incidents(Cog): if payload.channel_id != Channels.incidents or payload.member.bot: return - log.trace(f"Received reaction add event in #incidents, waiting for crawler: {self.crawl_task.done()=}") + log.trace( + f"Received reaction add event in #incidents, waiting for crawler: {self.crawl_task.done()=}" + ) await self.crawl_task log.trace(f"Acquiring event lock: {self.event_lock.locked()=}") @@ -493,14 +521,16 @@ class Incidents(Cog): await self.send_webhooks(webhook_embed_list, message, webhook) - log.trace(f"Skipping discord message link detection on {message.id}: message doesn't qualify.") + log.trace( + f"Skipping discord message link detection on {message.id}: message doesn't qualify." + ) await add_signals(message) async def send_webhooks( - self, - webhook_embed_list: t.List, - message: discord.Message, - webhook: discord.Webhook + self, + webhook_embed_list: t.List, + message: discord.Message, + webhook: discord.Webhook ) -> t.List[int]: """ Send Message Link Embeds to #incidents channel. @@ -518,7 +548,7 @@ class Incidents(Cog): embeds=[x for x in embed if x is not None], username=sub_clyde(message.author.name), avatar_url=message.author.avatar_url, - wait=True + wait=True, ) webhook_msg_ids.append(webhook_msg.id) log.trace(f"Message Link Embed {x + 1}/{len(webhook_embed_list)} Sent Succesfully") @@ -527,7 +557,9 @@ class Incidents(Cog): log.exception(f"Failed to send message link embeds {message.id} to #incidents") else: - await self.message_link_embeds_cache.set(message.id, ','.join(map(str, webhook_msg_ids))) + await self.message_link_embeds_cache.set( + message.id, ",".join(map(str, webhook_msg_ids)) + ) log.trace("Message Link Embeds Sent successfully!") return webhook_msg_ids -- cgit v1.2.3 From fa54337286adcbb812a0e4a6c53fa730818f1f6c Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sat, 20 Mar 2021 15:25:00 +0530 Subject: Apply grammar and style changes. --- bot/exts/moderation/incidents.py | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 6a2c8c4b0..b77fdfabe 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -1,6 +1,7 @@ import asyncio import logging import re +import textwrap import typing as t from datetime import datetime from enum import Enum @@ -150,7 +151,7 @@ def has_signals(message: discord.Message) -> bool: async def make_message_link_embed(ctx: Context, message_link: str) -> discord.Embed: """ - Create an embed representation of discord message link contained in the incident report. + Create an embedded representation of the discord message link contained in the incident report. The Embed would contain the following information --> Author: @Jason Terror ♦ (736234578745884682) @@ -174,7 +175,7 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> discord.Em embed.description = ( f"**Author:** {format_user(message.author)}\n" f"**Channel:** {channel.category}/#{channel.name} (`{channel.id}`)\n" - f"**Content:** {text[:300] + '...' if len(text) > 500 else text}\n" + f"**Content:** {textwrap.shorten(text, 300, placeholder='...')}\n" "\n" ) @@ -320,7 +321,9 @@ class Incidents(Cog): file=attachment_file, ) except Exception: - log.exception(f"Failed to archive incident {incident.id} to #incidents-archive") + log.exception( + f"Failed to archive incident {incident.id} to #incidents-archive" + ) return False else: log.trace("Message archived successfully!") @@ -498,19 +501,19 @@ class Incidents(Cog): return if not is_incident(message): - log.debug("Ignoring event for a non-incident message") + log.debug("Ignoring event for a non-incident message.") return await self.process_event(str(payload.emoji), message, payload.member) - log.trace("Releasing event lock") + log.trace("Releasing event lock.") @Cog.listener() async def on_message(self, message: discord.Message) -> None: """Pass `message` to `add_signals` if and only if it satisfies `is_incident`.""" if is_incident(message): message_links = DISCORD_MESSAGE_LINK_RE.findall(str(message.content)) - if message_links: + if message_links: embeds = [] for message_link in message_links: ctx = await self.bot.get_context(message) @@ -530,16 +533,16 @@ class Incidents(Cog): self, webhook_embed_list: t.List, message: discord.Message, - webhook: discord.Webhook + webhook: discord.Webhook, ) -> t.List[int]: """ Send Message Link Embeds to #incidents channel. - Uses the `webhook` passed in as parameter to send the embeds - in `webhook_embed_list` parameter. + Uses the `webhook` passed in as a parameter to send + the embeds in the `webhook_embed_list` parameter. - After sending each webhook it maps the `message.id` to the - `webhook_msg_ids` IDs in the async rediscache. + After sending each webhook it maps the `message.id` + to the `webhook_msg_ids` IDs in the async redis-cache. """ webhook_msg_ids = [] try: @@ -551,10 +554,14 @@ class Incidents(Cog): wait=True, ) webhook_msg_ids.append(webhook_msg.id) - log.trace(f"Message Link Embed {x + 1}/{len(webhook_embed_list)} Sent Succesfully") + log.trace( + f"Message Link Embed {x + 1}/{len(webhook_embed_list)} sent successfully." + ) except Exception: - log.exception(f"Failed to send message link embeds {message.id} to #incidents") + log.exception( + f"Failed to send message link embeds {message.id} to #incidents." + ) else: await self.message_link_embeds_cache.set( -- cgit v1.2.3 From e8625daa99d9bbbd929d132be44164ce1254b74e Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Fri, 16 Apr 2021 16:49:32 +0530 Subject: Use `DiscordException` instead of broad exception clause. --- bot/exts/moderation/incidents.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index b77fdfabe..edf621e02 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -164,7 +164,7 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> discord.Em message_convert_object = MessageConverter() message = await message_convert_object.convert(ctx, message_link) - except Exception as e: + except discord.DiscordException as e: embed.title = f"{e}" embed.colour = Colours.soft_red @@ -558,7 +558,7 @@ class Incidents(Cog): f"Message Link Embed {x + 1}/{len(webhook_embed_list)} sent successfully." ) - except Exception: + except discord.DiscordException: log.exception( f"Failed to send message link embeds {message.id} to #incidents." ) -- cgit v1.2.3 From a1bb6f38738b50183ea9042c29d1fbc8b0b18bb7 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sat, 17 Apr 2021 08:45:16 +0530 Subject: Refactors code. Earlier on message edit the message wasn't run through extract message links to see if new message links are added or if some got deleted. Similarly the cache was updated when a message got deleted. Now it makes extract message links a helper function and runs it on message edits and deletes in case there are some changes in the message links. This commit also updates the doc strings for functions according to the new changes done. --- bot/exts/moderation/incidents.py | 120 ++++++++++++++++++++++++++++++--------- 1 file changed, 94 insertions(+), 26 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index edf621e02..77017659e 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -207,6 +207,35 @@ async def add_signals(incident: discord.Message) -> None: return +async def extract_message_links(message: discord.Message) -> t.Optional[list]: + """ + Checks if there's any message links in the text content. + + Then passes the the message_link into `make_message_link_embed` to format a + embed for it containing information about the link. + + As discord only allows a max of 10 embeds in a single webhook we need to + group the embeds into group of 10 and then return the list. + + If no links are found for the message, it logs a trace statement. + """ + message_links = DISCORD_MESSAGE_LINK_RE.findall(str(message.content)) + + if message_links: + embeds = [] + for message_link in message_links: + ctx = await message.bot.get_context(message) + embeds.append(await make_message_link_embed(ctx, message_link[0])) + + webhook_embed_list = list(grouper(embeds, 10)) + + return webhook_embed_list + + log.trace( + f"Skipping discord message link detection on {message.id}: message doesn't qualify." + ) + + class Incidents(Cog): """ Automation for the #incidents channel. @@ -365,6 +394,9 @@ class Incidents(Cog): This ensures that if there is a racing event awaiting the lock, it will fail to find the message, and will abort. There is a `timeout` to ensure that this doesn't hold the lock forever should something go wrong. + + Deletes cache value (`message_link_embeds_cache`) of `msg_before` if it exists and removes the + webhook message for that particular link from the channel. """ members_roles: t.Set[int] = {role.id for role in member.roles} if not members_roles & ALLOWED_ROLES: # Intersection is truthy on at least 1 common element @@ -415,18 +447,8 @@ class Incidents(Cog): else: log.trace("Deletion was confirmed") - log.trace("Deleting discord links webhook message.") - webhook_msg_ids = await self.message_link_embeds_cache.get(incident.id) - - if webhook_msg_ids: - webhook_msg_ids = webhook_msg_ids.split(",") - webhook = await self.bot.fetch_webhook(Webhooks.incidents) - - for x, msg in enumerate(webhook_msg_ids): - await webhook.delete_message(msg) - log.trace(f"Deleted discord links webhook message{x}/{len(webhook_msg_ids)}") - - log.trace("Successfully deleted discord links webhook message.") + # Deletes the message link embeds found in cache from the channel and cache. + await self.delete_msg_link_embeds(incident) async def resolve_message(self, message_id: int) -> t.Optional[discord.Message]: """ @@ -509,25 +531,53 @@ class Incidents(Cog): @Cog.listener() async def on_message(self, message: discord.Message) -> None: - """Pass `message` to `add_signals` if and only if it satisfies `is_incident`.""" + """ + If the message (`message`) is a incident then run it through `extract_message_links` + to get all the message link embeds (embeds which contain information about that particular + link), this message link embeds are then sent into the channel. + + Also passes the message into `add_signals` if the message is a incident. + """ if is_incident(message): - message_links = DISCORD_MESSAGE_LINK_RE.findall(str(message.content)) + webhook_embed_list = await extract_message_links(message) + webhook = await self.bot.fetch_webhook(Webhooks.incidents) + await self.send_webhooks(webhook_embed_list, message, webhook) - if message_links: - embeds = [] - for message_link in message_links: - ctx = await self.bot.get_context(message) - embeds.append(await make_message_link_embed(ctx, message_link[0])) + await add_signals(message) - webhook = await self.bot.fetch_webhook(Webhooks.incidents) - webhook_embed_list = list(grouper(embeds, 10)) + @Cog.listener() + async def on_message_edit(self, msg_before: discord.Message, msg_after: discord.Message) -> None: + """ + Deletes cache value (`message_link_embeds_cache`) of `msg_before` if it exists and removes the + webhook message for that particular link from the channel. - await self.send_webhooks(webhook_embed_list, message, webhook) + If the message edit (`msg_after`) is a incident then run it through `extract_message_links` + to get all the message link embeds (embeds which contain information about that particular + link), this message link embeds are then sent into the channel. - log.trace( - f"Skipping discord message link detection on {message.id}: message doesn't qualify." - ) - await add_signals(message) + The edited message is also passed into `add_signals` if it is a incident message. + """ + if is_incident(msg_before): + if msg_before.id in self.message_link_embeds_cache.items: + # Deletes the message link embeds found in cache from the channel and cache. + await self.delete_msg_link_embeds(msg_before) + + if is_incident(msg_after): + webhook_embed_list = await extract_message_links(msg_after) + webhook = await self.bot.fetch_webhook(Webhooks.incidents) + await self.send_webhooks(webhook_embed_list, msg_after, webhook) + + await add_signals(msg_after) + + @Cog.listener() + async def on_message_delete(self, message: discord.Message) -> None: + """ + Deletes the message link embeds found in cache from the channel and cache if the message + is a incident and is found in msg link embeds cache. + """ + if is_incident(message): + if message.id in self.message_link_embeds_cache.items: + await self.delete_msg_link_embeds(message) async def send_webhooks( self, @@ -571,6 +621,24 @@ class Incidents(Cog): return webhook_msg_ids + async def delete_msg_link_embeds(self, message: discord.Message) -> None: + """Delete discord message links message found in cache for `message`.""" + log.trace("Deleting discord links webhook message.") + + webhook_msg_ids = await self.message_link_embeds_cache.get(message.id) + + if webhook_msg_ids: + webhook_msg_ids = webhook_msg_ids.split(",") + webhook = await self.bot.fetch_webhook(Webhooks.incidents) + + for x, msg in enumerate(webhook_msg_ids): + await webhook.delete_message(msg) + log.trace(f"Deleted discord links webhook message{x}/{len(webhook_msg_ids)}") + + await self.message_link_embeds_cache.delete(message.id) + + log.trace("Successfully deleted discord links webhook message.") + def setup(bot: Bot) -> None: """Load the Incidents cog.""" -- cgit v1.2.3 From 5406c45a08ba0532b10cc6609f1f54a9f0e80e3d Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sat, 17 Apr 2021 08:48:33 +0530 Subject: Updates type hints for `message_link_embeds_cache`. --- bot/exts/moderation/incidents.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 77017659e..a5e2ef945 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -269,7 +269,13 @@ class Incidents(Cog): """ # This dictionary maps a incident message to the message link embeds(s) sent by it - # RedisCache[discord.Message.id, List[discord.Message.id]] + # + # Discord doesn't allow more than 10 embeds to be sent in a single webhook message + # hence the embeds need to be broken into groups of 10. Since we have multiple embeds + # and RedisCache doesn't allow storing lists, we need to join the list with commas to + # make it a string and then store it. + # + # RedisCache[discord.Message.id, str] message_link_embeds_cache = RedisCache() def __init__(self, bot: Bot) -> None: -- cgit v1.2.3 From 3ca726c0edd838647b99f5a16fe3f15956d59e64 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sat, 17 Apr 2021 17:34:31 +0530 Subject: Revert changes done by black. --- bot/exts/moderation/incidents.py | 56 ++++++++++------------------------------ 1 file changed, 13 insertions(+), 43 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index a5e2ef945..c988c45bb 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -73,11 +73,7 @@ async def download_file(attachment: discord.Attachment) -> t.Optional[discord.Fi log.exception("Failed to download attachment") -async def make_embed( - incident: discord.Message, - outcome: Signal, - actioned_by: discord.Member -) -> FileEmbed: +async def make_embed(incident: discord.Message, outcome: Signal, actioned_by: discord.Member) -> FileEmbed: """ Create an embed representation of `incident` for the #incidents-archive channel. @@ -115,13 +111,9 @@ async def make_embed( file = await download_file(attachment) if file is not None: - embed.set_image( - url=f"attachment://{attachment.filename}" - ) # Embed displays the attached file + embed.set_image(url=f"attachment://{attachment.filename}") # Embed displays the attached file else: - embed.set_author( - name="[Failed to relay attachment]", url=attachment.proxy_url - ) # Embed links the file + embed.set_author(name="[Failed to relay attachment]", url=attachment.proxy_url) # Embed links the file else: file = None @@ -191,9 +183,7 @@ async def add_signals(incident: discord.Message) -> None: existing_reacts = own_reactions(incident) for signal_emoji in Signal: - if ( - signal_emoji.value in existing_reacts - ): # This would not raise, but it is a superfluous API call + if signal_emoji.value in existing_reacts: # This would not raise, but it is a superfluous API call log.trace(f"Skipping emoji as it's already been placed: {signal_emoji}") else: log.trace(f"Adding reaction: {signal_emoji}") @@ -316,12 +306,7 @@ class Incidents(Cog): log.debug("Crawl task finished!") - async def archive( - self, - incident: discord.Message, - outcome: Signal, - actioned_by: discord.Member - ) -> bool: + async def archive(self, incident: discord.Message, outcome: Signal, actioned_by: discord.Member) -> bool: """ Relay an embed representation of `incident` to the #incidents-archive channel. @@ -342,9 +327,7 @@ class Incidents(Cog): not all information was relayed, return False. This signals that the original message is not safe to be deleted, as we will lose some information. """ - log.info( - f"Archiving incident: {incident.id} (outcome: {outcome}, actioned by: {actioned_by})" - ) + log.info(f"Archiving incident: {incident.id} (outcome: {outcome}, actioned by: {actioned_by})") embed, attachment_file = await make_embed(incident, outcome, actioned_by) try: @@ -356,9 +339,7 @@ class Incidents(Cog): file=attachment_file, ) except Exception: - log.exception( - f"Failed to archive incident {incident.id} to #incidents-archive" - ) + log.exception(f"Failed to archive incident {incident.id} to #incidents-archive") return False else: log.trace("Message archived successfully!") @@ -371,9 +352,7 @@ class Incidents(Cog): If `timeout` passes, this will raise `asyncio.TimeoutError`, signaling that we haven't been able to confirm that the message was deleted. """ - log.trace( - f"Confirmation task will wait {timeout=} seconds for {incident.id=} to be deleted" - ) + log.trace(f"Confirmation task will wait {timeout=} seconds for {incident.id=} to be deleted") def check(payload: discord.RawReactionActionEvent) -> bool: return payload.message_id == incident.id @@ -381,12 +360,7 @@ class Incidents(Cog): coroutine = self.bot.wait_for(event="raw_message_delete", check=check, timeout=timeout) return scheduling.create_task(coroutine, event_loop=self.bot.loop) - async def process_event( - self, - reaction: str, - incident: discord.Message, - member: discord.Member - ) -> None: + async def process_event(self, reaction: str, incident: discord.Message, member: discord.Member) -> None: """ Process a `reaction_add` event in #incidents. @@ -431,9 +405,7 @@ class Incidents(Cog): relay_successful = await self.archive(incident, signal, actioned_by=member) if not relay_successful: - log.trace( - "Original message will not be deleted as we failed to relay it to the archive" - ) + log.trace("Original message will not be deleted as we failed to relay it to the archive") return timeout = 5 # Seconds @@ -515,9 +487,7 @@ class Incidents(Cog): if payload.channel_id != Channels.incidents or payload.member.bot: return - log.trace( - f"Received reaction add event in #incidents, waiting for crawler: {self.crawl_task.done()=}" - ) + log.trace(f"Received reaction add event in #incidents, waiting for crawler: {self.crawl_task.done()=}") await self.crawl_task log.trace(f"Acquiring event lock: {self.event_lock.locked()=}") @@ -529,11 +499,11 @@ class Incidents(Cog): return if not is_incident(message): - log.debug("Ignoring event for a non-incident message.") + log.debug("Ignoring event for a non-incident message") return await self.process_event(str(payload.emoji), message, payload.member) - log.trace("Releasing event lock.") + log.trace("Releasing event lock") @Cog.listener() async def on_message(self, message: discord.Message) -> None: -- cgit v1.2.3 From 23578ae0381b0d8d81b7be9d8eb3bc86a1557e0b Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sun, 18 Apr 2021 06:19:06 +0530 Subject: Don't allow more than 10 embeds per report. If more than 10 embeds found, just get the first 10 and ignore the rest. --- bot/exts/moderation/incidents.py | 85 +++++++++++++++------------------------- 1 file changed, 32 insertions(+), 53 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index c988c45bb..032c15ca2 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -9,7 +9,6 @@ from enum import Enum import discord from async_rediscache import RedisCache from discord.ext.commands import Cog, Context, MessageConverter -from more_itertools.recipes import grouper from bot.bot import Bot from bot.constants import Channels, Colours, Emojis, Guild, Webhooks @@ -197,15 +196,15 @@ async def add_signals(incident: discord.Message) -> None: return -async def extract_message_links(message: discord.Message) -> t.Optional[list]: +async def extract_message_links(message: discord.Message, bot: Bot) -> t.Optional[list]: """ Checks if there's any message links in the text content. Then passes the the message_link into `make_message_link_embed` to format a embed for it containing information about the link. - As discord only allows a max of 10 embeds in a single webhook we need to - group the embeds into group of 10 and then return the list. + As discord only allows a max of 10 embeds in a single webhook, just send the + first 10 embeds and don't care about the rest. If no links are found for the message, it logs a trace statement. """ @@ -214,12 +213,10 @@ async def extract_message_links(message: discord.Message) -> t.Optional[list]: if message_links: embeds = [] for message_link in message_links: - ctx = await message.bot.get_context(message) + ctx = await bot.get_context(message) embeds.append(await make_message_link_embed(ctx, message_link[0])) - webhook_embed_list = list(grouper(embeds, 10)) - - return webhook_embed_list + return embeds[:10] log.trace( f"Skipping discord message link detection on {message.id}: message doesn't qualify." @@ -240,6 +237,7 @@ class Incidents(Cog): * See: `crawl_incidents` On message: + * Run message through `extract_message_links` and send them into the channel * Add `Signal` member emoji if message qualifies as an incident * Ignore messages starting with # * Use this if verbal communication is necessary @@ -253,18 +251,13 @@ class Incidents(Cog): * If `Signal.ACTIONED` or `Signal.NOT_ACTIONED` were chosen, attempt to relay the incident message to #incidents-archive * If relay successful, delete original message + * Search the cache for the webhook message for this message, if found delete it. * See: `on_raw_reaction_add` Please refer to function docstrings for implementation details. """ - # This dictionary maps a incident message to the message link embeds(s) sent by it - # - # Discord doesn't allow more than 10 embeds to be sent in a single webhook message - # hence the embeds need to be broken into groups of 10. Since we have multiple embeds - # and RedisCache doesn't allow storing lists, we need to join the list with commas to - # make it a string and then store it. - # + # This dictionary maps a incident message to the message link embeds sent by it # RedisCache[discord.Message.id, str] message_link_embeds_cache = RedisCache() @@ -426,7 +419,7 @@ class Incidents(Cog): log.trace("Deletion was confirmed") # Deletes the message link embeds found in cache from the channel and cache. - await self.delete_msg_link_embeds(incident) + await self.delete_msg_link_embed(incident) async def resolve_message(self, message_id: int) -> t.Optional[discord.Message]: """ @@ -515,7 +508,7 @@ class Incidents(Cog): Also passes the message into `add_signals` if the message is a incident. """ if is_incident(message): - webhook_embed_list = await extract_message_links(message) + webhook_embed_list = await extract_message_links(message, self.bot) webhook = await self.bot.fetch_webhook(Webhooks.incidents) await self.send_webhooks(webhook_embed_list, message, webhook) @@ -535,11 +528,11 @@ class Incidents(Cog): """ if is_incident(msg_before): if msg_before.id in self.message_link_embeds_cache.items: - # Deletes the message link embeds found in cache from the channel and cache. - await self.delete_msg_link_embeds(msg_before) + # Deletes the message link embed found in cache from the channel and cache. + await self.delete_msg_link_embed(msg_before) if is_incident(msg_after): - webhook_embed_list = await extract_message_links(msg_after) + webhook_embed_list = await extract_message_links(msg_after, self.bot) webhook = await self.bot.fetch_webhook(Webhooks.incidents) await self.send_webhooks(webhook_embed_list, msg_after, webhook) @@ -548,19 +541,19 @@ class Incidents(Cog): @Cog.listener() async def on_message_delete(self, message: discord.Message) -> None: """ - Deletes the message link embeds found in cache from the channel and cache if the message + Deletes the message link embed found in cache from the channel and cache if the message is a incident and is found in msg link embeds cache. """ if is_incident(message): if message.id in self.message_link_embeds_cache.items: - await self.delete_msg_link_embeds(message) + await self.delete_msg_link_embed(message) async def send_webhooks( self, webhook_embed_list: t.List, message: discord.Message, webhook: discord.Webhook, - ) -> t.List[int]: + ) -> t.Optional[int]: """ Send Message Link Embeds to #incidents channel. @@ -570,49 +563,35 @@ class Incidents(Cog): After sending each webhook it maps the `message.id` to the `webhook_msg_ids` IDs in the async redis-cache. """ - webhook_msg_ids = [] try: - for x, embed in enumerate(webhook_embed_list): - webhook_msg = await webhook.send( - embeds=[x for x in embed if x is not None], - username=sub_clyde(message.author.name), - avatar_url=message.author.avatar_url, - wait=True, - ) - webhook_msg_ids.append(webhook_msg.id) - log.trace( - f"Message Link Embed {x + 1}/{len(webhook_embed_list)} sent successfully." - ) + webhook_msg = await webhook.send( + embeds=[x for x in webhook_embed_list if x is not None], + username=sub_clyde(message.author.name), + avatar_url=message.author.avatar_url, + wait=True, + ) + log.trace(f"Message Link Embed sent successfully.") except discord.DiscordException: log.exception( - f"Failed to send message link embeds {message.id} to #incidents." + f"Failed to send message link embed {message.id} to #incidents." ) else: - await self.message_link_embeds_cache.set( - message.id, ",".join(map(str, webhook_msg_ids)) - ) - log.trace("Message Link Embeds Sent successfully!") - - return webhook_msg_ids + await self.message_link_embeds_cache.set(message.id, webhook_msg.id) + log.trace("Message Link Embed Sent successfully!") + return webhook_msg.id - async def delete_msg_link_embeds(self, message: discord.Message) -> None: - """Delete discord message links message found in cache for `message`.""" + async def delete_msg_link_embed(self, message: discord.Message) -> None: + """Delete discord message link message found in cache for `message`.""" log.trace("Deleting discord links webhook message.") + webhook_msg_id = await self.message_link_embeds_cache.get(message.id) - webhook_msg_ids = await self.message_link_embeds_cache.get(message.id) - - if webhook_msg_ids: - webhook_msg_ids = webhook_msg_ids.split(",") + if webhook_msg_id: webhook = await self.bot.fetch_webhook(Webhooks.incidents) - - for x, msg in enumerate(webhook_msg_ids): - await webhook.delete_message(msg) - log.trace(f"Deleted discord links webhook message{x}/{len(webhook_msg_ids)}") + await webhook.delete_message(webhook_msg_id) await self.message_link_embeds_cache.delete(message.id) - log.trace("Successfully deleted discord links webhook message.") -- cgit v1.2.3 From 691a63c8dcfee89f2cf8e5d2c9456b84789dfc9a Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sun, 18 Apr 2021 06:22:00 +0530 Subject: Use str() rather than f string for single variable. Makes the intent much more clear. --- bot/exts/moderation/incidents.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 032c15ca2..df8d08509 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -156,7 +156,7 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> discord.Em message = await message_convert_object.convert(ctx, message_link) except discord.DiscordException as e: - embed.title = f"{e}" + embed.title = str(e) embed.colour = Colours.soft_red else: -- cgit v1.2.3 From 00175a55784603c6030e83f2099e7e7daba02654 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sun, 18 Apr 2021 06:27:02 +0530 Subject: Make incidents channel webhook a cog level attribute This would not fetch it everytime. --- bot/exts/moderation/incidents.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index df8d08509..a2548daca 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -265,6 +265,9 @@ class Incidents(Cog): """Prepare `event_lock` and schedule `crawl_task` on start-up.""" self.bot = bot + # Webhook to send message link embeds in #incidents + self.incidents_webhook = await self.bot.fetch_webhook(Webhooks.incidents) + self.event_lock = asyncio.Lock() self.crawl_task = scheduling.create_task(self.crawl_incidents(), event_loop=self.bot.loop) @@ -509,8 +512,7 @@ class Incidents(Cog): """ if is_incident(message): webhook_embed_list = await extract_message_links(message, self.bot) - webhook = await self.bot.fetch_webhook(Webhooks.incidents) - await self.send_webhooks(webhook_embed_list, message, webhook) + await self.send_webhooks(webhook_embed_list, message, self.incidents_webhook) await add_signals(message) @@ -533,8 +535,7 @@ class Incidents(Cog): if is_incident(msg_after): webhook_embed_list = await extract_message_links(msg_after, self.bot) - webhook = await self.bot.fetch_webhook(Webhooks.incidents) - await self.send_webhooks(webhook_embed_list, msg_after, webhook) + await self.send_webhooks(webhook_embed_list, msg_after, self.incidents_webhook) await add_signals(msg_after) @@ -588,8 +589,7 @@ class Incidents(Cog): webhook_msg_id = await self.message_link_embeds_cache.get(message.id) if webhook_msg_id: - webhook = await self.bot.fetch_webhook(Webhooks.incidents) - await webhook.delete_message(webhook_msg_id) + await self.incidents_webhook.delete_message(webhook_msg_id) await self.message_link_embeds_cache.delete(message.id) log.trace("Successfully deleted discord links webhook message.") -- cgit v1.2.3 From 593e5fe3172ac36de1f4875ce1eb734734a15d70 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sun, 18 Apr 2021 06:53:07 +0530 Subject: On msg edits, edit the msg link embed rather than deleting it --- bot/exts/moderation/incidents.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index a2548daca..f6607e651 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -528,10 +528,15 @@ class Incidents(Cog): The edited message is also passed into `add_signals` if it is a incident message. """ - if is_incident(msg_before): - if msg_before.id in self.message_link_embeds_cache.items: - # Deletes the message link embed found in cache from the channel and cache. - await self.delete_msg_link_embed(msg_before) + + webhook_embed_list = await extract_message_links(msg_after, self.bot) + webhook_msg_id = self.message_link_embeds_cache.get(msg_before.id) + + if webhook_msg_id: + await self.incidents_webhook.edit_message( + message_id=webhook_msg_id, + embeds=[x for x in webhook_embed_list if x is not None], + ) if is_incident(msg_after): webhook_embed_list = await extract_message_links(msg_after, self.bot) -- cgit v1.2.3 From 91ffa412294a8bf63da132df19557fec54b02a00 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sun, 18 Apr 2021 06:54:24 +0530 Subject: Use tasks to fetch incidents channel webhook. --- bot/exts/moderation/incidents.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index f6607e651..deaabcfa0 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -123,9 +123,9 @@ def is_incident(message: discord.Message) -> bool: """True if `message` qualifies as an incident, False otherwise.""" conditions = ( message.channel.id == Channels.incidents, # Message sent in #incidents - not message.author.bot, # Not by a bot - not message.content.startswith("#"), # Doesn't start with a hash - not message.pinned, # And isn't header + not message.author.bot, # Not by a bot + not message.content.startswith("#"), # Doesn't start with a hash + not message.pinned, # And isn't header ) return all(conditions) @@ -265,12 +265,16 @@ class Incidents(Cog): """Prepare `event_lock` and schedule `crawl_task` on start-up.""" self.bot = bot - # Webhook to send message link embeds in #incidents - self.incidents_webhook = await self.bot.fetch_webhook(Webhooks.incidents) + self.bot.loop.create_task(self.get_webhook()) self.event_lock = asyncio.Lock() self.crawl_task = scheduling.create_task(self.crawl_incidents(), event_loop=self.bot.loop) + async def get_webhook(self) -> None: + """Fetch and store message link embeds webhook, present in #incidents channel.""" + await self.bot.wait_until_guild_available() + self.incidents_webhook = await self.bot.fetch_webhook(Webhooks.incidents) + async def crawl_incidents(self) -> None: """ Crawl #incidents and add missing emoji where necessary. @@ -555,10 +559,10 @@ class Incidents(Cog): await self.delete_msg_link_embed(message) async def send_webhooks( - self, - webhook_embed_list: t.List, - message: discord.Message, - webhook: discord.Webhook, + self, + webhook_embed_list: t.List, + message: discord.Message, + webhook: discord.Webhook, ) -> t.Optional[int]: """ Send Message Link Embeds to #incidents channel. -- cgit v1.2.3 From a9ac92b19d6b4f562383e9eeab09eec8ef063d44 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sun, 18 Apr 2021 07:02:19 +0530 Subject: Do required flake8 changes in docstrings. --- bot/exts/moderation/incidents.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index deaabcfa0..7ef7eb327 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -508,6 +508,8 @@ class Incidents(Cog): @Cog.listener() async def on_message(self, message: discord.Message) -> None: """ + Pass `message` to `add_signals` and `extract_message_links` if it satisfies `is_incident`. + If the message (`message`) is a incident then run it through `extract_message_links` to get all the message link embeds (embeds which contain information about that particular link), this message link embeds are then sent into the channel. @@ -523,6 +525,8 @@ class Incidents(Cog): @Cog.listener() async def on_message_edit(self, msg_before: discord.Message, msg_after: discord.Message) -> None: """ + Pass `msg_after` to `extract_message_links` and edit `msg_before` webhook msg. + Deletes cache value (`message_link_embeds_cache`) of `msg_before` if it exists and removes the webhook message for that particular link from the channel. @@ -532,7 +536,6 @@ class Incidents(Cog): The edited message is also passed into `add_signals` if it is a incident message. """ - webhook_embed_list = await extract_message_links(msg_after, self.bot) webhook_msg_id = self.message_link_embeds_cache.get(msg_before.id) @@ -551,8 +554,9 @@ class Incidents(Cog): @Cog.listener() async def on_message_delete(self, message: discord.Message) -> None: """ - Deletes the message link embed found in cache from the channel and cache if the message - is a incident and is found in msg link embeds cache. + Delete message link embeds for `message`. + + Search through the cache for message, if found delete it from cache and channel. """ if is_incident(message): if message.id in self.message_link_embeds_cache.items: @@ -580,7 +584,7 @@ class Incidents(Cog): avatar_url=message.author.avatar_url, wait=True, ) - log.trace(f"Message Link Embed sent successfully.") + log.trace("Message Link Embed sent successfully.") except discord.DiscordException: log.exception( -- cgit v1.2.3 From 0f92fe11ffc8c6262c62bd7e9d0c4c81bd8da6f5 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Mon, 19 Apr 2021 05:40:45 +0530 Subject: Don't send errors, instead log them. Errors shouldn't be sent in #incidents. Instead, log them with log.exception and make the function return. --- bot/exts/moderation/incidents.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 7ef7eb327..a259db10d 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -123,9 +123,9 @@ def is_incident(message: discord.Message) -> bool: """True if `message` qualifies as an incident, False otherwise.""" conditions = ( message.channel.id == Channels.incidents, # Message sent in #incidents - not message.author.bot, # Not by a bot - not message.content.startswith("#"), # Doesn't start with a hash - not message.pinned, # And isn't header + not message.author.bot, # Not by a bot + not message.content.startswith("#"), # Doesn't start with a hash + not message.pinned, # And isn't header ) return all(conditions) @@ -140,7 +140,7 @@ def has_signals(message: discord.Message) -> bool: return ALL_SIGNALS.issubset(own_reactions(message)) -async def make_message_link_embed(ctx: Context, message_link: str) -> discord.Embed: +async def make_message_link_embed(ctx: Context, message_link: str) -> t.Optional[discord.Embed]: """ Create an embedded representation of the discord message link contained in the incident report. @@ -156,8 +156,7 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> discord.Em message = await message_convert_object.convert(ctx, message_link) except discord.DiscordException as e: - embed.title = str(e) - embed.colour = Colours.soft_red + log.exception(f"Failed to make message link embed for '{message_link}', raised exception: {e}") else: text = message.content @@ -169,8 +168,7 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> discord.Em f"**Content:** {textwrap.shorten(text, 300, placeholder='...')}\n" "\n" ) - - return embed + return embed async def add_signals(incident: discord.Message) -> None: @@ -214,7 +212,9 @@ async def extract_message_links(message: discord.Message, bot: Bot) -> t.Optiona embeds = [] for message_link in message_links: ctx = await bot.get_context(message) - embeds.append(await make_message_link_embed(ctx, message_link[0])) + embed = await make_message_link_embed(ctx, message_link[0]) + if embed: + embeds.append(embed) return embeds[:10] -- cgit v1.2.3 From 138dc2e5039fce0b267c9d47db6e387a832d3df0 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Mon, 19 Apr 2021 06:03:53 +0530 Subject: Bug fixes - `await` message link embeds cache get - don't double send webhook embeds (edit, send) on message edits --- bot/exts/moderation/incidents.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index a259db10d..413c9bcf9 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -517,11 +517,12 @@ class Incidents(Cog): Also passes the message into `add_signals` if the message is a incident. """ if is_incident(message): - webhook_embed_list = await extract_message_links(message, self.bot) - await self.send_webhooks(webhook_embed_list, message, self.incidents_webhook) - await add_signals(message) + webhook_embed_list = await extract_message_links(message, self.bot) + if webhook_embed_list: + await self.send_webhooks(webhook_embed_list, message, self.incidents_webhook) + @Cog.listener() async def on_message_edit(self, msg_before: discord.Message, msg_after: discord.Message) -> None: """ @@ -536,20 +537,18 @@ class Incidents(Cog): The edited message is also passed into `add_signals` if it is a incident message. """ - webhook_embed_list = await extract_message_links(msg_after, self.bot) - webhook_msg_id = self.message_link_embeds_cache.get(msg_before.id) - - if webhook_msg_id: - await self.incidents_webhook.edit_message( - message_id=webhook_msg_id, - embeds=[x for x in webhook_embed_list if x is not None], - ) - if is_incident(msg_after): webhook_embed_list = await extract_message_links(msg_after, self.bot) - await self.send_webhooks(webhook_embed_list, msg_after, self.incidents_webhook) + webhook_msg_id = await self.message_link_embeds_cache.get(msg_before.id) - await add_signals(msg_after) + if webhook_msg_id: + await self.incidents_webhook.edit_message( + message_id=webhook_msg_id, + embeds=[x for x in webhook_embed_list if x is not None], + ) + return + + await self.send_webhooks(webhook_embed_list, msg_after, self.incidents_webhook) @Cog.listener() async def on_message_delete(self, message: discord.Message) -> None: @@ -559,8 +558,7 @@ class Incidents(Cog): Search through the cache for message, if found delete it from cache and channel. """ if is_incident(message): - if message.id in self.message_link_embeds_cache.items: - await self.delete_msg_link_embed(message) + await self.delete_msg_link_embed(message) async def send_webhooks( self, -- cgit v1.2.3 From b116d3c47d9c5c8e99b2557b37d0e402652b5ef3 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Wed, 21 Apr 2021 15:09:29 +0530 Subject: Rework message link embed. - Instead of default black colour, use gold to give it some shine! - Mention the channel also in the channel field. - Add message ID in footer, so it is easy to figure out for which message link is that embed. --- bot/exts/moderation/incidents.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 413c9bcf9..aebf22d00 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -149,8 +149,6 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> t.Optional Channel: Special/#bot-commands (814190307980607493) Content: This is a very important message! """ - embed = discord.Embed() - try: message_convert_object = MessageConverter() message = await message_convert_object.convert(ctx, message_link) @@ -162,12 +160,16 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> t.Optional text = message.content channel = message.channel - embed.description = ( - f"**Author:** {format_user(message.author)}\n" - f"**Channel:** {channel.category}/#{channel.name} (`{channel.id}`)\n" - f"**Content:** {textwrap.shorten(text, 300, placeholder='...')}\n" - "\n" + embed = discord.Embed( + colour=discord.Colour.gold(), + description=( + f"**Author:** {format_user(message.author)}\n" + f"**Channel:** <#{channel.id}> ({channel.category}/#{channel.name})\n" + f"**Content:** {textwrap.shorten(text, 300, placeholder='...')}\n" + ) ) + embed.set_footer(text=f"Message ID: {message.id}") + return embed -- cgit v1.2.3 From 5f57103b9dea3af864c916a24a8ffcc61d0106dc Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Mon, 26 Apr 2021 08:08:57 +0530 Subject: Remove redundant code --- bot/exts/moderation/incidents.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index aebf22d00..24cd21406 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -150,8 +150,7 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> t.Optional Content: This is a very important message! """ try: - message_convert_object = MessageConverter() - message = await message_convert_object.convert(ctx, message_link) + message = await MessageConverter().convert(ctx, message_link) except discord.DiscordException as e: log.exception(f"Failed to make message link embed for '{message_link}', raised exception: {e}") @@ -164,7 +163,7 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> t.Optional colour=discord.Colour.gold(), description=( f"**Author:** {format_user(message.author)}\n" - f"**Channel:** <#{channel.id}> ({channel.category}/#{channel.name})\n" + f"**Channel:** {channel.mention} ({channel.category}/#{channel.name})\n" f"**Content:** {textwrap.shorten(text, 300, placeholder='...')}\n" ) ) @@ -584,7 +583,6 @@ class Incidents(Cog): avatar_url=message.author.avatar_url, wait=True, ) - log.trace("Message Link Embed sent successfully.") except discord.DiscordException: log.exception( -- cgit v1.2.3 From e95b139593b9014638c187402343141967aba765 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Mon, 26 Apr 2021 08:13:19 +0530 Subject: Appy requested grammar changes. Co-authored-by: Boris Muratov <8bee278@gmail.com> --- bot/exts/moderation/incidents.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 24cd21406..b174ce668 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -258,8 +258,8 @@ class Incidents(Cog): Please refer to function docstrings for implementation details. """ - # This dictionary maps a incident message to the message link embeds sent by it - # RedisCache[discord.Message.id, str] + # This dictionary maps an incident report message to the message link embed's ID + # RedisCache[discord.Message.id, discord.Message.id] message_link_embeds_cache = RedisCache() def __init__(self, bot: Bot) -> None: @@ -511,11 +511,11 @@ class Incidents(Cog): """ Pass `message` to `add_signals` and `extract_message_links` if it satisfies `is_incident`. - If the message (`message`) is a incident then run it through `extract_message_links` + If the message (`message`) is an incident report, then run it through `extract_message_links` to get all the message link embeds (embeds which contain information about that particular - link), this message link embeds are then sent into the channel. + link).These message link embeds are then sent into the channel. - Also passes the message into `add_signals` if the message is a incident. + Also passes the message into `add_signals` if the message is an incident. """ if is_incident(message): await add_signals(message) -- cgit v1.2.3 From 1fa3ce5acc98dd3bea77881dfea1fdc0001feccb Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Mon, 26 Apr 2021 08:15:30 +0530 Subject: Rename 'send_webhooks' to 'send_message_link_embed' Co-authored-by: Boris Muratov <8bee278@gmail.com> --- bot/exts/moderation/incidents.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index b174ce668..da349f654 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -522,7 +522,7 @@ class Incidents(Cog): webhook_embed_list = await extract_message_links(message, self.bot) if webhook_embed_list: - await self.send_webhooks(webhook_embed_list, message, self.incidents_webhook) + await self.send_message_link_embeds(webhook_embed_list, message, self.incidents_webhook) @Cog.listener() async def on_message_edit(self, msg_before: discord.Message, msg_after: discord.Message) -> None: @@ -549,7 +549,7 @@ class Incidents(Cog): ) return - await self.send_webhooks(webhook_embed_list, msg_after, self.incidents_webhook) + await self.send_message_link_embeds(webhook_embed_list, msg_after, self.incidents_webhook) @Cog.listener() async def on_message_delete(self, message: discord.Message) -> None: @@ -561,7 +561,7 @@ class Incidents(Cog): if is_incident(message): await self.delete_msg_link_embed(message) - async def send_webhooks( + async def send_message_link_embeds( self, webhook_embed_list: t.List, message: discord.Message, -- cgit v1.2.3 From b0c4cdbd3328e46f5a1d6dd6be3600e20e7f19aa Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Mon, 26 Apr 2021 08:17:39 +0530 Subject: Remove leading whitespace from msg link embed content --- bot/exts/moderation/incidents.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index da349f654..9d3b0fe6f 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -164,7 +164,7 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> t.Optional description=( f"**Author:** {format_user(message.author)}\n" f"**Channel:** {channel.mention} ({channel.category}/#{channel.name})\n" - f"**Content:** {textwrap.shorten(text, 300, placeholder='...')}\n" + f"**Content:** {textwrap.shorten(text.lstrip(), 300, placeholder='...')}\n" ) ) embed.set_footer(text=f"Message ID: {message.id}") -- cgit v1.2.3 From f74e894d3ba2d26130e31ba13a8a7ced2b63af4e Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Mon, 26 Apr 2021 08:20:24 +0530 Subject: Handle discord.errors.NotFound while deleting msg link webhook embeds --- bot/exts/moderation/incidents.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 9d3b0fe6f..840327cb6 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -600,7 +600,10 @@ class Incidents(Cog): webhook_msg_id = await self.message_link_embeds_cache.get(message.id) if webhook_msg_id: - await self.incidents_webhook.delete_message(webhook_msg_id) + try: + await self.incidents_webhook.delete_message(webhook_msg_id) + except discord.errors.NotFound: + log.trace(f"Incidents message link embed (`{webhook_msg_id}`) has already been deleted, skipping.") await self.message_link_embeds_cache.delete(message.id) log.trace("Successfully deleted discord links webhook message.") -- cgit v1.2.3 From 5ce51115f19f7ce13802701dc58d508ab5eb69f8 Mon Sep 17 00:00:00 2001 From: Shivansh Date: Tue, 27 Apr 2021 06:27:16 +0530 Subject: Fix truncation bug When you take a long message, just one word of 400 A's then the truncated wouldn't be able to handle it properly and just return the placeholder. This is a bug in the textwrap.shorten function. To solve this, I went the long way to use slicing on the list. This commit seems to have resolved the bug. --- bot/exts/moderation/incidents.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 840327cb6..235f7a0f7 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -156,15 +156,16 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> t.Optional log.exception(f"Failed to make message link embed for '{message_link}', raised exception: {e}") else: - text = message.content + text = message.content.lstrip() channel = message.channel - + shortened_text = text[:300] + (text[300:] and '...') + embed = discord.Embed( colour=discord.Colour.gold(), description=( f"**Author:** {format_user(message.author)}\n" f"**Channel:** {channel.mention} ({channel.category}/#{channel.name})\n" - f"**Content:** {textwrap.shorten(text.lstrip(), 300, placeholder='...')}\n" + f"**Content:** {shortened_text}\n" ) ) embed.set_footer(text=f"Message ID: {message.id}") -- cgit v1.2.3 From a068ce561024ddb60677e6b6d6887102567dcf2e Mon Sep 17 00:00:00 2001 From: Shivansh Date: Sat, 1 May 2021 07:33:24 +0530 Subject: Write tests for this feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In short, I have written two tests, one which tests the whether `extract_message_links` is called on message edits or not. And the second one to test the regex of `extract_message_links` and assert the message link embeds sent by it. Special thanks to kwzrd💜#1198 for helping me out with it. --- bot/exts/moderation/incidents.py | 5 +-- tests/bot/exts/moderation/test_incidents.py | 64 +++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 235f7a0f7..a71cea45f 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -1,7 +1,6 @@ import asyncio import logging import re -import textwrap import typing as t from datetime import datetime from enum import Enum @@ -159,7 +158,7 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> t.Optional text = message.content.lstrip() channel = message.channel shortened_text = text[:300] + (text[300:] and '...') - + embed = discord.Embed( colour=discord.Colour.gold(), description=( @@ -591,7 +590,7 @@ class Incidents(Cog): ) else: - await self.message_link_embeds_cache.set(message.id, webhook_msg.id) + await self.message_link_embeds_cache.set(int(message.id), int(webhook_msg.id)) log.trace("Message Link Embed Sent successfully!") return webhook_msg.id diff --git a/tests/bot/exts/moderation/test_incidents.py b/tests/bot/exts/moderation/test_incidents.py index c015951b3..4b2b652fc 100644 --- a/tests/bot/exts/moderation/test_incidents.py +++ b/tests/bot/exts/moderation/test_incidents.py @@ -3,6 +3,7 @@ import enum import logging import typing as t import unittest +from unittest import mock from unittest.mock import AsyncMock, MagicMock, call, patch import aiohttp @@ -11,6 +12,8 @@ from async_rediscache import RedisSession from bot.constants import Colours from bot.exts.moderation import incidents +from bot.exts.moderation.incidents import extract_message_links +from bot.utils.messages import format_user from tests.helpers import ( MockAsyncWebhook, MockAttachment, @@ -785,3 +788,64 @@ class TestOnMessage(TestIncidents): await self.cog_instance.on_message(MockMessage()) mock_add_signals.assert_not_called() + + +class TestMessageLinkEmbeds(TestIncidents): + """Tests for `extract_message_links` coroutine.""" + + async def extract_and_form_message_link_embeds(self): + """ + Extract message links from a mocked message and form the message link embed. + + Considers all types of message links, discord supports. + """ + self.guild_id_patcher = mock.patch("bot.exts.backend.sync._cog.constants.Guild.id", 5) + self.guild_id = self.guild_id_patcher.start() + + msg = MockMessage(id=555, content="Hello, World!" * 3000) + msg.channel.mention = "#lemonade-stand" + + msg_links = [ + # Valid Message links + f"https://discord.com/channels/{self.guild_id}/{msg.channel.discord_id}/{msg.discord_id}", + f"http://canary.discord.com/channels/{self.guild_id}/{msg.channel.discord_id}/{msg.discord_id}", + + # Invalid Message links + f"https://discord.com/channels/{msg.channel.discord_id}/{msg.discord_id}", + f"https://discord.com/channels/{self.guild_id}/{msg.channel.discord_id}000/{msg.discord_id}", + ] + + incident_msg = MockMessage( + id=777, + content=f"I would like to report the following messages, " + f"as they break our rules: \n{', '.join(msg_links)}" + ) + + embeds = await extract_message_links(incident_msg, self.cog_instance.bot) + description = ( + f"**Author:** {format_user(msg.author)}\n" + f"**Channel:** {msg.channel.mention} ({msg.channel.category}/#{msg.channel.name})\n" + f"**Content:** {('Hello, World!' * 3000)[:300] + '...'}\n" + ) + + # Check number of embeds returned with number of valid links + self.assertEqual( + self, len(embeds), 2 + ) + + # Check for the embed descriptions + for embed in embeds: + self.assertEqual( + self, embed.description, description + ) + + @patch("bot.exts.moderation.incidents.is_incident", MagicMock(return_value=True)) + async def test_incident_message_edit(self): + """Edit the incident message and check whether `extract_message_links` is called or not.""" + self.cog_instance.incidents_webhook = MockAsyncWebhook() # Patch in our webhook + + edited_msg = MockMessage(id=123) + with patch("bot.exts.moderation.incidents.extract_message_links", AsyncMock()) as mock_extract_message_links: + await self.cog_instance.on_message_edit(MockMessage(id=123), edited_msg) + + mock_extract_message_links.assert_awaited_once() -- cgit v1.2.3 From c8a8cec90b376ea5e2a191957b82bab8d519ff00 Mon Sep 17 00:00:00 2001 From: Shivansh Date: Mon, 3 May 2021 10:13:41 +0530 Subject: Rework text shortner. Explanation: It is unnecessary to show 300 characters, when there is only one word which is so long, so if there is only one word in the text, it would be truncated to 50 words. Also in some cases, there are messages of many lines with 1 word on each line(say), this would again make the embed big and polluting, so it would limit the number of lines to a maximum of 3. Rest of the feature is the same as before. This implementation has been inspired from the `format_output` function of snekbox cog. --- bot/exts/moderation/incidents.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index a71cea45f..18c229644 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -139,6 +139,23 @@ def has_signals(message: discord.Message) -> bool: return ALL_SIGNALS.issubset(own_reactions(message)) +async def shorten_text(text: str) -> str: + lines = text.count("\n") + if lines > 3: + text = "\n".join(line for line in text.split('\n')[:3]) + if len(text) >= 300: + text = f"{text[:300]}\n... (truncated - too long, too many lines)" + else: + text = f"{text}\n... (truncated - too many lines)" + elif len(text) >= 300: + if text.count(" ") < 1: + text = f"{text[:50]}\n... (truncated - single word)" + else: + text = f"{text[:300]}\n... (truncated - too long)" + + return text + + async def make_message_link_embed(ctx: Context, message_link: str) -> t.Optional[discord.Embed]: """ Create an embedded representation of the discord message link contained in the incident report. @@ -149,22 +166,20 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> t.Optional Content: This is a very important message! """ try: - message = await MessageConverter().convert(ctx, message_link) + message: discord.Message = await MessageConverter().convert(ctx, message_link) except discord.DiscordException as e: log.exception(f"Failed to make message link embed for '{message_link}', raised exception: {e}") else: - text = message.content.lstrip() channel = message.channel - shortened_text = text[:300] + (text[300:] and '...') embed = discord.Embed( colour=discord.Colour.gold(), description=( f"**Author:** {format_user(message.author)}\n" f"**Channel:** {channel.mention} ({channel.category}/#{channel.name})\n" - f"**Content:** {shortened_text}\n" + f"**Content:** {await shorten_text(message.content)}\n" ) ) embed.set_footer(text=f"Message ID: {message.id}") -- cgit v1.2.3 From 95d14d30a29aeeb2ced0a90e6e01cb9fd0ad4f6e Mon Sep 17 00:00:00 2001 From: Shivansh Date: Thu, 6 May 2021 09:22:05 +0530 Subject: (incidents): Refactor text shortner --- bot/exts/moderation/incidents.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 18c229644..09712f5a0 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -140,18 +140,21 @@ def has_signals(message: discord.Message) -> bool: async def shorten_text(text: str) -> str: + """Truncate the text if there are over 3 lines or 300 characters, or if it is a single word.""" + original_length = len(text) lines = text.count("\n") + # Limit to a maximum of three lines if lines > 3: text = "\n".join(line for line in text.split('\n')[:3]) - if len(text) >= 300: - text = f"{text[:300]}\n... (truncated - too long, too many lines)" - else: - text = f"{text}\n... (truncated - too many lines)" - elif len(text) >= 300: - if text.count(" ") < 1: - text = f"{text[:50]}\n... (truncated - single word)" - else: - text = f"{text[:300]}\n... (truncated - too long)" + # If it is a single word, then truncate it to 50 characters + if text.count(" ") < 1: + text = text[:50] + # Truncate text to a maximum of 300 characters + if len(text) > 300: + text = text[:300] + # Add placeholder if the text was shortened + if len(text) < original_length: + text += "..." return text @@ -179,9 +182,12 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> t.Optional description=( f"**Author:** {format_user(message.author)}\n" f"**Channel:** {channel.mention} ({channel.category}/#{channel.name})\n" - f"**Content:** {await shorten_text(message.content)}\n" ) ) + embed.add_field( + name="Content", + value=await shorten_text(message.content) + ) embed.set_footer(text=f"Message ID: {message.id}") return embed -- cgit v1.2.3 From 86988ac67ceaf6fb6fb5cfada0d964fef4b591e3 Mon Sep 17 00:00:00 2001 From: Shivansh Date: Fri, 7 May 2021 09:36:49 +0530 Subject: (incidents): Add test for text shortner Pass all 3 cases of text shortening to the test case and test them, the cases being: i. If the message is just one word, then shorten to 50 characters. ii. Maximum lines being 3. iii. Maximum characters being 300. This commit also removes a misc bug, of passing self, while asserting equal. --- bot/exts/moderation/incidents.py | 4 ++-- tests/bot/exts/moderation/test_incidents.py | 24 ++++++++++++++++-------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 09712f5a0..22b50625a 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -139,7 +139,7 @@ def has_signals(message: discord.Message) -> bool: return ALL_SIGNALS.issubset(own_reactions(message)) -async def shorten_text(text: str) -> str: +def shorten_text(text: str) -> str: """Truncate the text if there are over 3 lines or 300 characters, or if it is a single word.""" original_length = len(text) lines = text.count("\n") @@ -186,7 +186,7 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> t.Optional ) embed.add_field( name="Content", - value=await shorten_text(message.content) + value=shorten_text(message.content) ) embed.set_footer(text=f"Message ID: {message.id}") diff --git a/tests/bot/exts/moderation/test_incidents.py b/tests/bot/exts/moderation/test_incidents.py index 4b2b652fc..3c5d8f47d 100644 --- a/tests/bot/exts/moderation/test_incidents.py +++ b/tests/bot/exts/moderation/test_incidents.py @@ -12,7 +12,6 @@ from async_rediscache import RedisSession from bot.constants import Colours from bot.exts.moderation import incidents -from bot.exts.moderation.incidents import extract_message_links from bot.utils.messages import format_user from tests.helpers import ( MockAsyncWebhook, @@ -793,6 +792,19 @@ class TestOnMessage(TestIncidents): class TestMessageLinkEmbeds(TestIncidents): """Tests for `extract_message_links` coroutine.""" + async def test_shorten_text(self): + """Test all cases of text shortening by mocking messages.""" + tests = { + "thisisasingleword"*10: ('thisisasingleword'*10)[:50]+"...", + "\n".join("Lets make a new line test".split()): "Lets\nmake\na"+"...", + 'Hello, World!' * 300: ('Hello, World!' * 300)[:300] + '...' + } + + for test, value in tests.items(): + self.assertEqual( + str(incidents.shorten_text(test)), value + ) + async def extract_and_form_message_link_embeds(self): """ Extract message links from a mocked message and form the message link embed. @@ -821,7 +833,7 @@ class TestMessageLinkEmbeds(TestIncidents): f"as they break our rules: \n{', '.join(msg_links)}" ) - embeds = await extract_message_links(incident_msg, self.cog_instance.bot) + embeds = await incidents.extract_message_links(incident_msg, self.cog_instance.bot) description = ( f"**Author:** {format_user(msg.author)}\n" f"**Channel:** {msg.channel.mention} ({msg.channel.category}/#{msg.channel.name})\n" @@ -829,15 +841,11 @@ class TestMessageLinkEmbeds(TestIncidents): ) # Check number of embeds returned with number of valid links - self.assertEqual( - self, len(embeds), 2 - ) + self.assertEqual(len(embeds), 2) # Check for the embed descriptions for embed in embeds: - self.assertEqual( - self, embed.description, description - ) + self.assertEqual(embed.description, description) @patch("bot.exts.moderation.incidents.is_incident", MagicMock(return_value=True)) async def test_incident_message_edit(self): -- cgit v1.2.3 From 24c7a975cf18b80ae4bb6d65f5a4950bae0ca4cb Mon Sep 17 00:00:00 2001 From: Shivansh Date: Mon, 10 May 2021 10:02:22 +0530 Subject: (incidents): Use subtests for test_shorten_text --- tests/bot/exts/moderation/test_incidents.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/bot/exts/moderation/test_incidents.py b/tests/bot/exts/moderation/test_incidents.py index 3c5d8f47d..875b76057 100644 --- a/tests/bot/exts/moderation/test_incidents.py +++ b/tests/bot/exts/moderation/test_incidents.py @@ -800,10 +800,10 @@ class TestMessageLinkEmbeds(TestIncidents): 'Hello, World!' * 300: ('Hello, World!' * 300)[:300] + '...' } - for test, value in tests.items(): - self.assertEqual( - str(incidents.shorten_text(test)), value - ) + for content, expected_conversion in tests.items(): + with self.subTest(content=content, expected_conversion=expected_conversion): + conversion = incidents.shorten_text(content) + self.assertEqual(conversion, expected_conversion) async def extract_and_form_message_link_embeds(self): """ -- cgit v1.2.3 From fc9a9d2cd01530444804b271ed00432cacf85353 Mon Sep 17 00:00:00 2001 From: Shivansh Date: Mon, 10 May 2021 10:07:48 +0530 Subject: (incidents):Log with error if webhook not found --- bot/exts/moderation/incidents.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 22b50625a..7d0984bd1 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -297,6 +297,9 @@ class Incidents(Cog): await self.bot.wait_until_guild_available() self.incidents_webhook = await self.bot.fetch_webhook(Webhooks.incidents) + if not self.incidents_webhook: + log.error(f"Failed to fetch incidents webhook with id `{Webhooks.incidents}`.") + async def crawl_incidents(self) -> None: """ Crawl #incidents and add missing emoji where necessary. -- cgit v1.2.3 From 23126ee86d2aa4d9357c41247faf46e1b2a8d138 Mon Sep 17 00:00:00 2001 From: Shivansh Date: Tue, 11 May 2021 10:39:19 +0530 Subject: Only process the first 10 message links --- bot/exts/moderation/incidents.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 7d0984bd1..9ce892024 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -232,14 +232,12 @@ async def extract_message_links(message: discord.Message, bot: Bot) -> t.Optiona if message_links: embeds = [] - for message_link in message_links: + for message_link in message_links[:10]: ctx = await bot.get_context(message) embed = await make_message_link_embed(ctx, message_link[0]) if embed: embeds.append(embed) - return embeds[:10] - log.trace( f"Skipping discord message link detection on {message.id}: message doesn't qualify." ) -- cgit v1.2.3 From fc8c0c121fe853baa3ee4ecd760229eac6689387 Mon Sep 17 00:00:00 2001 From: Shivansh Date: Tue, 11 May 2021 10:44:31 +0530 Subject: Apply requested changes to doc strings --- bot/exts/moderation/incidents.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 9ce892024..950d419c0 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -271,7 +271,7 @@ class Incidents(Cog): * If `Signal.ACTIONED` or `Signal.NOT_ACTIONED` were chosen, attempt to relay the incident message to #incidents-archive * If relay successful, delete original message - * Search the cache for the webhook message for this message, if found delete it. + * Delete quotation message if cached * See: `on_raw_reaction_add` Please refer to function docstrings for implementation details. @@ -533,9 +533,9 @@ class Incidents(Cog): """ Pass `message` to `add_signals` and `extract_message_links` if it satisfies `is_incident`. - If the message (`message`) is an incident report, then run it through `extract_message_links` - to get all the message link embeds (embeds which contain information about that particular - link).These message link embeds are then sent into the channel. + If `message` is an incident report, then run it through `extract_message_links` to get all + the message link embeds (embeds which contain information about that particular link). + These message link embeds are then sent into the channel. Also passes the message into `add_signals` if the message is an incident. """ -- cgit v1.2.3 From 19112affa86ceb2fbe55e0cf751ac675f24d725e Mon Sep 17 00:00:00 2001 From: Shivansh Date: Tue, 11 May 2021 10:46:35 +0530 Subject: Use better variable names This commit also adds a line which was got removed by mistake earlier. --- bot/exts/moderation/incidents.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 950d419c0..05c2ad6c9 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -238,6 +238,8 @@ async def extract_message_links(message: discord.Message, bot: Bot) -> t.Optiona if embed: embeds.append(embed) + return embeds + log.trace( f"Skipping discord message link detection on {message.id}: message doesn't qualify." ) @@ -567,7 +569,7 @@ class Incidents(Cog): if webhook_msg_id: await self.incidents_webhook.edit_message( message_id=webhook_msg_id, - embeds=[x for x in webhook_embed_list if x is not None], + embeds=[embed for embed in webhook_embed_list if embed is not None], ) return @@ -600,7 +602,7 @@ class Incidents(Cog): """ try: webhook_msg = await webhook.send( - embeds=[x for x in webhook_embed_list if x is not None], + embeds=[embed for embed in webhook_embed_list if embed is not None], username=sub_clyde(message.author.name), avatar_url=message.author.avatar_url, wait=True, -- cgit v1.2.3 From aa620fefd1dbf9f5cda19a72bf29483a61aa2a93 Mon Sep 17 00:00:00 2001 From: Shivansh Date: Wed, 12 May 2021 09:29:39 +0530 Subject: Make `extract_message_links` an instance method Since it was used cog's state (`self.bot`), it would be better to move it to the cog. --- bot/exts/moderation/incidents.py | 61 ++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 05c2ad6c9..197842034 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -216,35 +216,6 @@ async def add_signals(incident: discord.Message) -> None: return -async def extract_message_links(message: discord.Message, bot: Bot) -> t.Optional[list]: - """ - Checks if there's any message links in the text content. - - Then passes the the message_link into `make_message_link_embed` to format a - embed for it containing information about the link. - - As discord only allows a max of 10 embeds in a single webhook, just send the - first 10 embeds and don't care about the rest. - - If no links are found for the message, it logs a trace statement. - """ - message_links = DISCORD_MESSAGE_LINK_RE.findall(str(message.content)) - - if message_links: - embeds = [] - for message_link in message_links[:10]: - ctx = await bot.get_context(message) - embed = await make_message_link_embed(ctx, message_link[0]) - if embed: - embeds.append(embed) - - return embeds - - log.trace( - f"Skipping discord message link detection on {message.id}: message doesn't qualify." - ) - - class Incidents(Cog): """ Automation for the #incidents channel. @@ -544,7 +515,7 @@ class Incidents(Cog): if is_incident(message): await add_signals(message) - webhook_embed_list = await extract_message_links(message, self.bot) + webhook_embed_list = await self.extract_message_links(message) if webhook_embed_list: await self.send_message_link_embeds(webhook_embed_list, message, self.incidents_webhook) @@ -563,7 +534,7 @@ class Incidents(Cog): The edited message is also passed into `add_signals` if it is a incident message. """ if is_incident(msg_after): - webhook_embed_list = await extract_message_links(msg_after, self.bot) + webhook_embed_list = await self.extract_message_links(msg_after) webhook_msg_id = await self.message_link_embeds_cache.get(msg_before.id) if webhook_msg_id: @@ -585,6 +556,34 @@ class Incidents(Cog): if is_incident(message): await self.delete_msg_link_embed(message) + async def extract_message_links(self, message: discord.Message) -> t.Optional[t.List[discord.Embed]]: + """ + Checks if there's any message links in the text content. + + Then passes the the message_link into `make_message_link_embed` to format a + embed for it containing information about the link. + + As discord only allows a max of 10 embeds in a single webhook, just send the + first 10 embeds and don't care about the rest. + + If no links are found for the message, it logs a trace statement. + """ + message_links = DISCORD_MESSAGE_LINK_RE.findall(str(message.content)) + + if message_links: + embeds = [] + for message_link in message_links[:10]: + ctx = await self.bot.get_context(message) + embed = await make_message_link_embed(ctx, message_link[0]) + if embed: + embeds.append(embed) + + return embeds + + log.trace( + f"Skipping discord message link detection on {message.id}: message doesn't qualify." + ) + async def send_message_link_embeds( self, webhook_embed_list: t.List, -- cgit v1.2.3 From 682693bc07960db186cef95b0188031f934a360c Mon Sep 17 00:00:00 2001 From: Shivansh Date: Wed, 12 May 2021 09:35:40 +0530 Subject: Delete msg link embed if no link on edit Earlier, if we edited a message which contained message links originally but not now, then the webhook message wouldn't get deleted. This commits fixes that bug. --- bot/exts/moderation/incidents.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 197842034..7aad1df35 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -537,6 +537,10 @@ class Incidents(Cog): webhook_embed_list = await self.extract_message_links(msg_after) webhook_msg_id = await self.message_link_embeds_cache.get(msg_before.id) + if not webhook_embed_list: + await self.delete_msg_link_embed(msg_after) + return + if webhook_msg_id: await self.incidents_webhook.edit_message( message_id=webhook_msg_id, -- cgit v1.2.3 From ecfcc902fc619c2f07c449b40c4373a61b5abaf7 Mon Sep 17 00:00:00 2001 From: Shivansh Date: Wed, 12 May 2021 09:55:19 +0530 Subject: Use `on_raw_message_edit` Originally it was using `on_message_edit` which would have failed if the message was not in the bot' cache. Therefore we would have to use a raw listener. --- bot/exts/moderation/incidents.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 7aad1df35..0da4acaa2 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -520,9 +520,12 @@ class Incidents(Cog): await self.send_message_link_embeds(webhook_embed_list, message, self.incidents_webhook) @Cog.listener() - async def on_message_edit(self, msg_before: discord.Message, msg_after: discord.Message) -> None: + async def on_raw_message_edit(self, payload: discord.RawMessageUpdateEvent) -> None: """ - Pass `msg_after` to `extract_message_links` and edit `msg_before` webhook msg. + Pass processed `payload` to `extract_message_links` and edit `msg_before` webhook msg. + + Fetch the message found in payload, if not found i.e. the message got deleted then delete its + webhook message and return. Deletes cache value (`message_link_embeds_cache`) of `msg_before` if it exists and removes the webhook message for that particular link from the channel. @@ -533,9 +536,15 @@ class Incidents(Cog): The edited message is also passed into `add_signals` if it is a incident message. """ + try: + channel = self.bot.get_channel(int(payload.data["channel_id"])) + msg_after = await channel.fetch_message(payload.message_id) + except discord.NotFound: # Was deleted before we got the event + return + if is_incident(msg_after): webhook_embed_list = await self.extract_message_links(msg_after) - webhook_msg_id = await self.message_link_embeds_cache.get(msg_before.id) + webhook_msg_id = await self.message_link_embeds_cache.get(payload.message_id) if not webhook_embed_list: await self.delete_msg_link_embed(msg_after) -- cgit v1.2.3 From 44d5481d23cf819e347d3940812be3450ead1934 Mon Sep 17 00:00:00 2001 From: Shivansh Date: Wed, 12 May 2021 10:07:01 +0530 Subject: Use raw message delete listener --- bot/exts/moderation/incidents.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 0da4acaa2..7ef4af3df 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -540,6 +540,7 @@ class Incidents(Cog): channel = self.bot.get_channel(int(payload.data["channel_id"])) msg_after = await channel.fetch_message(payload.message_id) except discord.NotFound: # Was deleted before we got the event + await self.delete_msg_link_embed(payload.message_id) return if is_incident(msg_after): @@ -547,7 +548,7 @@ class Incidents(Cog): webhook_msg_id = await self.message_link_embeds_cache.get(payload.message_id) if not webhook_embed_list: - await self.delete_msg_link_embed(msg_after) + await self.delete_msg_link_embed(msg_after.id) return if webhook_msg_id: @@ -560,14 +561,13 @@ class Incidents(Cog): await self.send_message_link_embeds(webhook_embed_list, msg_after, self.incidents_webhook) @Cog.listener() - async def on_message_delete(self, message: discord.Message) -> None: + async def on_raw_message_delete(self, payload: discord.RawMessageDeleteEvent) -> None: """ - Delete message link embeds for `message`. + Delete message link embeds for `payload.message_id`. Search through the cache for message, if found delete it from cache and channel. """ - if is_incident(message): - await self.delete_msg_link_embed(message) + await self.delete_msg_link_embed(payload.message_id) async def extract_message_links(self, message: discord.Message) -> t.Optional[t.List[discord.Embed]]: """ @@ -630,10 +630,10 @@ class Incidents(Cog): log.trace("Message Link Embed Sent successfully!") return webhook_msg.id - async def delete_msg_link_embed(self, message: discord.Message) -> None: + async def delete_msg_link_embed(self, message_id: int) -> None: """Delete discord message link message found in cache for `message`.""" log.trace("Deleting discord links webhook message.") - webhook_msg_id = await self.message_link_embeds_cache.get(message.id) + webhook_msg_id = await self.message_link_embeds_cache.get(int(message_id)) if webhook_msg_id: try: @@ -641,7 +641,7 @@ class Incidents(Cog): except discord.errors.NotFound: log.trace(f"Incidents message link embed (`{webhook_msg_id}`) has already been deleted, skipping.") - await self.message_link_embeds_cache.delete(message.id) + await self.message_link_embeds_cache.delete(message_id) log.trace("Successfully deleted discord links webhook message.") -- cgit v1.2.3 From 40a57a1dad45f0b32f2c5137e9c36d9c6df183fd Mon Sep 17 00:00:00 2001 From: Shivansh Date: Wed, 12 May 2021 11:19:59 +0530 Subject: Update tests for message link embeds This commit updates the test in accordance with 0b35f2a and 0c5561d. --- bot/exts/moderation/incidents.py | 2 +- tests/bot/exts/moderation/test_incidents.py | 42 +++++++++++++++++++---------- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 7ef4af3df..97bb32591 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -422,7 +422,7 @@ class Incidents(Cog): log.trace("Deletion was confirmed") # Deletes the message link embeds found in cache from the channel and cache. - await self.delete_msg_link_embed(incident) + await self.delete_msg_link_embed(incident.id) async def resolve_message(self, message_id: int) -> t.Optional[discord.Message]: """ diff --git a/tests/bot/exts/moderation/test_incidents.py b/tests/bot/exts/moderation/test_incidents.py index 875b76057..6e97d31af 100644 --- a/tests/bot/exts/moderation/test_incidents.py +++ b/tests/bot/exts/moderation/test_incidents.py @@ -833,27 +833,41 @@ class TestMessageLinkEmbeds(TestIncidents): f"as they break our rules: \n{', '.join(msg_links)}" ) - embeds = await incidents.extract_message_links(incident_msg, self.cog_instance.bot) - description = ( - f"**Author:** {format_user(msg.author)}\n" - f"**Channel:** {msg.channel.mention} ({msg.channel.category}/#{msg.channel.name})\n" - f"**Content:** {('Hello, World!' * 3000)[:300] + '...'}\n" - ) + with patch( + "bot.exts.moderation.incidents.Incidents.extract_message_links", AsyncMock() + ) as mock_extract_message_links: + embeds = mock_extract_message_links(incident_msg) + description = ( + f"**Author:** {format_user(msg.author)}\n" + f"**Channel:** {msg.channel.mention} ({msg.channel.category}/#{msg.channel.name})\n" + f"**Content:** {('Hello, World!' * 3000)[:300] + '...'}\n" + ) - # Check number of embeds returned with number of valid links - self.assertEqual(len(embeds), 2) + # Check number of embeds returned with number of valid links + self.assertEqual(len(embeds), 2) - # Check for the embed descriptions - for embed in embeds: - self.assertEqual(embed.description, description) + # Check for the embed descriptions + for embed in embeds: + self.assertEqual(embed.description, description) @patch("bot.exts.moderation.incidents.is_incident", MagicMock(return_value=True)) async def test_incident_message_edit(self): """Edit the incident message and check whether `extract_message_links` is called or not.""" self.cog_instance.incidents_webhook = MockAsyncWebhook() # Patch in our webhook - edited_msg = MockMessage(id=123) - with patch("bot.exts.moderation.incidents.extract_message_links", AsyncMock()) as mock_extract_message_links: - await self.cog_instance.on_message_edit(MockMessage(id=123), edited_msg) + text_channel = MockTextChannel() + self.cog_instance.bot.get_channel = MagicMock(return_value=text_channel) + text_channel.fetch_message = AsyncMock(return_value=MockMessage()) + + payload = AsyncMock( + discord.RawMessageUpdateEvent, + channel_id=123, + message_id=456 + ) + + with patch( + "bot.exts.moderation.incidents.Incidents.extract_message_links", AsyncMock() + ) as mock_extract_message_links: + await self.cog_instance.on_raw_message_edit(payload) mock_extract_message_links.assert_awaited_once() -- cgit v1.2.3 From 43bed60ff788eefba704318f8b18e0b3f8b5eb4c Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Mon, 16 Aug 2021 14:26:14 +0530 Subject: Mock id,content attribute rather than type casting --- bot/exts/moderation/incidents.py | 4 ++-- tests/bot/exts/moderation/test_incidents.py | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 97bb32591..8d255071a 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -537,7 +537,7 @@ class Incidents(Cog): The edited message is also passed into `add_signals` if it is a incident message. """ try: - channel = self.bot.get_channel(int(payload.data["channel_id"])) + channel = self.bot.get_channel(payload.channel_id) msg_after = await channel.fetch_message(payload.message_id) except discord.NotFound: # Was deleted before we got the event await self.delete_msg_link_embed(payload.message_id) @@ -626,7 +626,7 @@ class Incidents(Cog): ) else: - await self.message_link_embeds_cache.set(int(message.id), int(webhook_msg.id)) + await self.message_link_embeds_cache.set(message.id, webhook_msg.id) log.trace("Message Link Embed Sent successfully!") return webhook_msg.id diff --git a/tests/bot/exts/moderation/test_incidents.py b/tests/bot/exts/moderation/test_incidents.py index 6e97d31af..06eafdde3 100644 --- a/tests/bot/exts/moderation/test_incidents.py +++ b/tests/bot/exts/moderation/test_incidents.py @@ -853,11 +853,12 @@ class TestMessageLinkEmbeds(TestIncidents): @patch("bot.exts.moderation.incidents.is_incident", MagicMock(return_value=True)) async def test_incident_message_edit(self): """Edit the incident message and check whether `extract_message_links` is called or not.""" - self.cog_instance.incidents_webhook = MockAsyncWebhook() # Patch in our webhook + self.cog_instance.incidents_webhook = MockAsyncWebhook(id=101) # Patch in our webhook + self.cog_instance.incidents_webhook.send = AsyncMock(return_value=MockMessage(id=191)) - text_channel = MockTextChannel() + text_channel = MockTextChannel(id=123) self.cog_instance.bot.get_channel = MagicMock(return_value=text_channel) - text_channel.fetch_message = AsyncMock(return_value=MockMessage()) + text_channel.fetch_message = AsyncMock(return_value=MockMessage(id=777, content="Did jason just screw up?")) payload = AsyncMock( discord.RawMessageUpdateEvent, -- cgit v1.2.3 From 6965c0868bb6230c35eca9dac4541e5e904b7575 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Mon, 16 Aug 2021 15:41:40 +0530 Subject: Correct log trace link to show the correct behaviour --- bot/exts/moderation/incidents.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 8d255071a..2de7dd666 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -594,7 +594,7 @@ class Incidents(Cog): return embeds log.trace( - f"Skipping discord message link detection on {message.id}: message doesn't qualify." + f"No message links detected on incident message with id {message.id}." ) async def send_message_link_embeds( -- cgit v1.2.3 From b01cccec51d5c9df298a0380ad2ab516ee219c3d Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Mon, 16 Aug 2021 15:43:59 +0530 Subject: Remove unnecessary check for embed when sending --- bot/exts/moderation/incidents.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 2de7dd666..dfc2e0bb0 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -614,7 +614,7 @@ class Incidents(Cog): """ try: webhook_msg = await webhook.send( - embeds=[embed for embed in webhook_embed_list if embed is not None], + embeds=[embed for embed in webhook_embed_list], username=sub_clyde(message.author.name), avatar_url=message.author.avatar_url, wait=True, -- cgit v1.2.3 From 1b52ccf00df8550b0e5df02d350fb8e0baef935d Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sat, 21 Aug 2021 07:26:11 +0530 Subject: Handle message not found specially Originally message not found would be passed into discord.Exceptions and then would be logged which was unnecessary for a `MessageNotFound` error. Now this has been handled, if the bot receives a deleted message it would look through the last 100 messages of mod_logs channel to check if a log entry exists for that message. If one exists, then the incident embed would have this log entry message linked to it, if it doesn't it would send a message not found error embed. In future, we could maybe do the modlog deleted message entry finding via the python discord API, rather than using hard coded values and checking if they are existing in the log entry. --- bot/exts/moderation/incidents.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index dfc2e0bb0..58777f8cc 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -7,7 +7,7 @@ from enum import Enum import discord from async_rediscache import RedisCache -from discord.ext.commands import Cog, Context, MessageConverter +from discord.ext.commands import Cog, Context, MessageConverter, MessageNotFound from bot.bot import Bot from bot.constants import Channels, Colours, Emojis, Guild, Webhooks @@ -168,9 +168,36 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> t.Optional Channel: Special/#bot-commands (814190307980607493) Content: This is a very important message! """ + embed = None + try: message: discord.Message = await MessageConverter().convert(ctx, message_link) + except MessageNotFound: + mod_logs_channel = ctx.bot.get_channel(Channels.mod_log) + last_100_logs: list[discord.Message] = await mod_logs_channel.history(limit=100).flatten() + + for log_entry in last_100_logs: + log_embed: discord.Embed = log_entry.embeds[0] + if ( + log_embed.author.name == "Message deleted" + and f"[Jump to message]({message_link})" in log_embed.description + ): + embed = discord.Embed( + colour=discord.Colour.dark_gold(), + title="Deleted Message Link", + description=( + f"Found <#{Channels.mod_log}> entry for deleted message: " + f"[Jump to message]({log_entry.jump_url})." + ) + ) + if not embed: + embed = discord.Embed( + colour=discord.Colour.red(), + title="Bad Message Link", + description=f"Message {message_link} not found." + ) + except discord.DiscordException as e: log.exception(f"Failed to make message link embed for '{message_link}', raised exception: {e}") @@ -190,7 +217,7 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> t.Optional ) embed.set_footer(text=f"Message ID: {message.id}") - return embed + return embed async def add_signals(incident: discord.Message) -> None: -- cgit v1.2.3 From 1724d323db69f2aebc9b74a28b6322ef5f784fe4 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sun, 22 Aug 2021 14:14:12 +0530 Subject: Sends msg embeds for helper readable msgs only --- bot/exts/moderation/incidents.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 58777f8cc..81a1f0721 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -10,7 +10,7 @@ from async_rediscache import RedisCache from discord.ext.commands import Cog, Context, MessageConverter, MessageNotFound from bot.bot import Bot -from bot.constants import Channels, Colours, Emojis, Guild, Webhooks +from bot.constants import Channels, Colours, Emojis, Guild, Roles, Webhooks from bot.utils import scheduling from bot.utils.messages import format_user, sub_clyde @@ -203,6 +203,13 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> t.Optional else: channel = message.channel + helpers_role = message.guild.get_role(Roles.helpers) + if not channel.overwrites_for(helpers_role).read_messages: + log.info( + f"Helpers don't have read permissions in #{channel.name}," + " not sending message link embed for {message_link}" + ) + return embed = discord.Embed( colour=discord.Colour.gold(), @@ -641,7 +648,7 @@ class Incidents(Cog): """ try: webhook_msg = await webhook.send( - embeds=[embed for embed in webhook_embed_list], + embeds=[embed for embed in webhook_embed_list if embed], username=sub_clyde(message.author.name), avatar_url=message.author.avatar_url, wait=True, -- cgit v1.2.3 From 842cf60966e4a568b20961d350fb8ceaee6d8d96 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Tue, 31 Aug 2021 05:26:06 +0530 Subject: Goodbye enhanced incidents edits Was discussed with Mr.Webscale (joe), Xithrius in dev-voice --- bot/exts/moderation/incidents.py | 41 ----------------------------- tests/bot/exts/moderation/test_incidents.py | 23 ---------------- 2 files changed, 64 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 81a1f0721..70f5272e0 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -553,47 +553,6 @@ class Incidents(Cog): if webhook_embed_list: await self.send_message_link_embeds(webhook_embed_list, message, self.incidents_webhook) - @Cog.listener() - async def on_raw_message_edit(self, payload: discord.RawMessageUpdateEvent) -> None: - """ - Pass processed `payload` to `extract_message_links` and edit `msg_before` webhook msg. - - Fetch the message found in payload, if not found i.e. the message got deleted then delete its - webhook message and return. - - Deletes cache value (`message_link_embeds_cache`) of `msg_before` if it exists and removes the - webhook message for that particular link from the channel. - - If the message edit (`msg_after`) is a incident then run it through `extract_message_links` - to get all the message link embeds (embeds which contain information about that particular - link), this message link embeds are then sent into the channel. - - The edited message is also passed into `add_signals` if it is a incident message. - """ - try: - channel = self.bot.get_channel(payload.channel_id) - msg_after = await channel.fetch_message(payload.message_id) - except discord.NotFound: # Was deleted before we got the event - await self.delete_msg_link_embed(payload.message_id) - return - - if is_incident(msg_after): - webhook_embed_list = await self.extract_message_links(msg_after) - webhook_msg_id = await self.message_link_embeds_cache.get(payload.message_id) - - if not webhook_embed_list: - await self.delete_msg_link_embed(msg_after.id) - return - - if webhook_msg_id: - await self.incidents_webhook.edit_message( - message_id=webhook_msg_id, - embeds=[embed for embed in webhook_embed_list if embed is not None], - ) - return - - await self.send_message_link_embeds(webhook_embed_list, msg_after, self.incidents_webhook) - @Cog.listener() async def on_raw_message_delete(self, payload: discord.RawMessageDeleteEvent) -> None: """ diff --git a/tests/bot/exts/moderation/test_incidents.py b/tests/bot/exts/moderation/test_incidents.py index 06eafdde3..3bdc9128c 100644 --- a/tests/bot/exts/moderation/test_incidents.py +++ b/tests/bot/exts/moderation/test_incidents.py @@ -849,26 +849,3 @@ class TestMessageLinkEmbeds(TestIncidents): # Check for the embed descriptions for embed in embeds: self.assertEqual(embed.description, description) - - @patch("bot.exts.moderation.incidents.is_incident", MagicMock(return_value=True)) - async def test_incident_message_edit(self): - """Edit the incident message and check whether `extract_message_links` is called or not.""" - self.cog_instance.incidents_webhook = MockAsyncWebhook(id=101) # Patch in our webhook - self.cog_instance.incidents_webhook.send = AsyncMock(return_value=MockMessage(id=191)) - - text_channel = MockTextChannel(id=123) - self.cog_instance.bot.get_channel = MagicMock(return_value=text_channel) - text_channel.fetch_message = AsyncMock(return_value=MockMessage(id=777, content="Did jason just screw up?")) - - payload = AsyncMock( - discord.RawMessageUpdateEvent, - channel_id=123, - message_id=456 - ) - - with patch( - "bot.exts.moderation.incidents.Incidents.extract_message_links", AsyncMock() - ) as mock_extract_message_links: - await self.cog_instance.on_raw_message_edit(payload) - - mock_extract_message_links.assert_awaited_once() -- cgit v1.2.3 From b8f2694b99e182f716cd3d241c34cfbfcc484954 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Mon, 11 Oct 2021 12:37:03 +0530 Subject: Apply requested grammar and style changes Co-authored-by: Bluenix Co-authored-by: Boris Muratov <8bee278@gmail.com> --- bot/exts/moderation/incidents.py | 67 +++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 36 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 70f5272e0..0d28490b3 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -172,7 +172,6 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> t.Optional try: message: discord.Message = await MessageConverter().convert(ctx, message_link) - except MessageNotFound: mod_logs_channel = ctx.bot.get_channel(Channels.mod_log) last_100_logs: list[discord.Message] = await mod_logs_channel.history(limit=100).flatten() @@ -197,10 +196,8 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> t.Optional title="Bad Message Link", description=f"Message {message_link} not found." ) - except discord.DiscordException as e: log.exception(f"Failed to make message link embed for '{message_link}', raised exception: {e}") - else: channel = message.channel helpers_role = message.guild.get_role(Roles.helpers) @@ -292,13 +289,13 @@ class Incidents(Cog): """Prepare `event_lock` and schedule `crawl_task` on start-up.""" self.bot = bot - self.bot.loop.create_task(self.get_webhook()) + self.bot.loop.create_task(self.fetch_webhook()) self.event_lock = asyncio.Lock() self.crawl_task = scheduling.create_task(self.crawl_incidents(), event_loop=self.bot.loop) - async def get_webhook(self) -> None: - """Fetch and store message link embeds webhook, present in #incidents channel.""" + async def fetch_webhook(self) -> None: + """Fetches the incidents webhook object, so we can post message link embeds to it.""" await self.bot.wait_until_guild_available() self.incidents_webhook = await self.bot.fetch_webhook(Webhooks.incidents) @@ -405,7 +402,7 @@ class Incidents(Cog): message, and will abort. There is a `timeout` to ensure that this doesn't hold the lock forever should something go wrong. - Deletes cache value (`message_link_embeds_cache`) of `msg_before` if it exists and removes the + Deletes cache value (`message_link_embeds_cache`) of `incident` if it exists. It then removes the webhook message for that particular link from the channel. """ members_roles: t.Set[int] = {role.id for role in member.roles} @@ -546,12 +543,12 @@ class Incidents(Cog): Also passes the message into `add_signals` if the message is an incident. """ - if is_incident(message): - await add_signals(message) + if not is_incident(message): + return - webhook_embed_list = await self.extract_message_links(message) - if webhook_embed_list: - await self.send_message_link_embeds(webhook_embed_list, message, self.incidents_webhook) + await add_signals(message) + if embed_list := await self.extract_message_links(message): + await self.send_message_link_embeds(embed_list, message, self.incidents_webhook) @Cog.listener() async def on_raw_message_delete(self, payload: discord.RawMessageDeleteEvent) -> None: @@ -564,31 +561,31 @@ class Incidents(Cog): async def extract_message_links(self, message: discord.Message) -> t.Optional[t.List[discord.Embed]]: """ - Checks if there's any message links in the text content. + Check if there's any message links in the text content. - Then passes the the message_link into `make_message_link_embed` to format a + Then pass the message_link into `make_message_link_embed` to format an embed for it containing information about the link. - As discord only allows a max of 10 embeds in a single webhook, just send the + As Discord only allows a max of 10 embeds in a single webhook, just send the first 10 embeds and don't care about the rest. - If no links are found for the message, it logs a trace statement. + If no links are found for the message, just log a trace statement. """ message_links = DISCORD_MESSAGE_LINK_RE.findall(str(message.content)) + if not message_links: + log.trace( + f"No message links detected on incident message with id {message.id}." + ) + return - if message_links: - embeds = [] - for message_link in message_links[:10]: - ctx = await self.bot.get_context(message) - embed = await make_message_link_embed(ctx, message_link[0]) - if embed: - embeds.append(embed) - - return embeds + embeds = [] + for message_link in message_links[:10]: + ctx = await self.bot.get_context(message) + embed = await make_message_link_embed(ctx, message_link[0]) + if embed: + embeds.append(embed) - log.trace( - f"No message links detected on incident message with id {message.id}." - ) + return embeds async def send_message_link_embeds( self, @@ -597,12 +594,12 @@ class Incidents(Cog): webhook: discord.Webhook, ) -> t.Optional[int]: """ - Send Message Link Embeds to #incidents channel. + Send message link embeds to #incidents channel. - Uses the `webhook` passed in as a parameter to send + Using the `webhook` passed in as a parameter to send the embeds in the `webhook_embed_list` parameter. - After sending each webhook it maps the `message.id` + After sending each embed it maps the `message.id to the `webhook_msg_ids` IDs in the async redis-cache. """ try: @@ -612,20 +609,18 @@ class Incidents(Cog): avatar_url=message.author.avatar_url, wait=True, ) - except discord.DiscordException: log.exception( f"Failed to send message link embed {message.id} to #incidents." ) - else: await self.message_link_embeds_cache.set(message.id, webhook_msg.id) - log.trace("Message Link Embed Sent successfully!") + log.trace("Message link embeds sent successfully to #incidents!") return webhook_msg.id async def delete_msg_link_embed(self, message_id: int) -> None: - """Delete discord message link message found in cache for `message`.""" - log.trace("Deleting discord links webhook message.") + """Delete the Discord message link message found in cache for `message_id`.""" + log.trace("Deleting Discord message link's webhook message.") webhook_msg_id = await self.message_link_embeds_cache.get(int(message_id)) if webhook_msg_id: -- cgit v1.2.3 From 863c8d76c66ea748af9ab29bad1d02d16e3888f2 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Mon, 11 Oct 2021 12:52:39 +0530 Subject: Refactor `shorten_text` utility function --- bot/exts/moderation/incidents.py | 10 ++++++---- tests/bot/exts/moderation/test_incidents.py | 19 ++++++++++++++----- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 0d28490b3..4a84d825e 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -142,16 +142,18 @@ def has_signals(message: discord.Message) -> bool: def shorten_text(text: str) -> str: """Truncate the text if there are over 3 lines or 300 characters, or if it is a single word.""" original_length = len(text) - lines = text.count("\n") + lines = text.split("\n") # Limit to a maximum of three lines - if lines > 3: - text = "\n".join(line for line in text.split('\n')[:3]) + if len(lines) > 3: + text = "\n".join(line for line in lines[:3]) + # If it is a single word, then truncate it to 50 characters if text.count(" ") < 1: text = text[:50] # Truncate text to a maximum of 300 characters - if len(text) > 300: + elif len(text) > 300: text = text[:300] + # Add placeholder if the text was shortened if len(text) < original_length: text += "..." diff --git a/tests/bot/exts/moderation/test_incidents.py b/tests/bot/exts/moderation/test_incidents.py index 3bdc9128c..8304af1c0 100644 --- a/tests/bot/exts/moderation/test_incidents.py +++ b/tests/bot/exts/moderation/test_incidents.py @@ -795,9 +795,16 @@ class TestMessageLinkEmbeds(TestIncidents): async def test_shorten_text(self): """Test all cases of text shortening by mocking messages.""" tests = { - "thisisasingleword"*10: ('thisisasingleword'*10)[:50]+"...", - "\n".join("Lets make a new line test".split()): "Lets\nmake\na"+"...", - 'Hello, World!' * 300: ('Hello, World!' * 300)[:300] + '...' + "thisisasingleword"*10: "thisisasinglewordthisisasinglewordthisisasinglewor...", + + "\n".join("Lets make a new line test".split()): "Lets\nmake\na...", + + 'Hello, World!' * 300: ( + "Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!" + "Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!" + "Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!" + "Hello, World!Hello, World!H..." + ) } for content, expected_conversion in tests.items(): @@ -829,8 +836,10 @@ class TestMessageLinkEmbeds(TestIncidents): incident_msg = MockMessage( id=777, - content=f"I would like to report the following messages, " - f"as they break our rules: \n{', '.join(msg_links)}" + content=( + f"I would like to report the following messages, " + f"as they break our rules: \n{', '.join(msg_links)}" + ) ) with patch( -- cgit v1.2.3 From 7316463b54eeafef777468db872e81973663d435 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Tue, 12 Oct 2021 06:36:04 +0530 Subject: Correct discord message link regex The biggest size a Discord snowflake can be is 20. Co-authored-by: Bluenix --- bot/exts/moderation/incidents.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 4a84d825e..5d2c66d6c 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -26,8 +26,8 @@ CRAWL_SLEEP = 2 DISCORD_MESSAGE_LINK_RE = re.compile( r"(https?:\/\/(?:(ptb|canary|www)\.)?discord(?:app)?\.com\/channels\/" - r"[0-9]{15,21}" - r"\/[0-9]{15,21}\/[0-9]{15,21})" + r"[0-9]{15,20}" + r"\/[0-9]{15,20}\/[0-9]{15,20})" ) -- cgit v1.2.3 From 5e6e361ce5f08425d84e53fab611a3b8b685fcc0 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Tue, 12 Oct 2021 06:45:30 +0530 Subject: Add error handling on modlog channel fetch --- bot/exts/moderation/incidents.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 5d2c66d6c..14bec3877 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -175,7 +175,12 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> t.Optional try: message: discord.Message = await MessageConverter().convert(ctx, message_link) except MessageNotFound: - mod_logs_channel = ctx.bot.get_channel(Channels.mod_log) + try: + mod_logs_channel = ctx.bot.get_channel(Channels.mod_log) + except discord.NotFound: + log.exception(f"Mod-logs (<#{Channels.mod_log}> channel not found.") + return + last_100_logs: list[discord.Message] = await mod_logs_channel.history(limit=100).flatten() for log_entry in last_100_logs: -- cgit v1.2.3 From f28fc6bdd57f6def0fcd8bf9e43af82e20f7fa1e Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Tue, 12 Oct 2021 06:47:50 +0530 Subject: Update typehints --- bot/exts/moderation/incidents.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 14bec3877..5c1554861 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -1,9 +1,9 @@ import asyncio import logging import re -import typing as t from datetime import datetime from enum import Enum +from typing import Optional import discord from async_rediscache import RedisCache @@ -45,17 +45,17 @@ class Signal(Enum): # Reactions from non-mod roles will be removed -ALLOWED_ROLES: t.Set[int] = set(Guild.moderation_roles) +ALLOWED_ROLES: set[int] = set(Guild.moderation_roles) # Message must have all of these emoji to pass the `has_signals` check -ALL_SIGNALS: t.Set[str] = {signal.value for signal in Signal} +ALL_SIGNALS: set[str] = {signal.value for signal in Signal} # An embed coupled with an optional file to be dispatched # If the file is not None, the embed attempts to show it in its body -FileEmbed = t.Tuple[discord.Embed, t.Optional[discord.File]] +FileEmbed = tuple[discord.Embed, Optional[discord.File]] -async def download_file(attachment: discord.Attachment) -> t.Optional[discord.File]: +async def download_file(attachment: discord.Attachment) -> Optional[discord.File]: """ Download & return `attachment` file. @@ -129,7 +129,7 @@ def is_incident(message: discord.Message) -> bool: return all(conditions) -def own_reactions(message: discord.Message) -> t.Set[str]: +def own_reactions(message: discord.Message) -> set[str]: """Get the set of reactions placed on `message` by the bot itself.""" return {str(reaction.emoji) for reaction in message.reactions if reaction.me} @@ -161,7 +161,7 @@ def shorten_text(text: str) -> str: return text -async def make_message_link_embed(ctx: Context, message_link: str) -> t.Optional[discord.Embed]: +async def make_message_link_embed(ctx: Context, message_link: str) -> Optional[discord.Embed]: """ Create an embedded representation of the discord message link contained in the incident report. @@ -412,7 +412,7 @@ class Incidents(Cog): Deletes cache value (`message_link_embeds_cache`) of `incident` if it exists. It then removes the webhook message for that particular link from the channel. """ - members_roles: t.Set[int] = {role.id for role in member.roles} + members_roles: set[int] = {role.id for role in member.roles} if not members_roles & ALLOWED_ROLES: # Intersection is truthy on at least 1 common element log.debug(f"Removing invalid reaction: user {member} is not permitted to send signals") try: @@ -462,7 +462,7 @@ class Incidents(Cog): # Deletes the message link embeds found in cache from the channel and cache. await self.delete_msg_link_embed(incident.id) - async def resolve_message(self, message_id: int) -> t.Optional[discord.Message]: + async def resolve_message(self, message_id: int) -> Optional[discord.Message]: """ Get `discord.Message` for `message_id` from cache, or API. @@ -477,7 +477,7 @@ class Incidents(Cog): """ await self.bot.wait_until_guild_available() # First make sure that the cache is ready log.trace(f"Resolving message for: {message_id=}") - message: t.Optional[discord.Message] = self.bot._connection._get_message(message_id) + message: Optional[discord.Message] = self.bot._connection._get_message(message_id) if message is not None: log.trace("Message was found in cache") @@ -566,7 +566,7 @@ class Incidents(Cog): """ await self.delete_msg_link_embed(payload.message_id) - async def extract_message_links(self, message: discord.Message) -> t.Optional[t.List[discord.Embed]]: + async def extract_message_links(self, message: discord.Message) -> Optional[list[discord.Embed]]: """ Check if there's any message links in the text content. @@ -596,10 +596,10 @@ class Incidents(Cog): async def send_message_link_embeds( self, - webhook_embed_list: t.List, + webhook_embed_list: list, message: discord.Message, webhook: discord.Webhook, - ) -> t.Optional[int]: + ) -> Optional[int]: """ Send message link embeds to #incidents channel. -- cgit v1.2.3 From 9da71eddbf5641f2aa734a2fbe67fc444d390856 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Tue, 12 Oct 2021 06:49:52 +0530 Subject: Use scheduling create_task util instead of creating from loop directly --- bot/exts/moderation/incidents.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 5c1554861..65dc69ca6 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -296,7 +296,7 @@ class Incidents(Cog): """Prepare `event_lock` and schedule `crawl_task` on start-up.""" self.bot = bot - self.bot.loop.create_task(self.fetch_webhook()) + scheduling.create_task(self.fetch_webhook(), event_loop=self.bot.loop) self.event_lock = asyncio.Lock() self.crawl_task = scheduling.create_task(self.crawl_incidents(), event_loop=self.bot.loop) @@ -554,7 +554,9 @@ class Incidents(Cog): return await add_signals(message) - if embed_list := await self.extract_message_links(message): + + # Only use this feature if incidents webhook embed is found + if embed_list := await self.extract_message_links(message) and self.incidents_webhook: await self.send_message_link_embeds(embed_list, message, self.incidents_webhook) @Cog.listener() -- cgit v1.2.3 From 50d1cd96623f4d6423326b23e590f9378e69c14c Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sun, 17 Oct 2021 14:33:21 +0530 Subject: Check for webhook availability before extracting msg links --- bot/exts/moderation/incidents.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 65dc69ca6..a02a38b24 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -556,8 +556,9 @@ class Incidents(Cog): await add_signals(message) # Only use this feature if incidents webhook embed is found - if embed_list := await self.extract_message_links(message) and self.incidents_webhook: - await self.send_message_link_embeds(embed_list, message, self.incidents_webhook) + if self.incidents_webhook: + if embed_list := await self.extract_message_links(message): + await self.send_message_link_embeds(embed_list, message, self.incidents_webhook) @Cog.listener() async def on_raw_message_delete(self, payload: discord.RawMessageDeleteEvent) -> None: @@ -608,7 +609,7 @@ class Incidents(Cog): Using the `webhook` passed in as a parameter to send the embeds in the `webhook_embed_list` parameter. - After sending each embed it maps the `message.id + After sending each embed it maps the `message.id` to the `webhook_msg_ids` IDs in the async redis-cache. """ try: -- cgit v1.2.3 From 0324f5ba6a547242dde7543dacc60e069d767cec Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sun, 17 Oct 2021 14:38:11 +0530 Subject: Add incidents check in delete and reaction handlers also --- bot/exts/moderation/incidents.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index a02a38b24..92b4fd5cf 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -459,8 +459,9 @@ class Incidents(Cog): else: log.trace("Deletion was confirmed") - # Deletes the message link embeds found in cache from the channel and cache. - await self.delete_msg_link_embed(incident.id) + if self.incidents_webhook: + # Deletes the message link embeds found in cache from the channel and cache. + await self.delete_msg_link_embed(incident.id) async def resolve_message(self, message_id: int) -> Optional[discord.Message]: """ @@ -567,7 +568,8 @@ class Incidents(Cog): Search through the cache for message, if found delete it from cache and channel. """ - await self.delete_msg_link_embed(payload.message_id) + if self.incidents_webhook: + await self.delete_msg_link_embed(payload.message_id) async def extract_message_links(self, message: discord.Message) -> Optional[list[discord.Embed]]: """ -- cgit v1.2.3 From e84ced79ade1a28a7e24c307d16126dc80a17a7b Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sun, 17 Oct 2021 15:19:35 +0530 Subject: Refactor shorten_text utility function --- bot/exts/moderation/incidents.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 5c5efdb15..b62ba0629 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -142,21 +142,23 @@ def has_signals(message: discord.Message) -> bool: def shorten_text(text: str) -> str: """Truncate the text if there are over 3 lines or 300 characters, or if it is a single word.""" original_length = len(text) - lines = text.split("\n") + # Truncate text to a maximum of 300 characters + if len(text) > 300: + text = text[:300] + # Limit to a maximum of three lines - if len(lines) > 3: - text = "\n".join(line for line in lines[:3]) + text = "\n".join(line for line in text.split("\n", maxsplit=3)[:3]) # If it is a single word, then truncate it to 50 characters - if text.count(" ") < 1: + if text.find(" ") == -1: text = text[:50] - # Truncate text to a maximum of 300 characters - elif len(text) > 300: - text = text[:300] + + # Remove extra whitespaces from the `text` + text = text.strip() # Add placeholder if the text was shortened if len(text) < original_length: - text += "..." + text = f"{text}..." return text -- cgit v1.2.3 From 67e304ec7eefad638fe264731f52e324bfd7fef0 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Tue, 19 Oct 2021 04:55:55 +0530 Subject: Removing config validation checks --- bot/exts/moderation/incidents.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index b62ba0629..805b516c9 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -177,11 +177,7 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> Optional[d try: message: discord.Message = await MessageConverter().convert(ctx, message_link) except MessageNotFound: - try: - mod_logs_channel = ctx.bot.get_channel(Channels.mod_log) - except discord.NotFound: - log.exception(f"Mod-logs (<#{Channels.mod_log}> channel not found.") - return + mod_logs_channel = ctx.bot.get_channel(Channels.mod_log) last_100_logs: list[discord.Message] = await mod_logs_channel.history(limit=100).flatten() -- cgit v1.2.3 From 17e6cb4174c102a5e77aaa8515ba77634356b3e4 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Tue, 19 Oct 2021 04:58:34 +0530 Subject: Make docstring clear about max length --- bot/exts/moderation/incidents.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 805b516c9..9a526fa9f 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -140,7 +140,11 @@ def has_signals(message: discord.Message) -> bool: def shorten_text(text: str) -> str: - """Truncate the text if there are over 3 lines or 300 characters, or if it is a single word.""" + """ + Truncate the text if there are over 3 lines or 300 characters, or if it is a single word. + + The maximum length of the string would be 303 characters across 3 lines at maximum. + """ original_length = len(text) # Truncate text to a maximum of 300 characters if len(text) > 300: @@ -301,7 +305,7 @@ class Incidents(Cog): self.crawl_task = scheduling.create_task(self.crawl_incidents(), event_loop=self.bot.loop) async def fetch_webhook(self) -> None: - """Fetches the incidents webhook object, so we can post message link embeds to it.""" + """Fetch the incidents webhook object, so we can post message link embeds to it.""" await self.bot.wait_until_guild_available() self.incidents_webhook = await self.bot.fetch_webhook(Webhooks.incidents) -- cgit v1.2.3 From 792e05cf87cf52dde38031ca24de511623736c75 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Tue, 19 Oct 2021 05:03:12 +0530 Subject: Add message creation timestamp to message link embed --- bot/exts/moderation/incidents.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 9a526fa9f..0b6b7ad9a 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -222,7 +222,8 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> Optional[d description=( f"**Author:** {format_user(message.author)}\n" f"**Channel:** {channel.mention} ({channel.category}/#{channel.name})\n" - ) + ), + timestamp=message.created_at ) embed.add_field( name="Content", -- cgit v1.2.3 From 6402cc893833c2a03f8aca0048e674226e02cb72 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Tue, 19 Oct 2021 05:04:48 +0530 Subject: Fix incident webhook fetch validation --- bot/exts/moderation/incidents.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 0b6b7ad9a..693c01c81 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -308,9 +308,10 @@ class Incidents(Cog): async def fetch_webhook(self) -> None: """Fetch the incidents webhook object, so we can post message link embeds to it.""" await self.bot.wait_until_guild_available() - self.incidents_webhook = await self.bot.fetch_webhook(Webhooks.incidents) - if not self.incidents_webhook: + try: + self.incidents_webhook = await self.bot.fetch_webhook(Webhooks.incidents) + except discord.HTTPException: log.error(f"Failed to fetch incidents webhook with id `{Webhooks.incidents}`.") async def crawl_incidents(self) -> None: -- cgit v1.2.3 From 945313ea29bf053845665d829e675a1d78e2e545 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Thu, 21 Oct 2021 05:24:57 +0530 Subject: avatar.url -> display_avatar.url --- bot/exts/moderation/incidents.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 693c01c81..21aaafe4a 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -623,7 +623,7 @@ class Incidents(Cog): webhook_msg = await webhook.send( embeds=[embed for embed in webhook_embed_list if embed], username=sub_clyde(message.author.name), - avatar_url=message.author.avatar_url, + avatar_url=message.author.display_avatar.url, wait=True, ) except discord.DiscordException: -- cgit v1.2.3 From 14685ed77b6a359533ad2d72f11d26683fcc80e6 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Thu, 21 Oct 2021 05:28:07 +0530 Subject: Fix helpers view perms check to use 'permissions_for' --- bot/exts/moderation/incidents.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 21aaafe4a..82add2579 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -209,11 +209,10 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> Optional[d log.exception(f"Failed to make message link embed for '{message_link}', raised exception: {e}") else: channel = message.channel - helpers_role = message.guild.get_role(Roles.helpers) - if not channel.overwrites_for(helpers_role).read_messages: + if not channel.permissions_for(channel.guild.get_role(Roles.helpers)).view_channel: log.info( f"Helpers don't have read permissions in #{channel.name}," - " not sending message link embed for {message_link}" + f" not sending message link embed for {message_link}" ) return -- cgit v1.2.3 From 70948bc6ae3191030e3e173aacc1acd5890b3be7 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Thu, 21 Oct 2021 05:48:39 +0530 Subject: Missed a change for 43bed60 --- bot/exts/moderation/incidents.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 82add2579..20e73ccf5 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -587,7 +587,7 @@ class Incidents(Cog): If no links are found for the message, just log a trace statement. """ - message_links = DISCORD_MESSAGE_LINK_RE.findall(str(message.content)) + message_links = DISCORD_MESSAGE_LINK_RE.findall(message.content) if not message_links: log.trace( f"No message links detected on incident message with id {message.id}." -- cgit v1.2.3 From 689850fd99737ab742dedf265cebbc19333535e1 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sun, 7 Nov 2021 01:39:04 +0530 Subject: Add no message content handling --- bot/exts/moderation/incidents.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index c531f4902..87a6579f7 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -224,12 +224,14 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> Optional[d ), timestamp=message.created_at ) - embed.add_field( - name="Content", - value=shorten_text(message.content) - ) embed.set_footer(text=f"Message ID: {message.id}") + if message.content: + embed.add_field( + name="Content", + value=shorten_text(message.content) + ) + return embed -- cgit v1.2.3 From 19cb6d19134676b1eca797a9782010448d91eace Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sun, 7 Nov 2021 01:40:26 +0530 Subject: Attach attachments if present in linked message --- bot/exts/moderation/incidents.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 87a6579f7..e7c9d0399 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -231,6 +231,8 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> Optional[d name="Content", value=shorten_text(message.content) ) + if message.attachments: + embed.set_image(url=message.attachments[0].url) return embed -- cgit v1.2.3 From a3ee621fb53da75ba357a90c5b2d17529aef5c99 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sun, 7 Nov 2021 01:43:11 +0530 Subject: SHow thread parent name if linked message in thread --- bot/exts/moderation/incidents.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index e7c9d0399..18102587a 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -220,7 +220,9 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> Optional[d colour=discord.Colour.gold(), description=( f"**Author:** {format_user(message.author)}\n" - f"**Channel:** {channel.mention} ({channel.category}/#{channel.name})\n" + f"**Channel:** {channel.mention} ({channel.category}" + f"{f'/#{channel.parent.name} - ' if isinstance(channel, discord.Thread) else '/#'}" + f"{channel.name})\n" ), timestamp=message.created_at ) -- cgit v1.2.3 From 32381cdfebd33b39bb69019f174b070beb32c44a Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sun, 7 Nov 2021 01:46:01 +0530 Subject: Explicitly show there is no message content --- bot/exts/moderation/incidents.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 18102587a..bdbce4acb 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -226,13 +226,12 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> Optional[d ), timestamp=message.created_at ) + embed.add_field( + name="Content", + value=shorten_text(message.content) if message.content else "[No Message Content]" + ) embed.set_footer(text=f"Message ID: {message.id}") - if message.content: - embed.add_field( - name="Content", - value=shorten_text(message.content) - ) if message.attachments: embed.set_image(url=message.attachments[0].url) -- cgit v1.2.3 From cd3db5f9e51c79a9fe8f621cdafdc9052ef22033 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Wed, 10 Nov 2021 06:17:23 +0530 Subject: Check if log entry has embeds before indexing them --- bot/exts/moderation/incidents.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index bdbce4acb..77dfad255 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -151,7 +151,7 @@ def shorten_text(text: str) -> str: text = text[:300] # Limit to a maximum of three lines - text = "\n".join(line for line in text.split("\n", maxsplit=3)[:3]) + text = "\n".join(text.split("\n", maxsplit=3)[:3]) # If it is a single word, then truncate it to 50 characters if text.find(" ") == -1: @@ -186,6 +186,9 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> Optional[d last_100_logs: list[discord.Message] = await mod_logs_channel.history(limit=100).flatten() for log_entry in last_100_logs: + if not log_entry.embeds: + continue + log_embed: discord.Embed = log_entry.embeds[0] if ( log_embed.author.name == "Message deleted" -- cgit v1.2.3 From b1f73f4c1a67f061ee2c1cd74980fcd725ae9f1e Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Sun, 14 Nov 2021 03:30:10 +0000 Subject: Add #bot-commands to guild features in !server This prevents spam in dev-contrib and dev-core from people trying to find which Discord feature flags are enabled for Python Discord. It's not ideal that we have to increase output size in #bot-commands but it prevents spam in #dev-contrib. --- bot/exts/info/information.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bot/exts/info/information.py b/bot/exts/info/information.py index 7f4811a43..dab2dbb6c 100644 --- a/bot/exts/info/information.py +++ b/bot/exts/info/information.py @@ -178,7 +178,10 @@ class Information(Cog): # Server Features are only useful in certain channels if ctx.channel.id in ( - *constants.MODERATION_CHANNELS, constants.Channels.dev_core, constants.Channels.dev_contrib + *constants.MODERATION_CHANNELS, + constants.Channels.dev_core, + constants.Channels.dev_contrib, + constants.Channels.bot_commands ): features = f"\nFeatures: {', '.join(ctx.guild.features)}" else: -- cgit v1.2.3 From df8a44957aca30f929ed1bdebc97ff33ab5af1ba Mon Sep 17 00:00:00 2001 From: mina <75038675+minalike@users.noreply.github.com> Date: Sun, 14 Nov 2021 16:41:46 -0500 Subject: Update order of off-topic channels (#1956) Reverse order of off-topic channels from ot0, ot1, ot2 to ot2, ot1, ot0 --- bot/resources/tags/off-topic.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/resources/tags/off-topic.md b/bot/resources/tags/off-topic.md index 6a864a1d5..287224d7f 100644 --- a/bot/resources/tags/off-topic.md +++ b/bot/resources/tags/off-topic.md @@ -1,9 +1,9 @@ **Off-topic channels** There are three off-topic channels: -• <#291284109232308226> -• <#463035241142026251> • <#463035268514185226> +• <#463035241142026251> +• <#291284109232308226> Their names change randomly every 24 hours, but you can always find them under the `OFF-TOPIC/GENERAL` category in the channel list. -- cgit v1.2.3 From 6cebf897f0f1fa767bb593cfb7208a3d8b3a43c5 Mon Sep 17 00:00:00 2001 From: Izan Date: Tue, 16 Nov 2021 10:13:06 +0000 Subject: Add ability to reply to message for `!remind` --- bot/exts/utils/reminders.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index 86e4505fa..90677b2dd 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -214,7 +214,7 @@ class Reminders(Cog): @group(name="remind", aliases=("reminder", "reminders", "remindme"), invoke_without_command=True) async def remind_group( - self, ctx: Context, mentions: Greedy[ReminderMention], expiration: Duration, *, content: str + self, ctx: Context, mentions: Greedy[ReminderMention], expiration: Duration, *, content: t.Optional[str] = None ) -> None: """ Commands for managing your reminders. @@ -234,7 +234,7 @@ class Reminders(Cog): @remind_group.command(name="new", aliases=("add", "create")) async def new_reminder( - self, ctx: Context, mentions: Greedy[ReminderMention], expiration: Duration, *, content: str + self, ctx: Context, mentions: Greedy[ReminderMention], expiration: Duration, *, content: t.Optional[str] = None ) -> None: """ Set yourself a simple reminder. @@ -283,6 +283,20 @@ class Reminders(Cog): mention_ids = [mention.id for mention in mentions] + # If `content` isn't provided then we try to get message content of a replied message + if not content: + if reference := ctx.message.reference: + if isinstance((resolved_message := reference.resolved), discord.Message): + content = resolved_message.content + # If we weren't able to get the content of a replied message + if content is None: + await send_denial(ctx, "Your reminder must have a content and/or reply to a message.") + return + + # If the replied message has no content (e.g. only attachments/embeds) + if content == "": + content = "See referenced message." + # Now we can attempt to actually set the reminder. reminder = await self.bot.api_client.post( 'bot/reminders', -- cgit v1.2.3 From 9cdfe35abcc2379db8b4d98d9c74f3c60e230984 Mon Sep 17 00:00:00 2001 From: ChrisJL Date: Tue, 16 Nov 2021 14:08:34 +0000 Subject: Don't log threads in admin channels (#1954) This change disables the mod-log for any changes to threads in channels that mods don't have read perms to. Co-authored-by: Kieran Siek --- bot/exts/moderation/modlog.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/bot/exts/moderation/modlog.py b/bot/exts/moderation/modlog.py index 6416bc3c7..91709e5e5 100644 --- a/bot/exts/moderation/modlog.py +++ b/bot/exts/moderation/modlog.py @@ -775,6 +775,10 @@ class ModLog(Cog, name="ModLog"): @Cog.listener() async def on_thread_update(self, before: Thread, after: Thread) -> None: """Log thread archiving, un-archiving and name edits.""" + if self.is_channel_ignored(after.id): + log.trace("Ignoring update of thread %s (%d)", after.mention, after.id) + return + if before.name != after.name: await self.send_log_message( Icons.hash_blurple, @@ -811,6 +815,10 @@ class ModLog(Cog, name="ModLog"): @Cog.listener() async def on_thread_delete(self, thread: Thread) -> None: """Log thread deletion.""" + if self.is_channel_ignored(thread.id): + log.trace("Ignoring deletion of thread %s (%d)", thread.mention, thread.id) + return + await self.send_log_message( Icons.hash_red, Colours.soft_red, @@ -829,6 +837,10 @@ class ModLog(Cog, name="ModLog"): if thread.me: return + if self.is_channel_ignored(thread.id): + log.trace("Ignoring creation of thread %s (%d)", thread.mention, thread.id) + return + await self.send_log_message( Icons.hash_green, Colours.soft_green, -- cgit v1.2.3 From b55a2b94ea666f6891bcbc1c4d0e67857b1900ef Mon Sep 17 00:00:00 2001 From: ChrisJL Date: Fri, 19 Nov 2021 20:00:44 +0000 Subject: Remove unneeded new lines These new lines made the output embed look far to spaced out. --- bot/exts/moderation/infraction/_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/exts/moderation/infraction/_utils.py b/bot/exts/moderation/infraction/_utils.py index c0ef80e3d..bb3cc5380 100644 --- a/bot/exts/moderation/infraction/_utils.py +++ b/bot/exts/moderation/infraction/_utils.py @@ -30,9 +30,9 @@ Infraction = t.Dict[str, t.Union[str, int, bool]] APPEAL_SERVER_INVITE = "https://discord.gg/WXrCJxWBnm" INFRACTION_TITLE = "Please review our rules" -INFRACTION_APPEAL_SERVER_FOOTER = f"\n\nTo appeal this infraction, join our [appeals server]({APPEAL_SERVER_INVITE})." +INFRACTION_APPEAL_SERVER_FOOTER = f"\nTo appeal this infraction, join our [appeals server]({APPEAL_SERVER_INVITE})." INFRACTION_APPEAL_MODMAIL_FOOTER = ( - '\n\nIf you would like to discuss or appeal this infraction, ' + '\nIf you would like to discuss or appeal this infraction, ' 'send a message to the ModMail bot.' ) INFRACTION_AUTHOR_NAME = "Infraction information" -- cgit v1.2.3 From d14a15886301ba660564bfd80480d46d5a435e65 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela Date: Sat, 20 Nov 2021 23:04:01 +0300 Subject: Disable File Logging By Default Place logging to file behind an environment variable, and remove special considerations made for it. Signed-off-by: Hassan Abouelela --- bot/constants.py | 1 + bot/log.py | 15 ++++++++------- config-default.yml | 3 ++- docker-compose.yml | 1 - 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/bot/constants.py b/bot/constants.py index 2dfdd51e2..36b917734 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -685,6 +685,7 @@ class VideoPermission(metaclass=YAMLGetter): # Debug mode DEBUG_MODE: bool = _CONFIG_YAML["debug"] == "true" +FILE_LOGS: bool = _CONFIG_YAML["file_logs"].lower() == "true" # Paths BOT_DIR = os.path.dirname(__file__) diff --git a/bot/log.py b/bot/log.py index b3cecdcf2..100cd06f6 100644 --- a/bot/log.py +++ b/bot/log.py @@ -48,16 +48,17 @@ def setup() -> None: logging.addLevelName(TRACE_LEVEL, "TRACE") logging.setLoggerClass(CustomLogger) + root_log = get_logger() + format_string = "%(asctime)s | %(name)s | %(levelname)s | %(message)s" log_format = logging.Formatter(format_string) - log_file = Path("logs", "bot.log") - log_file.parent.mkdir(exist_ok=True) - file_handler = handlers.RotatingFileHandler(log_file, maxBytes=5242880, backupCount=7, encoding="utf8") - file_handler.setFormatter(log_format) - - root_log = get_logger() - root_log.addHandler(file_handler) + if constants.FILE_LOGS: + log_file = Path("logs", "bot.log") + log_file.parent.mkdir(exist_ok=True) + file_handler = handlers.RotatingFileHandler(log_file, maxBytes=5242880, backupCount=7, encoding="utf8") + file_handler.setFormatter(log_format) + root_log.addHandler(file_handler) if "COLOREDLOGS_LEVEL_STYLES" not in os.environ: coloredlogs.DEFAULT_LEVEL_STYLES = { diff --git a/config-default.yml b/config-default.yml index ed3c3a638..7400cf200 100644 --- a/config-default.yml +++ b/config-default.yml @@ -1,4 +1,5 @@ -debug: !ENV ["BOT_DEBUG", "true"] +debug: !ENV ["BOT_DEBUG", "true"] +file_logs: !ENV ["FILE_LOGS", "false"] bot: diff --git a/docker-compose.yml b/docker-compose.yml index b3ca6baa4..869d9acb6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -90,7 +90,6 @@ services: context: . dockerfile: Dockerfile volumes: - - ./logs:/bot/logs - .:/bot:ro tty: true depends_on: -- cgit v1.2.3 From 1872bb12766bf3c56284ef614b5ab22166b488e5 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Mon, 22 Nov 2021 18:14:43 +0000 Subject: Deal with activity_blocks not being returned by site We are planning to change metricity endpoints on site so that activcity_blocks are not returned if the user has more than 1000 messages. This is because the query to calculate those blocks can get expensive at a high message count. To deal with this, both places activity_blocks are used has been changed to reflect this planned behaviour. --- bot/exts/info/information.py | 7 ++++++- bot/exts/moderation/voice_gate.py | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/bot/exts/info/information.py b/bot/exts/info/information.py index dab2dbb6c..5b48495dc 100644 --- a/bot/exts/info/information.py +++ b/bot/exts/info/information.py @@ -422,7 +422,12 @@ class Information(Cog): activity_output = "No activity" else: activity_output.append(user_activity["total_messages"] or "No messages") - activity_output.append(user_activity["activity_blocks"] or "No activity") + + if (activity_blocks := user_activity.get("activity_blocks")) is not None: + # activity_blocks is not included in the response if the user has a lot of messages + activity_output.append(activity_blocks or "No activity") # Special case when activity_blocks is 0. + else: + activity_output.append("Too many to count!") activity_output = "\n".join( f"{name}: {metric}" for name, metric in zip(["Messages", "Activity blocks"], activity_output) diff --git a/bot/exts/moderation/voice_gate.py b/bot/exts/moderation/voice_gate.py index 31799ec73..ae55a03a0 100644 --- a/bot/exts/moderation/voice_gate.py +++ b/bot/exts/moderation/voice_gate.py @@ -171,8 +171,12 @@ class VoiceGate(Cog): ), "total_messages": data["total_messages"] < GateConf.minimum_messages, "voice_banned": data["voice_banned"], - "activity_blocks": data["activity_blocks"] < GateConf.minimum_activity_blocks } + if activity_blocks := data.get("activity_blocks"): + # activity_blocks is not included in the response if the user has a lot of messages. + # Only check if the user has enough activity blocks if it is included. + checks["activity_blocks"] = activity_blocks < GateConf.minimum_activity_blocks + failed = any(checks.values()) failed_reasons = [MESSAGE_FIELD_MAP[key] for key, value in checks.items() if value is True] [self.bot.stats.incr(f"voice_gate.failed.{key}") for key, value in checks.items() if value is True] -- cgit v1.2.3 From 2024766f182b1491e8d2031e3e26d0830563e578 Mon Sep 17 00:00:00 2001 From: zephyrus <75779179+git-zephyrus@users.noreply.github.com> Date: Wed, 24 Nov 2021 21:57:48 +0530 Subject: Suppress NotFound error when cleaning messages * Added suppress for notfound error * Update clean.py --- bot/exts/moderation/clean.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/exts/moderation/clean.py b/bot/exts/moderation/clean.py index 94494b983..826265aa3 100644 --- a/bot/exts/moderation/clean.py +++ b/bot/exts/moderation/clean.py @@ -293,7 +293,8 @@ class Clean(Cog): return deleted if len(to_delete) > 0: # Deleting any leftover messages if there are any - await channel.delete_messages(to_delete) + with suppress(NotFound): + await channel.delete_messages(to_delete) deleted.extend(to_delete) if not self.cleaning: -- cgit v1.2.3 From 68f27b730c27d58805556291f833030d37446425 Mon Sep 17 00:00:00 2001 From: Ben Soyka Date: Sat, 27 Nov 2021 15:57:49 -0700 Subject: Limit length of the invalid rule indices message --- bot/exts/info/site.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bot/exts/info/site.py b/bot/exts/info/site.py index e8e71558b..3b4b561c7 100644 --- a/bot/exts/info/site.py +++ b/bot/exts/info/site.py @@ -1,3 +1,5 @@ +from textwrap import shorten + from discord import Colour, Embed from discord.ext.commands import Cog, Context, Greedy, group @@ -123,7 +125,7 @@ class Site(Cog): # Remove duplicates and sort the rule indices rules = sorted(set(rules)) - invalid = ', '.join(str(index) for index in rules if index < 1 or index > len(full_rules)) + invalid = shorten(', '.join(str(index) for index in rules if index < 1 or index > len(full_rules)), 50) if invalid: await ctx.send(f":x: Invalid rule indices: {invalid}") -- cgit v1.2.3 From 94944ca0bd3dae1c97483f93563846eea7380dfb Mon Sep 17 00:00:00 2001 From: Ben Soyka Date: Sat, 27 Nov 2021 16:10:40 -0700 Subject: Change placeholder for invalid rules message shortening ... is used everywhere else across the codebase where extwrap.shorten is used, so I'm making it match here. --- bot/exts/info/site.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/exts/info/site.py b/bot/exts/info/site.py index 3b4b561c7..bcb04c909 100644 --- a/bot/exts/info/site.py +++ b/bot/exts/info/site.py @@ -125,7 +125,8 @@ class Site(Cog): # Remove duplicates and sort the rule indices rules = sorted(set(rules)) - invalid = shorten(', '.join(str(index) for index in rules if index < 1 or index > len(full_rules)), 50) + invalid = shorten(', '.join(str(index) for index in rules if index + < 1 or index > len(full_rules)), 50, placeholder='...') if invalid: await ctx.send(f":x: Invalid rule indices: {invalid}") -- cgit v1.2.3 From b57af0e076ef3e7eb1f7035429e56664ddf1ed55 Mon Sep 17 00:00:00 2001 From: Ben Soyka Date: Sun, 28 Nov 2021 01:47:05 -0700 Subject: Use bright_green for "Currently Helping" DMs (#1979) Co-authored-by: Xithrius <15021300+Xithrius@users.noreply.github.com> --- bot/exts/help_channels/_cog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py index 944a99917..0c411df04 100644 --- a/bot/exts/help_channels/_cog.py +++ b/bot/exts/help_channels/_cog.py @@ -590,7 +590,7 @@ class HelpChannels(commands.Cog): embed = discord.Embed( title="Currently Helping", description=f"You're currently helping in {message.channel.mention}", - color=constants.Colours.soft_green, + color=constants.Colours.bright_green, timestamp=message.created_at ) embed.add_field(name="Conversation", value=f"[Jump to message]({message.jump_url})") -- cgit v1.2.3