diff --git a/GearBot/Cogs/Moderation.py b/GearBot/Cogs/Moderation.py index 7af6f7dd..3ffd3445 100644 --- a/GearBot/Cogs/Moderation.py +++ b/GearBot/Cogs/Moderation.py @@ -1223,6 +1223,44 @@ async def channel(self, ctx, channel: typing.Union[disnake.TextChannel, disnake. else: await ctx.send(f"{Emoji.get_chat_emoji('NO')} {Translator.translate('archive_no_edit_logs', ctx)}") + @archive.command(aliases=['messages','range']) + async def message(self, ctx, first: disnake.PartialMessage, last: disnake.PartialMessage = None): + """archive_messages_help""" + if not await Configuration.get_var(ctx.guild.id, "MESSAGE_LOGS", "ENABLED"): + await MessageUtils.send_to(ctx, 'NO', 'archive_no_edit_logs') + return + permissions = first.channel.permissions_for(ctx.author) + if not permissions.read_messages: + await MessageUtils.send_to(ctx, 'NO', 'archive_leak_denied') + return + query = LoggedMessage.filter(server=ctx.guild.id, channel=first.channel.id) \ + .order_by("-messageid") \ + .prefetch_related("attachments") + if last is not None: + if first.channel.id != last.channel.id: + await MessageUtils.send_to(ctx, 'NO', 'archive_message_mismatched_channels', \ + first=first.channel.id, last=last.channel.id) + return + if last.id < first.id: + # Last is older than first, swap them + last, first = first, last + query = query.filter(messageid__gte=first.id, messageid__lte=last.id) + # COUNT to see if we are about to request more than 5000 rows + if await query.count() > 5000: + await MessageUtils.send_to(ctx, 'NO', 'archive_too_much') + return + # Even though we are not over 5000, let's be safe about it + query = query.limit(5000) + else: + # Not .first() because we still want to be an iterable + query = query.filter(messageid=first.id).limit(1) + await MessageUtils.send_to(ctx, 'SEARCH', 'searching_archives') + messages = await query + messages += DBUtils.get_messages_in_range(first.channel.id, first.id, last.id if last is not None else None) + await Archive.ship_messages(ctx, messages, "message", plural='yes' if last is not None else 'no') + if first.channel.id == ctx.channel.id and '/' not in ctx.message.content and '-' not in ctx.message.content: + await MessageUtils.send_to(ctx, 'WARNING', 'archive_message_no_channel_id') + @archive.command() async def user(self, ctx, user: DiscordUser, amount=100): """archive_user_help""" diff --git a/GearBot/Util/Archive.py b/GearBot/Util/Archive.py index c88902fc..7ef8e9bf 100644 --- a/GearBot/Util/Archive.py +++ b/GearBot/Util/Archive.py @@ -33,7 +33,7 @@ async def pack_messages(messages, guild_id): out += f"{timestamp} {message.server} - {message.channel} - {message.messageid} | {name} ({message.author}) | {message.content}{reply} | {(', '.join(Utils.assemble_attachment(message.channel, attachment.id, attachment.name) for attachment in message.attachments))}\r\n" return out -async def ship_messages(ctx, messages, t, filename="Message archive", filtered=False): +async def ship_messages(ctx, messages, t, filename="Message archive", filtered=False, **kwargs): addendum = "" if filtered: addendum = f"\n{Emoji.get_chat_emoji('WARNING')} {Translator.translate('archive_message_filtered', ctx)}" @@ -53,4 +53,4 @@ async def ship_messages(ctx, messages, t, filename="Message archive", filtered=F await ctx.send(f"{Emoji.get_chat_emoji('YES')} {Translator.translate('archived_count', ctx, count=len(messages))} {addendum}", file=disnake.File(fp=buffer, filename=f"{filename}.txt")) else: - await ctx.send(f"{Emoji.get_chat_emoji('WARNING')} {Translator.translate(f'archive_empty_{t}', ctx)} {addendum}") \ No newline at end of file + await ctx.send(f"{Emoji.get_chat_emoji('WARNING')} {Translator.translate(f'archive_empty_{t}', ctx, **kwargs)} {addendum}") diff --git a/GearBot/database/DBUtils.py b/GearBot/database/DBUtils.py index cf5a06ed..acff836e 100644 --- a/GearBot/database/DBUtils.py +++ b/GearBot/database/DBUtils.py @@ -103,8 +103,14 @@ async def do_flush(): raise e +def get_messages_in_range(channel_id, first_id, last_id=None): + if last_id is not None: + return [message for message in batch.values() if message.channel == channel_id and message.messageid >= first_id and message.messageid <= last_id] + else: + return [message for message in batch.values() if message.channel == channel_id and message.messageid == first_id] + def get_messages_for_channel(channel_id): return [message for message in batch.values() if message.channel == channel_id] def get_messages_for_user_in_guild(user_id, guild_id): - return [message for message in batch.values() if message.server == guild_id and message.author == user_id] \ No newline at end of file + return [message for message in batch.values() if message.server == guild_id and message.author == user_id] diff --git a/lang/en_US.json b/lang/en_US.json index b802e6eb..0f0459be 100644 --- a/lang/en_US.json +++ b/lang/en_US.json @@ -202,8 +202,11 @@ "purged_log": "Archived {count, plural, one {1 purged message} other {# purged messages}} from {channel}.", "archived_count": "Gear minions have returned with {count, plural, one {1 message} other {# messages}}.", "archive_empty_user": "My gear minions have returned from their quest to the archives with empty hands. It appears they didn't send any messages in the last 6 weeks in this server.", - "archive_empty_channel": "My gear minions have returned from their quest to the archives with empty hands. It appears nobody send any messages in that channel in the last 6 weeks.", + "archive_empty_message": "My gear minions have returned from their quest to the archives with empty hands. It appears the {plural, select, no {message is} yes {messages are}} older than 6 weeks, or {plural, select, no {it is} yes {they are}} in another channel. \n (Try using '`Copy Message Link`' or holding Shift when pressing '`Copy Message ID`')", + "archive_empty_channel": "My gear minions have returned from their quest to the archives with empty hands. It appears nobody sent any messages in that channel in the last 6 weeks.", "archive_denied_read_perms": "Trying to archive a channel you don't have access to? Sorry, leek denied.", + "archive_message_mismatched_channels": "Both messages must be in the same channel. Received messages from <#{first}> and <#{last}>.", + "archive_message_no_channel_id": "No channel information was provided, so our minions got what they could find. If these messages are incorrect, try including Channel IDs. \n (Use '`Copy Message Link`' or hold Shift when pressing '`Copy Message ID`')", "archive_no_subcommand": "Instructions unclear, search quest denied. Please read through `{prefix}help archive` and return with a new quest assignment when ready", "archive_no_edit_logs": "Please enable edit logs to be able to use archiving", "archive_too_much": "I get it, you like reading old conversations, good for you. But do you really need more than 5000 messages? You might want to try a good book instead.", @@ -804,6 +807,7 @@ "cf_help": "Base command to pull mod info from CurseForge, still WIP", "archive_help": "Base command for archiving, use the subcommands to actually make archives", "archive_channel_help": "Archive messages from a channel", + "archive_messages_help": "Archive a single message or a range of messages", "archive_user_help": "Archive messages from a user", "mban_help": "Bans multiple users with the same reason", "mwarn_help": "Warns multiple users with the same reason",