diff options
| author | 2020-11-22 12:31:14 +0100 | |
|---|---|---|
| committer | 2020-11-22 12:31:14 +0100 | |
| commit | b2367b20e9f73d269224a6dbee20e23e6b6de6b7 (patch) | |
| tree | 1b0324f9f3f9d080ab9aad0c3686e4ea4922f62b | |
| parent | Merge pull request #1204 from python-discord/superstar-fix (diff) | |
rework clean to fully use `delete_messages` instead of `purge`
| -rw-r--r-- | bot/exts/utils/clean.py | 86 |
1 files changed, 60 insertions, 26 deletions
diff --git a/bot/exts/utils/clean.py b/bot/exts/utils/clean.py index bf25cb4c2..d6dd2401f 100644 --- a/bot/exts/utils/clean.py +++ b/bot/exts/utils/clean.py @@ -1,9 +1,10 @@ import logging import random import re -from typing import Iterable, Optional +import time +from typing import Dict, Iterable, List, Optional -from discord import Colour, Embed, Message, TextChannel, User +from discord import Colour, Embed, Message, NotFound, TextChannel, User from discord.ext import commands from discord.ext.commands import Cog, Context, group, has_any_role @@ -36,6 +37,14 @@ class Clean(Cog): """Get currently loaded ModLog cog instance.""" return self.bot.get_cog("ModLog") + async def _delete_messages_individually(self, messages: List[Message]) -> None: + for message in messages: + try: + await message.delete() + except NotFound: + # message doesn't exist or was already deleted + continue + async def _clean_messages( self, amount: int, @@ -107,7 +116,7 @@ class Clean(Cog): elif regex: predicate = predicate_regex # Delete messages that match regex else: - predicate = None # Delete all messages + predicate = lambda m: True # Delete all messages # noqa: E731 # Default to using the invoking context's channel if not channels: @@ -117,19 +126,28 @@ class Clean(Cog): self.mod_log.ignore(Event.message_delete, ctx.message.id) await ctx.message.delete() - messages = [] + # we need Channel to Message mapping for easier deletion via TextChannel.delete_messages + message_mappings: Dict[TextChannel, List[Message]] = {} message_ids = [] self.cleaning = True # Find the IDs of the messages to delete. IDs are needed in order to ignore mod log events. for channel in channels: + + messages = [] + async for message in channel.history(limit=amount): # If at any point the cancel command is invoked, we should stop. if not self.cleaning: return - # If we are looking for specific message. + # If the message passes predicate, let's save it. + if predicate(message): + messages.append(message) + message_ids.append(message) + + # if we are looking for specific message if until_message: # we could use ID's here however in case if the message we are looking for gets deleted, @@ -138,33 +156,49 @@ class Clean(Cog): # means we have found the message until which we were supposed to be deleting. break - # Since we will be using `delete_messages` method of a TextChannel and we need message objects to - # use it as well as to send logs we will start appending messages here instead adding them from - # purge. - messages.append(message) - - # If the message passes predicate, let's save it. - if predicate is None or predicate(message): - message_ids.append(message.id) + if len(messages) > 0: + # we don't want to create mappings of TextChannel to empty list + message_mappings[channel] = messages self.cleaning = False # Now let's delete the actual messages with purge. self.mod_log.ignore(Event.message_delete, *message_ids) - for channel in channels: - if until_message: - for i in range(0, len(messages), 100): - # while purge automatically handles the amount of messages - # delete_messages only allows for up to 100 messages at once - # thus we need to paginate the amount to always be <= 100 - await channel.delete_messages(messages[i:i + 100]) - else: - messages += await channel.purge(limit=amount, check=predicate) - # Reverse the list to restore chronological order - if messages: - messages = reversed(messages) - log_url = await self.mod_log.upload_log(messages, ctx.author.id) + # Creates ID like int object that would represent an object that is exactly 14 days old + minimum_time = int((time.time() - 14 * 24 * 60 * 60) * 1000.0 - 1420070400000) << 22 + + for channel, messages in message_mappings.items(): + + to_delete = [] + + for current_index, message in enumerate(messages): + + if message.id < minimum_time: + # further messages are too old to be deleted in bulk + await self._delete_messages_individually(messages[current_index:]) + break + + to_delete.append(message) + + if len(to_delete) == 100: + # we can only delete up to 100 messages in a bulk + await channel.delete_messages(to_delete) + to_delete.clear() + + if len(to_delete) > 0: + # deleting any leftover messages if there are any + await channel.delete_messages(to_delete) + + log_messages = [] + + for messages in message_mappings.values(): + log_messages.extend(messages) + + if log_messages: + # Reverse the list to restore chronological order + log_messages = reversed(log_messages) + log_url = await self.mod_log.upload_log(log_messages, ctx.author.id) else: # Can't build an embed, nothing to clean! embed = Embed( |