import { Message } from "discord.js"; import { logUnexpectedDiscordAPIError, replyWithCleanup } from "../utilities"; import { logUnexpectedDiscordAPIResult } from "../../../shared/log"; import { disableMessageDeletionListener, enableMessageDeletionListener, } from "../listeners/messageDeletion"; export const handleDeleteRangeCommand = async (message: Message) => { if (message.author.bot) return; if (message.content.toLowerCase().startsWith("uma!deleterange")) { const application = await message.client.application?.fetch(); const ownerId = application?.owner?.id; if (message.author.id !== ownerId) { await replyWithCleanup( message, "❌ Only the server owner can use this command.", ); return; } const parameters = message.content.split(" ").slice(1); if (parameters.length < 3) { await replyWithCleanup( message, "❌ Usage: `uma!deleterange `\nExample: `uma!deleterange 1234567890123456789 1111111111111111111 2222222222222222222`", ); return; } const [channelId, startMessageId, endMessageId] = parameters; if (!/^\d{17,19}$/.test(channelId)) { await replyWithCleanup( message, "❌ Invalid channel ID format. Please provide a valid Discord channel ID.", ); return; } if ( !/^\d{17,19}$/.test(startMessageId) || !/^\d{17,19}$/.test(endMessageId) ) { await replyWithCleanup( message, "❌ Invalid message ID format. Please provide valid Discord message IDs.", ); return; } const targetChannel = message.client.channels.cache.get(channelId); if (!targetChannel || !targetChannel.isTextBased()) { await replyWithCleanup( message, "❌ Channel not found or is not a text channel.", ); return; } disableMessageDeletionListener(); try { const startMessage = await targetChannel.messages.fetch(startMessageId); const endMessage = await targetChannel.messages.fetch(endMessageId); const startTimestamp = startMessage.createdTimestamp; const endTimestamp = endMessage.createdTimestamp; const earlierTimestamp = Math.min(startTimestamp, endTimestamp); const laterTimestamp = Math.max(startTimestamp, endTimestamp); const earlierMessageId = startTimestamp < endTimestamp ? startMessageId : endMessageId; const laterMessageId = startTimestamp < endTimestamp ? endMessageId : startMessageId; const messagesToDelete: Message[] = []; const messageIdSet = new Set(); messagesToDelete.push( earlierMessageId === startMessageId ? startMessage : endMessage, ); messagesToDelete.push( laterMessageId === startMessageId ? startMessage : endMessage, ); messageIdSet.add(earlierMessageId); messageIdSet.add(laterMessageId); let lastMessageId: string | undefined = laterMessageId; let hasMoreMessages = true; while (hasMoreMessages) { const fetchOptions: any = { limit: 100 }; if (lastMessageId) fetchOptions.before = lastMessageId; const fetchedMessages = (await targetChannel.messages.fetch( fetchOptions, )) as any; if (fetchedMessages.size === 0) { hasMoreMessages = false; break; } for (const message of fetchedMessages.values()) { const messageTimestamp = message.createdTimestamp; if (messageTimestamp < earlierTimestamp) { hasMoreMessages = false; break; } if ( messageTimestamp >= earlierTimestamp && messageTimestamp <= laterTimestamp && !messageIdSet.has(message.id) ) { messagesToDelete.push(message); messageIdSet.add(message.id); } } lastMessageId = fetchedMessages.last()?.id; if ( !lastMessageId || fetchedMessages.last()!.createdTimestamp < earlierTimestamp ) hasMoreMessages = false; } if (messagesToDelete.length === 0) { await replyWithCleanup( message, "❌ No messages found in the specified range.", ); return; } messagesToDelete.sort((a, b) => a.createdTimestamp - b.createdTimestamp); if (messagesToDelete.length > 1 && "bulkDelete" in targetChannel) { try { const chunkSize = 100; let failedDeleteCount = 0; for (let i = 0; i < messagesToDelete.length; i += chunkSize) { const chunk = messagesToDelete.slice(i, i + chunkSize); if (chunk.length < 2) { for (const message of chunk) try { await message.delete(); } catch { failedDeleteCount += 1; } } else { try { await targetChannel.bulkDelete(chunk); } catch (chunkError) { logUnexpectedDiscordAPIError(chunkError); for (const message of chunk) try { await message.delete(); } catch { failedDeleteCount += 1; } } } } await message.delete(); if (failedDeleteCount > 0) logUnexpectedDiscordAPIResult( `Failed to delete ${failedDeleteCount} out of ${messagesToDelete.length} messages`, ); } catch (bulkError) { logUnexpectedDiscordAPIError(bulkError); let failedDeleteCount = 0; for (const message of messagesToDelete) try { await message.delete(); } catch { failedDeleteCount += 1; } await message.delete(); if (failedDeleteCount > 0) logUnexpectedDiscordAPIResult( `Failed to delete ${failedDeleteCount} out of ${messagesToDelete.length} messages`, ); } } else { let failedDeleteCount = 0; for (const message of messagesToDelete) try { await message.delete(); } catch { failedDeleteCount += 1; } await message.delete(); if (failedDeleteCount > 0) logUnexpectedDiscordAPIResult( `Failed to delete ${failedDeleteCount} out of ${messagesToDelete.length} messages`, ); } } catch (error) { logUnexpectedDiscordAPIError(error); await replyWithCleanup( message, "❌ Failed to delete messages. Check bot permissions and try again.", ); } finally { enableMessageDeletionListener(); } } };