From 88e65f60437dfa9caf2064487e7294b4f029e2f6 Mon Sep 17 00:00:00 2001 From: mbaruh Date: Thu, 2 Dec 2021 21:06:27 +0200 Subject: Remove cleaning based on number of messages All clean commands now use the clean limit (message, time delta, ISO datetime) instead of `traverse`. Consequently, `clean all` has been removed as `clean until` now effectively fulfills that role. --- bot/exts/moderation/clean.py | 93 +++++++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 49 deletions(-) diff --git a/bot/exts/moderation/clean.py b/bot/exts/moderation/clean.py index 826265aa3..8ad7a56d8 100644 --- a/bot/exts/moderation/clean.py +++ b/bot/exts/moderation/clean.py @@ -5,7 +5,6 @@ import time from collections import defaultdict from contextlib import suppress from datetime import datetime -from itertools import islice from typing import Any, Callable, Iterable, Literal, Optional, TYPE_CHECKING, Union from discord import Colour, Message, NotFound, TextChannel, User, errors @@ -21,8 +20,6 @@ from bot.utils.channel import is_mod_channel log = logging.getLogger(__name__) -# Default number of messages to look at in each channel. -DEFAULT_TRAVERSE = 10 # Number of seconds before command invocations and responses are deleted in non-moderation channels. MESSAGE_DELETE_DELAY = 5 @@ -87,7 +84,6 @@ class Clean(Cog): @staticmethod def _validate_input( - traverse: int, channels: Optional[CleanChannels], bots_only: bool, users: Optional[list[User]], @@ -95,9 +91,9 @@ class Clean(Cog): second_limit: Optional[CleanLimit], ) -> None: """Raise errors if an argument value or a combination of values is invalid.""" - # Is this an acceptable amount of messages to traverse? - if traverse > CleanMessages.message_limit: - raise BadArgument(f"Cannot traverse more than {CleanMessages.message_limit} messages.") + if first_limit is None: + # This is an optional argument for the sake of the master command, but it's actually required. + raise BadArgument("Missing cleaning limit.") if (isinstance(first_limit, Message) or isinstance(second_limit, Message)) and channels: raise BadArgument("Both a message limit and channels specified.") @@ -195,11 +191,11 @@ class Clean(Cog): # Invocation message has already been deleted log.info("Tried to delete invocation message, but it was already deleted.") - def _get_messages_from_cache(self, traverse: int, to_delete: Predicate) -> tuple[defaultdict[Any, list], list[int]]: + def _get_messages_from_cache(self, to_delete: Predicate) -> tuple[defaultdict[Any, list], list[int]]: """Helper function for getting messages from the cache.""" message_mappings = defaultdict(list) message_ids = [] - for message in islice(self.bot.cached_messages, traverse): + for message in self.bot.cached_messages: if not self.cleaning: # Cleaning was canceled return message_mappings, message_ids @@ -212,17 +208,16 @@ class Clean(Cog): async def _get_messages_from_channels( self, - traverse: int, channels: Iterable[TextChannel], to_delete: Predicate, - before: Optional[datetime] = None, + before: datetime, after: Optional[datetime] = None ) -> tuple[defaultdict[Any, list], list]: message_mappings = defaultdict(list) message_ids = [] for channel in channels: - async for message in channel.history(limit=traverse, before=before, after=after): + async for message in channel.history(limit=CleanMessages.message_limit, before=before, after=after): if not self.cleaning: # Cleaning was canceled, return empty containers. @@ -343,7 +338,6 @@ class Clean(Cog): async def _clean_messages( self, ctx: Context, - traverse: int, channels: Optional[CleanChannels], bots_only: bool = False, users: Optional[list[User]] = None, @@ -353,7 +347,7 @@ class Clean(Cog): use_cache: Optional[bool] = True ) -> None: """A helper function that does the actual message cleaning.""" - self._validate_input(traverse, channels, bots_only, users, first_limit, second_limit) + self._validate_input(channels, bots_only, users, first_limit, second_limit) # Are we already performing a clean? if self.cleaning: @@ -387,13 +381,12 @@ class Clean(Cog): await self._delete_invocation(ctx) if channels == "*" and use_cache: - message_mappings, message_ids = self._get_messages_from_cache(traverse=traverse, to_delete=predicate) + message_mappings, message_ids = self._get_messages_from_cache(to_delete=predicate) else: deletion_channels = channels if channels == "*": deletion_channels = [channel for channel in ctx.guild.channels if isinstance(channel, TextChannel)] message_mappings, message_ids = await self._get_messages_from_channels( - traverse=traverse, channels=deletion_channels, to_delete=predicate, before=second_limit, @@ -422,7 +415,6 @@ class Clean(Cog): self, ctx: Context, users: Greedy[User] = None, - traverse: Optional[int] = None, first_limit: Optional[CleanLimit] = None, second_limit: Optional[CleanLimit] = None, use_cache: Optional[bool] = None, @@ -437,11 +429,9 @@ class Clean(Cog): If arguments are provided, will act as a master command from which all subcommands can be derived. \u2003• `users`: A series of user mentions, ID's, or names. - \u2003• `traverse`: The number of messages to look at in each channel. If using the cache, will look at the - first `traverse` messages in the cache. \u2003• `first_limit` and `second_limit`: A message, a duration delta, or an ISO datetime. + At least one limit is required. If a message is provided, cleaning will happen in that channel, and channels cannot be provided. - If a limit is provided, multiple channels cannot be provided. If only one of them is provided, acts as `clean until`. If both are provided, acts as `clean between`. \u2003• `use_cache`: Whether to use the message cache. If not provided, will default to False unless an asterisk is used for the channels. @@ -451,20 +441,15 @@ class Clean(Cog): If the pattern contains spaces, it still needs to be enclosed in double quotes on top of that. \u2003• `channels`: A series of channels to delete in, or an asterisk to delete from all channels. """ - if not any([traverse, users, first_limit, second_limit, regex, channels]): + if not any([users, first_limit, second_limit, regex, channels]): await ctx.send_help(ctx.command) return - if not traverse: - if first_limit: - traverse = CleanMessages.message_limit - else: - traverse = DEFAULT_TRAVERSE if use_cache is None: use_cache = channels == "*" await self._clean_messages( - ctx, traverse, channels, bots_only, users, regex, first_limit, second_limit, use_cache + ctx, channels, bots_only, users, regex, first_limit, second_limit, use_cache ) @clean_group.command(name="user", aliases=["users"]) @@ -472,56 +457,68 @@ class Clean(Cog): self, ctx: Context, user: User, - traverse: Optional[int] = DEFAULT_TRAVERSE, + message_or_time: CleanLimit, use_cache: Optional[bool] = True, *, channels: CleanChannels = None ) -> None: - """Delete messages posted by the provided user, stop cleaning after traversing `traverse` messages.""" - await self._clean_messages(ctx, traverse, users=[user], channels=channels, use_cache=use_cache) + """ + Delete messages posted by the provided user, stop cleaning after reaching `message_or_time`. - @clean_group.command(name="all", aliases=["everything"]) - async def clean_all( - self, - ctx: Context, - traverse: Optional[int] = DEFAULT_TRAVERSE, - use_cache: Optional[bool] = True, - *, - channels: CleanChannels = None - ) -> None: - """Delete all messages, regardless of poster, stop cleaning after traversing `traverse` messages.""" - await self._clean_messages(ctx, traverse, channels=channels, use_cache=use_cache) + `message_or_time` can be either a message to stop at (exclusive), a timedelta for max message age, or an ISO + datetime. + + If a message is specified, `channels` cannot be specified. + """ + await self._clean_messages( + ctx, users=[user], channels=channels, first_limit=message_or_time, use_cache=use_cache + ) @clean_group.command(name="bots", aliases=["bot"]) async def clean_bots( self, ctx: Context, - traverse: Optional[int] = DEFAULT_TRAVERSE, + message_or_time: CleanLimit, use_cache: Optional[bool] = True, *, channels: CleanChannels = None ) -> None: - """Delete all messages posted by a bot, stop cleaning after traversing `traverse` messages.""" - await self._clean_messages(ctx, traverse, bots_only=True, channels=channels, use_cache=use_cache) + """ + Delete all messages posted by a bot, stop cleaning after traversing `traverse` messages. + + `message_or_time` can be either a message to stop at (exclusive), a timedelta for max message age, or an ISO + datetime. + + If a message is specified, `channels` cannot be specified. + """ + await self._clean_messages( + ctx, bots_only=True, channels=channels, first_limit=message_or_time, use_cache=use_cache + ) @clean_group.command(name="regex", aliases=["word", "expression", "pattern"]) async def clean_regex( self, ctx: Context, regex: Regex, - traverse: Optional[int] = DEFAULT_TRAVERSE, + message_or_time: CleanLimit, use_cache: Optional[bool] = True, *, channels: CleanChannels = None ) -> None: """ - Delete all messages that match a certain regex, stop cleaning after traversing `traverse` messages. + Delete all messages that match a certain regex, stop cleaning after reaching `message_or_time`. + + `message_or_time` can be either a message to stop at (exclusive), a timedelta for max message age, or an ISO + datetime. + If a message is specified, `channels` cannot be specified. The pattern must be provided enclosed in backticks. If the pattern contains spaces, it still needs to be enclosed in double quotes on top of that. For example: `[0-9]` """ - await self._clean_messages(ctx, traverse, regex=regex, channels=channels, use_cache=use_cache) + await self._clean_messages( + ctx, regex=regex, channels=channels, first_limit=message_or_time, use_cache=use_cache + ) @clean_group.command(name="until") async def clean_until( @@ -538,7 +535,6 @@ class Clean(Cog): """ await self._clean_messages( ctx, - CleanMessages.message_limit, channels=[channel] if channel else None, first_limit=until, ) @@ -562,7 +558,6 @@ class Clean(Cog): """ await self._clean_messages( ctx, - CleanMessages.message_limit, channels=[channel] if channel else None, first_limit=first_limit, second_limit=second_limit, -- cgit v1.2.3 From 0df94eac77703943221e39b9e9898515e266a9ef Mon Sep 17 00:00:00 2001 From: mbaruh Date: Thu, 2 Dec 2021 22:21:35 +0200 Subject: Simplify cache usage Removes the cache usage argument from the clean commands. Cache usage is now an implementation detail. The cache will be used if the age of the oldest message requested for cleaning is younger than the oldest message in the cache. Additionally fixes the logger to the one used in the rest of the bot (caused by a faulty merge). --- bot/exts/moderation/clean.py | 65 ++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 39 deletions(-) diff --git a/bot/exts/moderation/clean.py b/bot/exts/moderation/clean.py index 8ad7a56d8..bb6e44d6f 100644 --- a/bot/exts/moderation/clean.py +++ b/bot/exts/moderation/clean.py @@ -1,11 +1,11 @@ import contextlib -import logging import re import time from collections import defaultdict from contextlib import suppress from datetime import datetime -from typing import Any, Callable, Iterable, Literal, Optional, TYPE_CHECKING, Union +from itertools import takewhile +from typing import Callable, Iterable, Literal, Optional, TYPE_CHECKING, Union from discord import Colour, Message, NotFound, TextChannel, User, errors from discord.ext.commands import Cog, Context, Converter, Greedy, group, has_any_role @@ -16,9 +16,10 @@ from bot.bot import Bot from bot.constants import Channels, CleanMessages, Colours, Emojis, Event, Icons, MODERATION_ROLES from bot.converters import Age, ISODateTime from bot.exts.moderation.modlog import ModLog +from bot.log import get_logger from bot.utils.channel import is_mod_channel -log = logging.getLogger(__name__) +log = get_logger(__name__) # Number of seconds before command invocations and responses are deleted in non-moderation channels. MESSAGE_DELETE_DELAY = 5 @@ -191,11 +192,19 @@ class Clean(Cog): # Invocation message has already been deleted log.info("Tried to delete invocation message, but it was already deleted.") - def _get_messages_from_cache(self, to_delete: Predicate) -> tuple[defaultdict[Any, list], list[int]]: + def _use_cache(self, limit: datetime) -> bool: + """Tell whether all messages to be cleaned can be found in the cache.""" + return self.bot.cached_messages[0].created_at <= limit + + def _get_messages_from_cache( + self, + to_delete: Predicate, + lower_limit: datetime + ) -> tuple[defaultdict[TextChannel, list], list[int]]: """Helper function for getting messages from the cache.""" message_mappings = defaultdict(list) message_ids = [] - for message in self.bot.cached_messages: + for message in takewhile(lambda m: m.created_at > lower_limit, reversed(self.bot.cached_messages)): if not self.cleaning: # Cleaning was canceled return message_mappings, message_ids @@ -212,7 +221,7 @@ class Clean(Cog): to_delete: Predicate, before: datetime, after: Optional[datetime] = None - ) -> tuple[defaultdict[Any, list], list]: + ) -> tuple[defaultdict[TextChannel, list], list]: message_mappings = defaultdict(list) message_ids = [] @@ -344,7 +353,6 @@ class Clean(Cog): regex: Optional[re.Pattern] = None, first_limit: Optional[CleanLimit] = None, second_limit: Optional[CleanLimit] = None, - use_cache: Optional[bool] = True ) -> None: """A helper function that does the actual message cleaning.""" self._validate_input(channels, bots_only, users, first_limit, second_limit) @@ -380,9 +388,11 @@ class Clean(Cog): # Delete the invocation first await self._delete_invocation(ctx) - if channels == "*" and use_cache: - message_mappings, message_ids = self._get_messages_from_cache(to_delete=predicate) + if self._use_cache(first_limit): + log.trace(f"Messages for cleaning by {ctx.author.id} will be searched in the cache.") + message_mappings, message_ids = self._get_messages_from_cache(to_delete=predicate, lower_limit=first_limit) else: + log.trace(f"Messages for cleaning by {ctx.author.id} will be searched in channel histories.") deletion_channels = channels if channels == "*": deletion_channels = [channel for channel in ctx.guild.channels if isinstance(channel, TextChannel)] @@ -417,9 +427,8 @@ class Clean(Cog): users: Greedy[User] = None, first_limit: Optional[CleanLimit] = None, second_limit: Optional[CleanLimit] = None, - use_cache: Optional[bool] = None, - bots_only: Optional[bool] = False, regex: Optional[Regex] = None, + bots_only: Optional[bool] = False, *, channels: CleanChannels = None # "Optional" with discord.py silently ignores incorrect input. ) -> None: @@ -433,24 +442,17 @@ class Clean(Cog): At least one limit is required. If a message is provided, cleaning will happen in that channel, and channels cannot be provided. If only one of them is provided, acts as `clean until`. If both are provided, acts as `clean between`. - \u2003• `use_cache`: Whether to use the message cache. - If not provided, will default to False unless an asterisk is used for the channels. - \u2003• `bots_only`: Whether to delete only bots. If specified, users cannot be specified. \u2003• `regex`: A regex pattern the message must contain to be deleted. The pattern must be provided enclosed in backticks. If the pattern contains spaces, it still needs to be enclosed in double quotes on top of that. + \u2003• `bots_only`: Whether to delete only bots. If specified, users cannot be specified. \u2003• `channels`: A series of channels to delete in, or an asterisk to delete from all channels. """ if not any([users, first_limit, second_limit, regex, channels]): await ctx.send_help(ctx.command) return - if use_cache is None: - use_cache = channels == "*" - - await self._clean_messages( - ctx, channels, bots_only, users, regex, first_limit, second_limit, use_cache - ) + await self._clean_messages(ctx, channels, bots_only, users, regex, first_limit, second_limit) @clean_group.command(name="user", aliases=["users"]) async def clean_user( @@ -458,7 +460,6 @@ class Clean(Cog): ctx: Context, user: User, message_or_time: CleanLimit, - use_cache: Optional[bool] = True, *, channels: CleanChannels = None ) -> None: @@ -470,19 +471,10 @@ class Clean(Cog): If a message is specified, `channels` cannot be specified. """ - await self._clean_messages( - ctx, users=[user], channels=channels, first_limit=message_or_time, use_cache=use_cache - ) + await self._clean_messages(ctx, users=[user], channels=channels, first_limit=message_or_time) @clean_group.command(name="bots", aliases=["bot"]) - async def clean_bots( - self, - ctx: Context, - message_or_time: CleanLimit, - use_cache: Optional[bool] = True, - *, - channels: CleanChannels = None - ) -> None: + async def clean_bots(self, ctx: Context, message_or_time: CleanLimit, *, channels: CleanChannels = None) -> None: """ Delete all messages posted by a bot, stop cleaning after traversing `traverse` messages. @@ -491,9 +483,7 @@ class Clean(Cog): If a message is specified, `channels` cannot be specified. """ - await self._clean_messages( - ctx, bots_only=True, channels=channels, first_limit=message_or_time, use_cache=use_cache - ) + await self._clean_messages(ctx, bots_only=True, channels=channels, first_limit=message_or_time) @clean_group.command(name="regex", aliases=["word", "expression", "pattern"]) async def clean_regex( @@ -501,7 +491,6 @@ class Clean(Cog): ctx: Context, regex: Regex, message_or_time: CleanLimit, - use_cache: Optional[bool] = True, *, channels: CleanChannels = None ) -> None: @@ -516,9 +505,7 @@ class Clean(Cog): If the pattern contains spaces, it still needs to be enclosed in double quotes on top of that. For example: `[0-9]` """ - await self._clean_messages( - ctx, regex=regex, channels=channels, first_limit=message_or_time, use_cache=use_cache - ) + await self._clean_messages(ctx, regex=regex, channels=channels, first_limit=message_or_time) @clean_group.command(name="until") async def clean_until( -- cgit v1.2.3 From 89f991374b1d0e9d9f7312c3c715129a8bba6ac2 Mon Sep 17 00:00:00 2001 From: mbaruh Date: Thu, 2 Dec 2021 22:34:06 +0200 Subject: Update _build_predicate to require a limit --- bot/exts/moderation/clean.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/bot/exts/moderation/clean.py b/bot/exts/moderation/clean.py index bb6e44d6f..a08788fd6 100644 --- a/bot/exts/moderation/clean.py +++ b/bot/exts/moderation/clean.py @@ -119,11 +119,11 @@ class Clean(Cog): @staticmethod def _build_predicate( + first_limit: datetime, + second_limit: Optional[datetime] = None, bots_only: bool = False, users: Optional[list[User]] = None, regex: Optional[re.Pattern] = None, - first_limit: Optional[datetime] = None, - second_limit: Optional[datetime] = None, ) -> Predicate: """Return the predicate that decides whether to delete a given message.""" def predicate_bots_only(message: Message) -> bool: @@ -164,20 +164,18 @@ class Clean(Cog): predicates = [] # Set up the correct predicate + if second_limit: + predicates.append(predicate_range) # Delete messages in the specified age range + else: + predicates.append(predicate_after) # Delete messages older than the specified age + if bots_only: predicates.append(predicate_bots_only) # Delete messages from bots if users: predicates.append(predicate_specific_users) # Delete messages from specific user if regex: predicates.append(predicate_regex) # Delete messages that match regex - # Add up to one of the following: - if second_limit: - predicates.append(predicate_range) # Delete messages in the specified age range - elif first_limit: - predicates.append(predicate_after) # Delete messages older than specific message - if not predicates: - return lambda m: True if len(predicates) == 1: return predicates[0] return lambda m: all(pred(m) for pred in predicates) @@ -383,7 +381,7 @@ class Clean(Cog): first_limit, second_limit = sorted([first_limit, second_limit]) # Needs to be called after standardizing the input. - predicate = self._build_predicate(bots_only, users, regex, first_limit, second_limit) + predicate = self._build_predicate(first_limit, second_limit, bots_only, users, regex) # Delete the invocation first await self._delete_invocation(ctx) -- cgit v1.2.3 From 20eecf06513aaff02a6c8531d90cff0b3b7addce Mon Sep 17 00:00:00 2001 From: mbaruh Date: Fri, 3 Dec 2021 15:32:17 +0200 Subject: Remove now redundant input check. --- bot/exts/moderation/clean.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/bot/exts/moderation/clean.py b/bot/exts/moderation/clean.py index a08788fd6..3def2a416 100644 --- a/bot/exts/moderation/clean.py +++ b/bot/exts/moderation/clean.py @@ -107,10 +107,6 @@ class Clean(Cog): if users and bots_only: raise BadArgument("Marked as bots only, but users were specified.") - # This is an implementation error rather than user error. - if second_limit and not first_limit: - raise ValueError("Second limit specified without the first.") - @staticmethod async def _send_expiring_message(ctx: Context, content: str) -> None: """Send `content` to the context channel. Automatically delete if it's not a mod channel.""" -- cgit v1.2.3 From ed3f5aaec5ac87a6d92d8068d9e07190adc3a5d2 Mon Sep 17 00:00:00 2001 From: mbaruh Date: Sun, 5 Dec 2021 23:49:46 +0200 Subject: Properly check the channel when deleting from cache Previously the cache was only used to delete from all channels. I didn't add a channels check when I changed it. --- bot/exts/moderation/clean.py | 44 ++++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/bot/exts/moderation/clean.py b/bot/exts/moderation/clean.py index 3def2a416..0b83fc7e0 100644 --- a/bot/exts/moderation/clean.py +++ b/bot/exts/moderation/clean.py @@ -113,6 +113,28 @@ class Clean(Cog): delete_after = None if is_mod_channel(ctx.channel) else MESSAGE_DELETE_DELAY await ctx.send(content, delete_after=delete_after) + @staticmethod + def _channels_set( + channels: CleanChannels, ctx: Context, first_limit: CleanLimit, second_limit: CleanLimit + ) -> set[TextChannel]: + """Standardize the input `channels` argument to a usable set of text channels.""" + # Default to using the invoking context's channel or the channel of the message limit(s). + if not channels: + # Input was validated - if first_limit is a message, second_limit won't point at a different channel. + if isinstance(first_limit, Message): + channels = {first_limit.channel} + elif isinstance(second_limit, Message): + channels = {second_limit.channel} + else: + channels = {ctx.channel} + else: + if channels == "*": + channels = {channel for channel in ctx.guild.channels if isinstance(channel, TextChannel)} + else: + channels = set(channels) + + return channels + @staticmethod def _build_predicate( first_limit: datetime, @@ -192,6 +214,7 @@ class Clean(Cog): def _get_messages_from_cache( self, + channels: set[TextChannel], to_delete: Predicate, lower_limit: datetime ) -> tuple[defaultdict[TextChannel, list], list[int]]: @@ -203,7 +226,7 @@ class Clean(Cog): # Cleaning was canceled return message_mappings, message_ids - if to_delete(message): + if message.channel in channels and to_delete(message): message_mappings[message.channel].append(message) message_ids.append(message.id) @@ -359,15 +382,7 @@ class Clean(Cog): return self.cleaning = True - # Default to using the invoking context's channel or the channel of the message limit(s). - if not channels: - # Input was validated - if first_limit is a message, second_limit won't point at a different channel. - if isinstance(first_limit, Message): - channels = [first_limit.channel] - elif isinstance(second_limit, Message): - channels = [second_limit.channel] - else: - channels = [ctx.channel] + deletion_channels = self._channels_set(channels, ctx, first_limit, second_limit) if isinstance(first_limit, Message): first_limit = first_limit.created_at @@ -384,12 +399,11 @@ class Clean(Cog): if self._use_cache(first_limit): log.trace(f"Messages for cleaning by {ctx.author.id} will be searched in the cache.") - message_mappings, message_ids = self._get_messages_from_cache(to_delete=predicate, lower_limit=first_limit) + message_mappings, message_ids = self._get_messages_from_cache( + channels=deletion_channels, to_delete=predicate, lower_limit=first_limit + ) else: log.trace(f"Messages for cleaning by {ctx.author.id} will be searched in channel histories.") - deletion_channels = channels - if channels == "*": - deletion_channels = [channel for channel in ctx.guild.channels if isinstance(channel, TextChannel)] message_mappings, message_ids = await self._get_messages_from_channels( channels=deletion_channels, to_delete=predicate, @@ -406,6 +420,8 @@ class Clean(Cog): deleted_messages = await self._delete_found(message_mappings) self.cleaning = False + if not channels: + channels = deletion_channels logged = await self._modlog_cleaned_messages(deleted_messages, channels, ctx) if logged and is_mod_channel(ctx.channel): -- cgit v1.2.3 From a738d05c3f46969a091b1ba2b0eaa14fcd00644a Mon Sep 17 00:00:00 2001 From: mbaruh Date: Mon, 6 Dec 2021 00:11:28 +0200 Subject: Skip private channels when deleting from all When specifying all channels, the command now skips private channels to optimize for speed. --- bot/exts/moderation/clean.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/bot/exts/moderation/clean.py b/bot/exts/moderation/clean.py index 0b83fc7e0..e61ef7880 100644 --- a/bot/exts/moderation/clean.py +++ b/bot/exts/moderation/clean.py @@ -31,12 +31,12 @@ CleanLimit = Union[Message, Age, ISODateTime] class CleanChannels(Converter): - """A converter that turns the given string to a list of channels to clean, or the literal `*` for all channels.""" + """A converter to turn the string into a list of channels to clean, or the literal `*` for all public channels.""" _channel_converter = TextChannelConverter() async def convert(self, ctx: Context, argument: str) -> Union[Literal["*"], list[TextChannel]]: - """Converts a string to a list of channels to clean, or the literal `*` for all channels.""" + """Converts a string to a list of channels to clean, or the literal `*` for all public channels.""" if argument == "*": return "*" return [await self._channel_converter.convert(ctx, channel) for channel in argument.split()] @@ -129,7 +129,12 @@ class Clean(Cog): channels = {ctx.channel} else: if channels == "*": - channels = {channel for channel in ctx.guild.channels if isinstance(channel, TextChannel)} + channels = { + channel for channel in ctx.guild.channels + if isinstance(channel, TextChannel) + # Assume that non-public channels are not needed to optimize for speed. + and channel.permissions_for(ctx.guild.default_role).view_channel + } else: channels = set(channels) @@ -339,7 +344,7 @@ class Clean(Cog): # Build the embed and send it if channels == "*": - target_channels = "all channels" + target_channels = "all public channels" else: target_channels = ", ".join(channel.mention for channel in channels) @@ -456,7 +461,7 @@ class Clean(Cog): The pattern must be provided enclosed in backticks. If the pattern contains spaces, it still needs to be enclosed in double quotes on top of that. \u2003• `bots_only`: Whether to delete only bots. If specified, users cannot be specified. - \u2003• `channels`: A series of channels to delete in, or an asterisk to delete from all channels. + \u2003• `channels`: A series of channels to delete in, or an asterisk to delete from all public channels. """ if not any([users, first_limit, second_limit, regex, channels]): await ctx.send_help(ctx.command) -- cgit v1.2.3