diff --git a/README.md b/README.md index 49eb937cb..b6b8db448 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch ### Extra included plugins
-192 additional plugins +193 additional plugins ### All Platforms @@ -60,6 +60,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch - Equissant by SomeAspy & thororen - ExpandedUserTags by Cyn, TheSun, RyanCaoDev, LordElias, AutumnVN, hen - ExportContacts by dat_insanity +- ExportMessages by veygax - FakeProfileThemesAndEffects by ryan - CopyProfileColors by Crxa - FastDeleteChannels by thororen diff --git a/src/equicordplugins/equicordHelper/utils.tsx b/src/equicordplugins/equicordHelper/utils.tsx new file mode 100644 index 000000000..03ba6e8cd --- /dev/null +++ b/src/equicordplugins/equicordHelper/utils.tsx @@ -0,0 +1,102 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { showNotice } from "@api/Notices"; +import { Alerts, Toasts } from "@webpack/common"; +import { Settings } from "Vencord"; + +function showErrorToast(message: string) { + Toasts.show({ + message, + type: Toasts.Type.FAILURE, + id: Toasts.genId(), + options: { + position: Toasts.Position.BOTTOM + } + }); +} + +function restartPrompt(): Promise { + return new Promise(resolve => { + Alerts.show({ + title: "Restart Required", + body: ( + <> +

+ Some plugins require a restart to fully disable. +

+

Would you like to restart now?

+ + ), + confirmText: "Restart Now", + cancelText: "Later", + onConfirm: () => resolve(true), + onCancel: () => resolve(false), + }); + }); +} + + +export async function toggleEnabled(name: string) { + let restartNeeded = false; + function onRestartNeeded() { + restartNeeded = true; + } + + async function beforeReturn(settings: any, wasEnabled: boolean) { + if (restartNeeded) { + const confirmed = await restartPrompt(); + if (!confirmed) return false; + + settings.enabled = !wasEnabled; + location.reload(); + return true; + } + + return true; + } + + const plugin = Vencord.Plugins.plugins[name]; + const settings = Settings.plugins[plugin.name]; + const isEnabled = () => settings.enabled ?? false; + const wasEnabled = isEnabled(); + + if (!wasEnabled) { + const { restartNeeded, failures } = Vencord.Plugins.startDependenciesRecursive(plugin); + if (failures.length) { + console.error(`Failed to start dependencies for ${plugin.name}: ${failures.join(", ")}`); + showNotice("Failed to start dependencies: " + failures.join(", "), "Close", () => null); + return false; + } else if (restartNeeded) { + settings.enabled = true; + onRestartNeeded(); + return await beforeReturn(settings, wasEnabled); + } + } + + if (plugin.patches?.length) { + onRestartNeeded(); + return await beforeReturn(settings, wasEnabled); + } + + if (wasEnabled && !plugin.started) { + settings.enabled = !wasEnabled; + return await beforeReturn(settings, wasEnabled); + } + + const result = wasEnabled ? Vencord.Plugins.stopPlugin(plugin) : Vencord.Plugins.startPlugin(plugin); + + if (!result) { + settings.enabled = false; + const msg = `Error while ${wasEnabled ? "stopping" : "starting"} plugin ${plugin.name}`; + console.error(msg); + showErrorToast(msg); + return false; + } + + settings.enabled = !wasEnabled; + return await beforeReturn(settings, wasEnabled); +} diff --git a/src/equicordplugins/exportMessages/index.tsx b/src/equicordplugins/exportMessages/index.tsx new file mode 100644 index 000000000..c4cf96d04 --- /dev/null +++ b/src/equicordplugins/exportMessages/index.tsx @@ -0,0 +1,113 @@ +/* + * 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 { saveFile } from "@utils/web"; +import { Menu } from "@webpack/common"; +import { Message } from "discord-types/general"; + +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 { + 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/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index 7a016247e..23a197020 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -22,6 +22,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Flex } from "@components/Flex"; import { Link } from "@components/Link"; import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab"; +import { toggleEnabled } from "@equicordplugins/equicordHelper/utils"; import { CONTRIB_ROLE_ID, Devs, DONOR_ROLE_ID, EQUCORD_HELPERS, EQUIBOP_CONTRIB_ROLE_ID, EQUICORD_TEAM, GUILD_ID, SUPPORT_CHANNEL_ID, SUPPORT_CHANNEL_IDS, VC_CONTRIB_ROLE_ID, VC_DONOR_ROLE_ID, VC_GUILD_ID, VC_REGULAR_ROLE_ID, VC_SUPPORT_CHANNEL_IDS, VENCORD_CONTRIB_ROLE_ID } from "@utils/constants"; import { sendMessage } from "@utils/discord"; import { Logger } from "@utils/Logger"; @@ -295,11 +296,14 @@ export default definePlugin({ const shouldAddUpdateButton = !IS_UPDATER_DISABLED - && ( - (props.channel.id === SUPPORT_CHANNEL_ID && equicordSupport) - ) + && ((props.channel.id === SUPPORT_CHANNEL_ID && equicordSupport)) && props.message.content?.includes("update"); + const matchedPlugins = Object.keys(Vencord.Plugins.plugins).filter(name => (props.message.content?.toLowerCase() ?? "").includes(name.toLowerCase())); + const matchedPlugin = matchedPlugins.sort((a, b) => b.length - a.length)[0]; + const equicordGuild = ChannelStore.getChannel(props.channel.id)?.guild_id === GUILD_ID; + const shouldAddPluginButtons = equicordGuild && equicordSupport && matchedPlugin; + if (shouldAddUpdateButton) { buttons.push( + ); + } + if (props.channel.id === SUPPORT_CHANNEL_ID && PermissionStore.can(PermissionsBits.SEND_MESSAGES, props.channel)) { if (props.message.content.includes("/equicord-debug") || props.message.content.includes("/equicord-plugins")) { buttons.push( @@ -382,7 +408,7 @@ export default definePlugin({ Please do not private message plugin developers for support!
- Instead, use the support channel: {Parser.parse("https://discord.com/channels/1173279886065029291/1173342942858055721")} + Instead, use the support channel: {Parser.parse("https://discord.com/channels/1173279886065029291/1297590739911573585")} {!ChannelStore.getChannel(SUPPORT_CHANNEL_ID) && " (Click the link to join)"}
); 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