import { Snowflake } from "discord.js"; import moment from "moment-timezone"; import { commandTypeHelpers as ct } from "../../../commandTypes"; import { isOwner, sendErrorMessage } from "../../../pluginUtils"; import { SECONDS, confirm, noop, renderUsername } from "../../../utils"; import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin"; import { rehostAttachment } from "../rehostAttachment"; import { channelArchiverCmd } from "../types"; const MAX_ARCHIVED_MESSAGES = 5000; const MAX_MESSAGES_PER_FETCH = 100; const PROGRESS_UPDATE_INTERVAL = 5 * SECONDS; export const ArchiveChannelCmd = channelArchiverCmd({ trigger: "archive_channel", permission: null, config: { preFilters: [ (command, context) => { return isOwner(context.pluginData, context.message.author.id); }, ], }, signature: { channel: ct.textChannel(), "attachment-channel": ct.textChannel({ option: true }), messages: ct.number({ option: true }), }, async run({ message: msg, args, pluginData }) { if (!args["attachment-channel"]) { const confirmed = await confirm(msg.channel, msg.author.id, { content: "No `-attachment-channel` specified. Continue? Attachments will not be available in the log if their message is deleted.", }); if (!confirmed) { sendErrorMessage(pluginData, msg.channel, "Canceled"); return; } } const maxMessagesToArchive = args.messages ? Math.min(args.messages, MAX_ARCHIVED_MESSAGES) : MAX_ARCHIVED_MESSAGES; if (maxMessagesToArchive <= 0) return; const archiveLines: string[] = []; let archivedMessages = 0; let previousId: string | undefined; const startTime = Date.now(); const progressMsg = await msg.channel.send("Creating archive..."); const progressUpdateInterval = setInterval(() => { const secondsSinceStart = Math.round((Date.now() - startTime) / 1000); progressMsg .edit(`Creating archive...\n**Status:** ${archivedMessages} messages archived in ${secondsSinceStart} seconds`) .catch(() => clearInterval(progressUpdateInterval)); }, PROGRESS_UPDATE_INTERVAL); while (archivedMessages < maxMessagesToArchive) { const messagesToFetch = Math.min(MAX_MESSAGES_PER_FETCH, maxMessagesToArchive - archivedMessages); const messages = await args.channel.messages.fetch({ limit: messagesToFetch, before: previousId as Snowflake, }); if (messages.size === 0) break; for (const message of messages.values()) { const ts = moment.utc(message.createdTimestamp).format("YYYY-MM-DD HH:mm:ss"); let content = `[${ts}] [${message.author.id}] [${renderUsername( message.author.username, message.author.discriminator, )}]: ${message.content || ""}`; if (message.attachments.size) { if (args["attachment-channel"]) { const rehostedAttachmentUrl = await rehostAttachment(message.attachments[0], args["attachment-channel"]); content += `\n-- Attachment: ${rehostedAttachmentUrl}`; } else { content += `\n-- Attachment: ${message.attachments[0].url}`; } } if (message.reactions && Object.keys(message.reactions).length > 0) { const reactionCounts: string[] = []; for (const [emoji, info] of Object.entries(message.reactions)) { reactionCounts.push(`${info.count}x ${emoji}`); } content += `\n-- Reactions: ${reactionCounts.join(", ")}`; } archiveLines.push(content); previousId = message.id; archivedMessages++; } } clearInterval(progressUpdateInterval); archiveLines.reverse(); const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin); const nowTs = timeAndDate.inGuildTz().format(timeAndDate.getDateFormat("pretty_datetime")); let result = `Archived ${archiveLines.length} messages from #${args.channel.name} at ${nowTs}`; result += `\n\n${archiveLines.join("\n")}\n`; progressMsg.delete().catch(noop); msg.channel.send({ content: "Archive created!", files: [ { attachment: Buffer.from(result), name: `archive-${args.channel.name}-${moment.utc().format("YYYY-MM-DD-HH-mm-ss")}.txt`, }, ], }); }, });