From 6193ee2152d7dba63bd9d40b9b2a4f56524acbce Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Sun, 14 Aug 2022 20:35:08 +0100 Subject: Globally decode binary responses from redis to strings --- bot/__main__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bot/__main__.py b/bot/__main__.py index e0d2e6ad5..02af2e9ef 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -26,6 +26,7 @@ async def _create_redis_session() -> RedisSession: max_connections=20, use_fakeredis=constants.Redis.use_fakeredis, global_namespace="bot", + decode_responses=True, ) try: return await redis_session.connect() -- cgit v1.2.3 From fce6ea2f845889ab8eb6801e25e56ef0b7e67f3d Mon Sep 17 00:00:00 2001 From: dawnofmidnight Date: Mon, 15 Aug 2022 12:32:46 -0400 Subject: feat: command for banning compromised accounts --- bot/exts/moderation/infraction/infractions.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bot/exts/moderation/infraction/infractions.py b/bot/exts/moderation/infraction/infractions.py index 46fd3381c..679768268 100644 --- a/bot/exts/moderation/infraction/infractions.py +++ b/bot/exts/moderation/infraction/infractions.py @@ -1,6 +1,7 @@ import textwrap import typing as t +import arrow import discord from discord import Member from discord.ext import commands @@ -11,6 +12,7 @@ from bot.bot import Bot from bot.constants import Event from bot.converters import Age, Duration, Expiry, MemberOrUser, UnambiguousMemberOrUser from bot.decorators import ensure_future_timestamp, respect_role_hierarchy +from bot.exts.filters.filtering import AUTO_BAN_DURATION, AUTO_BAN_REASON from bot.exts.moderation.infraction import _utils from bot.exts.moderation.infraction._scheduler import InfractionScheduler from bot.log import get_logger @@ -151,6 +153,11 @@ class Infractions(InfractionScheduler, commands.Cog): ctx.send = send await infr_manage_cog.infraction_append(ctx, infraction, None, reason=f"[Clean log]({log_url})") + @command() + async def compban(self, ctx: Context, user: UnambiguousMemberOrUser) -> None: + """Same as cleanban, but specifically for four days and with the ban reason used for compromised accounts.""" + await self.cleanban(ctx, user, duration=arrow.utcnow() + AUTO_BAN_DURATION, reason=AUTO_BAN_REASON) + @command(aliases=("vban",)) async def voiceban(self, ctx: Context) -> None: """ -- cgit v1.2.3 From 872281ea5faa44cf3659b1678174b1a713555288 Mon Sep 17 00:00:00 2001 From: ionite34 Date: Mon, 15 Aug 2022 14:07:22 -0400 Subject: Added mod alerted notice to auto-infractions --- bot/exts/moderation/infraction/_scheduler.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bot/exts/moderation/infraction/_scheduler.py b/bot/exts/moderation/infraction/_scheduler.py index c7f03b2e9..69c7d7afc 100644 --- a/bot/exts/moderation/infraction/_scheduler.py +++ b/bot/exts/moderation/infraction/_scheduler.py @@ -189,7 +189,10 @@ class InfractionScheduler: f"Infraction #{id_} actor is bot; including the reason in the confirmation message." ) if reason: - end_msg = f" (reason: {textwrap.shorten(reason, width=1500, placeholder='...')})" + end_msg = ( + f" (reason: {textwrap.shorten(reason, width=1500, placeholder='...')})" + " Moderators have been alerted for review" + ) purge = infraction.get("purge", "") -- cgit v1.2.3 From a7234a824f3d4b2d052357d73d23a6e0c5cbb1b6 Mon Sep 17 00:00:00 2001 From: dawnofmidnight Date: Mon, 15 Aug 2022 14:38:01 -0400 Subject: fix: change use of arrow to datetime and change docstring wording --- bot/exts/filters/filtering.py | 4 ++-- bot/exts/moderation/infraction/infractions.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bot/exts/filters/filtering.py b/bot/exts/filters/filtering.py index ca6ad0064..51877d7f0 100644 --- a/bot/exts/filters/filtering.py +++ b/bot/exts/filters/filtering.py @@ -2,7 +2,7 @@ import asyncio import re import unicodedata import urllib.parse -from datetime import timedelta +from datetime import datetime, timedelta from typing import Any, Dict, List, Mapping, NamedTuple, Optional, Tuple, Union import arrow @@ -413,7 +413,7 @@ class Filtering(Cog): await context.invoke( context.command, msg.author, - arrow.utcnow() + AUTO_BAN_DURATION, + datetime.utcnow() + AUTO_BAN_DURATION, reason=AUTO_BAN_REASON ) diff --git a/bot/exts/moderation/infraction/infractions.py b/bot/exts/moderation/infraction/infractions.py index 679768268..c70ff7705 100644 --- a/bot/exts/moderation/infraction/infractions.py +++ b/bot/exts/moderation/infraction/infractions.py @@ -1,7 +1,7 @@ import textwrap import typing as t +from datetime import datetime -import arrow import discord from discord import Member from discord.ext import commands @@ -155,8 +155,8 @@ class Infractions(InfractionScheduler, commands.Cog): @command() async def compban(self, ctx: Context, user: UnambiguousMemberOrUser) -> None: - """Same as cleanban, but specifically for four days and with the ban reason used for compromised accounts.""" - await self.cleanban(ctx, user, duration=arrow.utcnow() + AUTO_BAN_DURATION, reason=AUTO_BAN_REASON) + """Same as cleanban, but specifically with the ban reason and duration used for compromised accounts.""" + await self.cleanban(ctx, user, duration=datetime.utcnow() + AUTO_BAN_DURATION, reason=AUTO_BAN_REASON) @command(aliases=("vban",)) async def voiceban(self, ctx: Context) -> None: -- cgit v1.2.3 From e0b593318eba77d6fe93f2145b43838d6eb09278 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Mon, 15 Aug 2022 22:14:32 +0100 Subject: Correctly initialise redis tests Calling the cog_load from within the setUp function resulted in interaction with a RedisSession before it was initialised. This wasn't noticed in CI as it only error under certain concurrency timings due to xdist. To resolve this, we moved the setup and async setup logic to a base class. Co-authored-by: Hassan Abouelela --- tests/bot/exts/moderation/test_silence.py | 79 +++++++++++++++---------------- 1 file changed, 37 insertions(+), 42 deletions(-) diff --git a/tests/bot/exts/moderation/test_silence.py b/tests/bot/exts/moderation/test_silence.py index 98547e2bc..2622f46a7 100644 --- a/tests/bot/exts/moderation/test_silence.py +++ b/tests/bot/exts/moderation/test_silence.py @@ -1,4 +1,3 @@ -import asyncio import itertools import unittest from datetime import datetime, timezone @@ -23,8 +22,24 @@ class PatchedDatetime(datetime): now = mock.create_autospec(datetime, "now") -class SilenceNotifierTests(unittest.IsolatedAsyncioTestCase): +class SilenceTest(RedisTestCase): + """A base class for Silence tests that correctly sets up the cog and redis.""" + + @autospec(silence, "Scheduler", pass_mocks=False) + @autospec(silence.Silence, "_reschedule", pass_mocks=False) + def setUp(self) -> None: + self.bot = MockBot(get_channel=lambda _id: MockTextChannel(id=_id)) + self.cog = silence.Silence(self.bot) + + @autospec(silence, "SilenceNotifier", pass_mocks=False) + async def asyncSetUp(self) -> None: + await super().asyncSetUp() + await self.cog.cog_load() # Populate instance attributes. + + +class SilenceNotifierTests(SilenceTest): def setUp(self) -> None: + super().setUp() self.alert_channel = MockTextChannel() self.notifier = silence.SilenceNotifier(self.alert_channel) self.notifier.stop = self.notifier_stop_mock = Mock() @@ -89,34 +104,24 @@ class SilenceNotifierTests(unittest.IsolatedAsyncioTestCase): @autospec(silence.Silence, "previous_overwrites", "unsilence_timestamps", pass_mocks=False) -class SilenceCogTests(RedisTestCase): +class SilenceCogTests(SilenceTest): """Tests for the general functionality of the Silence cog.""" - @autospec(silence, "Scheduler", pass_mocks=False) - def setUp(self) -> None: - self.bot = MockBot() - self.cog = silence.Silence(self.bot) - @autospec(silence, "SilenceNotifier", pass_mocks=False) async def test_cog_load_got_guild(self): """Bot got guild after it became available.""" - await self.cog.cog_load() self.bot.wait_until_guild_available.assert_awaited_once() self.bot.get_guild.assert_called_once_with(Guild.id) @autospec(silence, "SilenceNotifier", pass_mocks=False) async def test_cog_load_got_channels(self): """Got channels from bot.""" - self.bot.get_channel.side_effect = lambda id_: MockTextChannel(id=id_) - await self.cog.cog_load() self.assertEqual(self.cog._mod_alerts_channel.id, Channels.mod_alerts) @autospec(silence, "SilenceNotifier") async def test_cog_load_got_notifier(self, notifier): """Notifier was started with channel.""" - self.bot.get_channel.side_effect = lambda id_: MockTextChannel(id=id_) - await self.cog.cog_load() notifier.assert_called_once_with(MockTextChannel(id=Channels.mod_log)) self.assertEqual(self.cog.notifier, notifier.return_value) @@ -229,13 +234,9 @@ class SilenceCogTests(RedisTestCase): self.assertEqual(member.move_to.call_count, 1 if member == failing_member else 2) -class SilenceArgumentParserTests(RedisTestCase): +class SilenceArgumentParserTests(SilenceTest): """Tests for the silence argument parser utility function.""" - def setUp(self): - self.bot = MockBot() - self.cog = silence.Silence(self.bot) - @autospec(silence.Silence, "send_message", pass_mocks=False) @autospec(silence.Silence, "_set_silence_overwrites", return_value=False, pass_mocks=False) @autospec(silence.Silence, "parse_silence_args") @@ -303,17 +304,19 @@ class SilenceArgumentParserTests(RedisTestCase): @autospec(silence.Silence, "previous_overwrites", "unsilence_timestamps", pass_mocks=False) -class RescheduleTests(unittest.IsolatedAsyncioTestCase): +class RescheduleTests(RedisTestCase): """Tests for the rescheduling of cached unsilences.""" - @autospec(silence, "Scheduler", "SilenceNotifier", pass_mocks=False) - def setUp(self): + @autospec(silence, "Scheduler", pass_mocks=False) + def setUp(self) -> None: self.bot = MockBot() self.cog = silence.Silence(self.bot) self.cog._unsilence_wrapper = mock.create_autospec(self.cog._unsilence_wrapper) - with mock.patch.object(self.cog, "_reschedule", autospec=True): - asyncio.run(self.cog.cog_load()) # Populate instance attributes. + @autospec(silence, "SilenceNotifier", pass_mocks=False) + async def asyncSetUp(self) -> None: + await super().asyncSetUp() + await self.cog.cog_load() # Populate instance attributes. async def test_skipped_missing_channel(self): """Did nothing because the channel couldn't be retrieved.""" @@ -388,20 +391,14 @@ def voice_sync_helper(function): @autospec(silence.Silence, "previous_overwrites", "unsilence_timestamps", pass_mocks=False) -class SilenceTests(RedisTestCase): +class SilenceTests(SilenceTest): """Tests for the silence command and its related helper methods.""" - @autospec(silence.Silence, "_reschedule", pass_mocks=False) - @autospec(silence, "Scheduler", "SilenceNotifier", pass_mocks=False) def setUp(self) -> None: - self.bot = MockBot(get_channel=lambda _: MockTextChannel()) - self.cog = silence.Silence(self.bot) + super().setUp() # Avoid unawaited coroutine warnings. self.cog.scheduler.schedule_later.side_effect = lambda delay, task_id, coro: coro.close() - - asyncio.run(self.cog.cog_load()) # Populate instance attributes. - self.text_channel = MockTextChannel() self.text_overwrite = PermissionOverwrite( send_messages=True, @@ -659,22 +656,13 @@ class SilenceTests(RedisTestCase): @autospec(silence.Silence, "unsilence_timestamps", pass_mocks=False) -class UnsilenceTests(unittest.IsolatedAsyncioTestCase): +class UnsilenceTests(SilenceTest): """Tests for the unsilence command and its related helper methods.""" - @autospec(silence.Silence, "_reschedule", pass_mocks=False) - @autospec(silence, "Scheduler", "SilenceNotifier", pass_mocks=False) def setUp(self) -> None: - self.bot = MockBot(get_channel=lambda _: MockTextChannel()) - self.cog = silence.Silence(self.bot) - - overwrites_cache = mock.create_autospec(self.cog.previous_overwrites, spec_set=True) - self.cog.previous_overwrites = overwrites_cache - - asyncio.run(self.cog.cog_load()) # Populate instance attributes. + super().setUp() self.cog.scheduler.__contains__.return_value = True - overwrites_cache.get.return_value = '{"send_messages": true, "add_reactions": false}' self.text_channel = MockTextChannel() self.text_overwrite = PermissionOverwrite(send_messages=False, add_reactions=False) self.text_channel.overwrites_for.return_value = self.text_overwrite @@ -683,6 +671,13 @@ class UnsilenceTests(unittest.IsolatedAsyncioTestCase): self.voice_overwrite = PermissionOverwrite(connect=True, speak=True) self.voice_channel.overwrites_for.return_value = self.voice_overwrite + async def asyncSetUp(self) -> None: + await super().asyncSetUp() + overwrites_cache = mock.create_autospec(self.cog.previous_overwrites, spec_set=True) + self.cog.previous_overwrites = overwrites_cache + + overwrites_cache.get.return_value = '{"send_messages": true, "add_reactions": false}' + async def test_sent_correct_message(self): """Appropriate failure/success message was sent by the command.""" unsilenced_overwrite = PermissionOverwrite(send_messages=True, add_reactions=True) -- cgit v1.2.3 From e68868e253128b9eb2035e5ea1bae93e67bda4f1 Mon Sep 17 00:00:00 2001 From: ionite34 Date: Tue, 16 Aug 2022 16:29:28 -0400 Subject: Added newlines and non-mentioned mod role --- bot/exts/moderation/infraction/_scheduler.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bot/exts/moderation/infraction/_scheduler.py b/bot/exts/moderation/infraction/_scheduler.py index 69c7d7afc..0f8a55838 100644 --- a/bot/exts/moderation/infraction/_scheduler.py +++ b/bot/exts/moderation/infraction/_scheduler.py @@ -12,7 +12,7 @@ from discord.ext.commands import Context from bot import constants from bot.bot import Bot -from bot.constants import Colours +from bot.constants import Colours, Roles from bot.converters import MemberOrUser from bot.exts.moderation.infraction import _utils from bot.exts.moderation.modlog import ModLog @@ -190,8 +190,8 @@ class InfractionScheduler: ) if reason: end_msg = ( - f" (reason: {textwrap.shorten(reason, width=1500, placeholder='...')})" - " Moderators have been alerted for review" + f" (reason: {textwrap.shorten(reason, width=1500, placeholder='...')})." + f"\n\n<@&{Roles.moderators}> have been alerted for review" ) purge = infraction.get("purge", "") @@ -246,7 +246,8 @@ class InfractionScheduler: # Send a confirmation message to the invoking context. log.trace(f"Sending infraction #{id_} confirmation message.") - await ctx.send(f"{dm_result}{confirm_msg}{infr_message}.") + mentions = discord.AllowedMentions(users=[user], roles=False) + await ctx.send(f"{dm_result}{confirm_msg}{infr_message}.", allowed_mentions=mentions) # Send a log message to the mod log. # Don't use ctx.message.author for the actor; antispam only patches ctx.author. -- cgit v1.2.3 From c9d5aac1d71bdbee672355e75ee8e655892c8b6c Mon Sep 17 00:00:00 2001 From: ionite34 Date: Tue, 16 Aug 2022 17:35:44 -0400 Subject: Added article to automute message --- bot/exts/moderation/infraction/_scheduler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/moderation/infraction/_scheduler.py b/bot/exts/moderation/infraction/_scheduler.py index 0f8a55838..280b0fb0c 100644 --- a/bot/exts/moderation/infraction/_scheduler.py +++ b/bot/exts/moderation/infraction/_scheduler.py @@ -191,7 +191,7 @@ class InfractionScheduler: if reason: end_msg = ( f" (reason: {textwrap.shorten(reason, width=1500, placeholder='...')})." - f"\n\n<@&{Roles.moderators}> have been alerted for review" + f"\n\nThe <@&{Roles.moderators}> have been alerted for review" ) purge = infraction.get("purge", "") -- cgit v1.2.3 From 4eaecb32830aa122cd53656de562caf20b79e33d Mon Sep 17 00:00:00 2001 From: dawnofmidnight Date: Wed, 17 Aug 2022 17:02:25 -0400 Subject: fix: replace datetime.utcnow() with arrow --- bot/exts/filters/filtering.py | 4 ++-- bot/exts/moderation/infraction/infractions.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bot/exts/filters/filtering.py b/bot/exts/filters/filtering.py index 51877d7f0..e4df0b1fd 100644 --- a/bot/exts/filters/filtering.py +++ b/bot/exts/filters/filtering.py @@ -2,7 +2,7 @@ import asyncio import re import unicodedata import urllib.parse -from datetime import datetime, timedelta +from datetime import timedelta from typing import Any, Dict, List, Mapping, NamedTuple, Optional, Tuple, Union import arrow @@ -413,7 +413,7 @@ class Filtering(Cog): await context.invoke( context.command, msg.author, - datetime.utcnow() + AUTO_BAN_DURATION, + (arrow.utcnow() + AUTO_BAN_DURATION).datetime, reason=AUTO_BAN_REASON ) diff --git a/bot/exts/moderation/infraction/infractions.py b/bot/exts/moderation/infraction/infractions.py index c70ff7705..08a3609a7 100644 --- a/bot/exts/moderation/infraction/infractions.py +++ b/bot/exts/moderation/infraction/infractions.py @@ -1,7 +1,7 @@ import textwrap import typing as t -from datetime import datetime +import arrow import discord from discord import Member from discord.ext import commands @@ -156,7 +156,7 @@ class Infractions(InfractionScheduler, commands.Cog): @command() async def compban(self, ctx: Context, user: UnambiguousMemberOrUser) -> None: """Same as cleanban, but specifically with the ban reason and duration used for compromised accounts.""" - await self.cleanban(ctx, user, duration=datetime.utcnow() + AUTO_BAN_DURATION, reason=AUTO_BAN_REASON) + await self.cleanban(ctx, user, duration=(arrow.utcnow() + AUTO_BAN_DURATION).datetime, reason=AUTO_BAN_REASON) @command(aliases=("vban",)) async def voiceban(self, ctx: Context) -> None: -- cgit v1.2.3