diff options
-rw-r--r-- | bot/cogs/clean.py | 78 | ||||
-rw-r--r-- | bot/cogs/moderation/modlog.py | 35 |
2 files changed, 73 insertions, 40 deletions
diff --git a/bot/cogs/clean.py b/bot/cogs/clean.py index b5d9132cb..892c638b8 100644 --- a/bot/cogs/clean.py +++ b/bot/cogs/clean.py @@ -1,16 +1,16 @@ import logging import random import re -from typing import Optional +from typing import Iterable, Optional from discord import Colour, Embed, Message, TextChannel, User +from discord.ext import commands from discord.ext.commands import Cog, Context, group from bot.bot import Bot from bot.cogs.moderation import ModLog from bot.constants import ( - Channels, CleanMessages, Colours, Event, - Icons, MODERATION_ROLES, NEGATIVE_REPLIES + Channels, CleanMessages, Colours, Icons, MODERATION_ROLES, NEGATIVE_REPLIES ) from bot.decorators import with_role @@ -41,10 +41,10 @@ class Clean(Cog): self, amount: int, ctx: Context, + channels: Iterable[TextChannel], bots_only: bool = False, user: User = None, regex: Optional[str] = None, - channel: Optional[TextChannel] = None ) -> None: """A helper function that does the actual message cleaning.""" def predicate_bots_only(message: Message) -> bool: @@ -110,44 +110,40 @@ class Clean(Cog): predicate = None # Delete all messages # Default to using the invoking context's channel - if not channel: - channel = ctx.channel + if not channels: + channels = [ctx.channel] + + # Delete the invocation first + with self.mod_log.ignore_all(): + await ctx.message.delete() # Look through the history and retrieve message data + # This is only done so we can create a log to upload. messages = [] message_ids = [] self.cleaning = True - invocation_deleted = False - - # To account for the invocation message, we index `amount + 1` messages. - async for message in channel.history(limit=amount + 1): - # If at any point the cancel command is invoked, we should stop. - if not self.cleaning: - return + for channel in channels: + async for message in channel.history(limit=amount): - # Always start by deleting the invocation - if not invocation_deleted: - self.mod_log.ignore(Event.message_delete, message.id) - await message.delete() - invocation_deleted = True - continue + # If at any point the cancel command is invoked, we should stop. + if not self.cleaning: + return - # If the message passes predicate, let's save it. - if predicate is None or predicate(message): - message_ids.append(message.id) - messages.append(message) + # If the message passes predicate, let's save it. + if predicate is None or predicate(message): + message_ids.append(message.id) + messages.append(message) self.cleaning = False - # We should ignore the ID's we stored, so we don't get mod-log spam. - self.mod_log.ignore(Event.message_delete, *message_ids) - - # Use bulk delete to actually do the cleaning. It's far faster. - await channel.purge( - limit=amount, - check=predicate - ) + # Now let's delete the actual messages with purge. + with self.mod_log.ignore_all(): + for channel in channels: + await channel.purge( + limit=amount, + check=predicate + ) # Reverse the list to restore chronological order if messages: @@ -163,8 +159,10 @@ class Clean(Cog): return # Build the embed and send it + target_channels = ", ".join(channel.mention for channel in channels) + message = ( - f"**{len(message_ids)}** messages deleted in <#{channel.id}> by **{ctx.author.name}**\n\n" + f"**{len(message_ids)}** messages deleted in {target_channels} by **{ctx.author.name}**\n\n" f"A log of the deleted messages can be found [here]({log_url})." ) @@ -189,10 +187,10 @@ class Clean(Cog): ctx: Context, user: User, amount: Optional[int] = 10, - channel: TextChannel = None + channels: commands.Greedy[TextChannel] = None ) -> None: """Delete messages posted by the provided user, stop cleaning after traversing `amount` messages.""" - await self._clean_messages(amount, ctx, user=user, channel=channel) + await self._clean_messages(amount, ctx, user=user, channels=channels) @clean_group.command(name="all", aliases=["everything"]) @with_role(*MODERATION_ROLES) @@ -200,10 +198,10 @@ class Clean(Cog): self, ctx: Context, amount: Optional[int] = 10, - channel: TextChannel = None + channels: commands.Greedy[TextChannel] = None ) -> None: """Delete all messages, regardless of poster, stop cleaning after traversing `amount` messages.""" - await self._clean_messages(amount, ctx, channel=channel) + await self._clean_messages(amount, ctx, channels=channels) @clean_group.command(name="bots", aliases=["bot"]) @with_role(*MODERATION_ROLES) @@ -211,10 +209,10 @@ class Clean(Cog): self, ctx: Context, amount: Optional[int] = 10, - channel: TextChannel = None + channels: commands.Greedy[TextChannel] = None ) -> None: """Delete all messages posted by a bot, stop cleaning after traversing `amount` messages.""" - await self._clean_messages(amount, ctx, bots_only=True, channel=channel) + await self._clean_messages(amount, ctx, bots_only=True, channels=channels) @clean_group.command(name="regex", aliases=["word", "expression"]) @with_role(*MODERATION_ROLES) @@ -223,10 +221,10 @@ class Clean(Cog): ctx: Context, regex: str, amount: Optional[int] = 10, - channel: TextChannel = None + channels: commands.Greedy[TextChannel] = None ) -> None: """Delete all messages that match a certain regex, stop cleaning after traversing `amount` messages.""" - await self._clean_messages(amount, ctx, regex=regex, channel=channel) + await self._clean_messages(amount, ctx, regex=regex, channels=channels) @clean_group.command(name="stop", aliases=["cancel", "abort"]) @with_role(*MODERATION_ROLES) diff --git a/bot/cogs/moderation/modlog.py b/bot/cogs/moderation/modlog.py index 9d28030d9..b3ae8e215 100644 --- a/bot/cogs/moderation/modlog.py +++ b/bot/cogs/moderation/modlog.py @@ -3,6 +3,7 @@ import difflib import itertools import logging import typing as t +from contextlib import contextmanager from datetime import datetime from itertools import zip_longest @@ -40,6 +41,7 @@ class ModLog(Cog, name="ModLog"): def __init__(self, bot: Bot): self.bot = bot self._ignored = {event: [] for event in Event} + self._ignore_all = False self._cached_deletes = [] self._cached_edits = [] @@ -81,6 +83,15 @@ class ModLog(Cog, name="ModLog"): if item not in self._ignored[event]: self._ignored[event].append(item) + @contextmanager + def ignore_all(self) -> None: + """Ignore all events while inside this context scope.""" + self._ignore_all = True + try: + yield + finally: + self._ignore_all = False + async def send_log_message( self, icon_url: t.Optional[str], @@ -191,6 +202,9 @@ class ModLog(Cog, name="ModLog"): self._ignored[Event.guild_channel_update].remove(before.id) return + if self._ignore_all: + return + # Two channel updates are sent for a single edit: 1 for topic and 1 for category change. # TODO: remove once support is added for ignoring multiple occurrences for the same channel. help_categories = (Categories.help_available, Categories.help_dormant, Categories.help_in_use) @@ -386,6 +400,9 @@ class ModLog(Cog, name="ModLog"): self._ignored[Event.member_ban].remove(member.id) return + if self._ignore_all: + return + await self.send_log_message( Icons.user_ban, Colours.soft_red, "User banned", f"{member} (`{member.id}`)", @@ -426,6 +443,9 @@ class ModLog(Cog, name="ModLog"): self._ignored[Event.member_remove].remove(member.id) return + if self._ignore_all: + return + member_str = escape_markdown(str(member)) await self.send_log_message( Icons.sign_out, Colours.soft_red, @@ -444,6 +464,9 @@ class ModLog(Cog, name="ModLog"): self._ignored[Event.member_unban].remove(member.id) return + if self._ignore_all: + return + member_str = escape_markdown(str(member)) await self.send_log_message( Icons.user_unban, Colour.blurple(), @@ -462,6 +485,9 @@ class ModLog(Cog, name="ModLog"): self._ignored[Event.member_update].remove(before.id) return + if self._ignore_all: + return + diff = DeepDiff(before, after) changes = [] done = [] @@ -564,6 +590,9 @@ class ModLog(Cog, name="ModLog"): self._ignored[Event.message_delete].remove(message.id) return + if self._ignore_all: + return + if author.bot: return @@ -623,6 +652,9 @@ class ModLog(Cog, name="ModLog"): self._ignored[Event.message_delete].remove(event.message_id) return + if self._ignore_all: + return + channel = self.bot.get_channel(event.channel_id) if channel.category: @@ -797,6 +829,9 @@ class ModLog(Cog, name="ModLog"): self._ignored[Event.voice_state_update].remove(member.id) return + if self._ignore_all: + return + # Exclude all channel attributes except the name. diff = DeepDiff( before, |