From 830b5852f80f6c100fbfe01a8b094dfc34d7fe70 Mon Sep 17 00:00:00 2001 From: VeygaX <84923630+veygax@users.noreply.github.com> Date: Mon, 7 Jul 2025 12:25:58 +0100 Subject: [PATCH] feat: add new ExportMessages plugin (#300) * feat: add new ExportMessages plugin * fix: update filename format in exportMessage function to use message ID cos it makes more sense --------- Co-authored-by: thororen <78185467+thororen1234@users.noreply.github.com> --- src/equicordplugins/exportMessages/index.tsx | 115 +++++++++++++++++++ src/utils/constants.ts | 4 + 2 files changed, 119 insertions(+) create mode 100644 src/equicordplugins/exportMessages/index.tsx diff --git a/src/equicordplugins/exportMessages/index.tsx b/src/equicordplugins/exportMessages/index.tsx new file mode 100644 index 000000000..b65db8dac --- /dev/null +++ b/src/equicordplugins/exportMessages/index.tsx @@ -0,0 +1,115 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { showNotification } from "@api/Notifications"; +import { definePluginSettings } from "@api/Settings"; +import { EquicordDevs } from "@utils/constants"; +import { showItemInFolder } from "@utils/native"; +import definePlugin, { OptionType } from "@utils/types"; +import { findByPropsLazy } from "@webpack"; +import { Menu } from "@webpack/common"; +import { Message } from "discord-types/general"; + +const { saveFile } = findByPropsLazy("saveFile", "openFile"); + +const settings = definePluginSettings({ + openFileAfterExport: { + type: OptionType.BOOLEAN, + description: "Open the exported file in the default file handler after export", + default: true + } +}); + +function formatMessage(message: Message) { + const { author } = message; + const timestamp = new Date(message.timestamp.toString()).toLocaleString(); + + let content = `[${timestamp}] ${author.username}`; + if (author.discriminator !== "0") { + content += `#${author.discriminator}`; + } + content += `: ${message.content}`; + + if (message.attachments?.length > 0) { + content += "\n Attachments:"; + message.attachments.forEach(attachment => { + content += `\n - ${attachment.filename} (${attachment.url})`; + }); + } + + if (message.embeds?.length > 0) { + content += "\n Embeds:"; + message.embeds.forEach(embed => { + if (embed.rawTitle) content += `\n Title: ${embed.rawTitle}`; + if (embed.rawDescription) content += `\n Description: ${embed.rawDescription}`; + if (embed.url) content += `\n URL: ${embed.url}`; + }); + } + + return content; +} + +async function exportMessage(message: Message) { + const timestamp = new Date(message.timestamp.toString()).toISOString().split("T")[0]; + const filename = `message-${message.id}-${timestamp}.txt`; + + const content = formatMessage(message); + + try { + if (IS_DISCORD_DESKTOP) { + const data = new TextEncoder().encode(content); + const result = await DiscordNative.fileManager.saveWithDialog(data, filename); + + if (result && settings.store.openFileAfterExport) { + showItemInFolder(result); + } + } else { // for web + const file = new File([content], filename, { type: "text/plain" }); + saveFile(file); + } + + showNotification({ + title: "Export Messages", + body: `Message exported successfully as ${filename}`, + icon: "📄" + }); + } catch (error) { + showNotification({ + title: "Export Messages", + body: "Failed to export message", + icon: "❌" + }); + } +} + +const messageContextMenuPatch = (children: Array | null>, props: { message: Message; }) => { + const { message } = props; + + if (!message) return; + + children.push( + ( + + + + )} + action={() => exportMessage(message)} + /> + ); +}; + +export default definePlugin({ + name: "ExportMessages", + description: "Allows you to export any message to a file", + authors: [EquicordDevs.veygax], + settings, + contextMenus: { + "message": messageContextMenuPatch + } +}); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 6c5e8fd4e..3ec7d7d89 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1096,6 +1096,10 @@ export const EquicordDevs = Object.freeze({ name: "Suffocate", id: 772601756776923187n }, + veygax: { + name: "veygax", + id: 1119938236245094521n + } } satisfies Record); // iife so #__PURE__ works correctly