From e63d0ab4515323429b4deb6bdbb22dfce17d00a8 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Fri, 23 May 2025 13:48:16 -0400 Subject: [PATCH 01/54] Fix React DevTools --- src/main/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/index.ts b/src/main/index.ts index ae3f9d2f..8f9d13c3 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -24,7 +24,7 @@ import { RendererSettings } from "./settings"; import { IS_VANILLA, THEMES_DIR } from "./utils/constants"; import { installExt } from "./utils/extensions"; -if (IS_VESKTOP || IS_EQUIBOP || !IS_VANILLA) { +if (!IS_VANILLA && !IS_EXTENSION) { app.whenReady().then(() => { // Source Maps! Maybe there's a better way but since the renderer is executed // from a string I don't think any other form of sourcemaps would work From 1fdfd6f30528f4d02c5fc07319d65c2e7763a546 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 23 May 2025 22:07:48 -0300 Subject: [PATCH 02/54] Fix broken UserSettingsAPI patch --- src/plugins/_api/userSettings.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/_api/userSettings.ts b/src/plugins/_api/userSettings.ts index 3a00bc11..559de369 100644 --- a/src/plugins/_api/userSettings.ts +++ b/src/plugins/_api/userSettings.ts @@ -30,8 +30,8 @@ export default definePlugin({ replacement: [ // Main setting definition { - match: /(?<=INFREQUENT_USER_ACTION.{0,20},)useSetting:/, - replace: "userSettingsAPIGroup:arguments[0],userSettingsAPIName:arguments[1],$&" + match: /\.updateAsync\(.+?(?=,useSetting:)/, + replace: "$&,userSettingsAPIGroup:arguments[0],userSettingsAPIName:arguments[1]" }, // Selective wrapper { From 6ea960cf90cef607697b57e1e17db155549f75f3 Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Fri, 23 May 2025 21:14:36 -0400 Subject: [PATCH 03/54] NoUnblockToJump: Fix outdated plugin description (#3422) --- src/plugins/noUnblockToJump/README.md | 2 +- src/plugins/noUnblockToJump/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/noUnblockToJump/README.md b/src/plugins/noUnblockToJump/README.md index 326bca3b..630ef7dc 100644 --- a/src/plugins/noUnblockToJump/README.md +++ b/src/plugins/noUnblockToJump/README.md @@ -1,5 +1,5 @@ # No Unblock To Jump -Removes the popup preventing you to jump to a message from a blocked/ignored user (eg: in search results) +Removes the popup preventing you to jump to a message from a blocked/ignored user or likely spammer (eg: in search results) ![A modal popup telling you to unblock a user to jump their message](https://github.com/user-attachments/assets/0e4b859d-f3b3-4101-9a83-829afb473d1e) diff --git a/src/plugins/noUnblockToJump/index.ts b/src/plugins/noUnblockToJump/index.ts index cb379bf8..b36a2e24 100644 --- a/src/plugins/noUnblockToJump/index.ts +++ b/src/plugins/noUnblockToJump/index.ts @@ -21,7 +21,7 @@ import definePlugin from "@utils/types"; export default definePlugin({ name: "NoUnblockToJump", - description: "Allows you to jump to messages of blocked users without unblocking them", + description: "Allows you to jump to messages of blocked or ignored users and likely spammers without unblocking them", authors: [Devs.dzshn], patches: [ { From f030937bfa1bdec4e740c680f9f207fc7def7869 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 24 May 2025 18:33:30 +0200 Subject: [PATCH 04/54] clean up some misleading plugin names --- src/plugins/arRPC.web/index.tsx | 4 +--- src/plugins/{customidle => customIdle}/README.md | 0 src/plugins/{customidle => customIdle}/index.ts | 0 src/plugins/{emoteCloner => expressionCloner}/index.tsx | 8 +++++--- src/plugins/{lastfm => lastfmRichPresence}/index.tsx | 0 5 files changed, 6 insertions(+), 6 deletions(-) rename src/plugins/{customidle => customIdle}/README.md (100%) rename src/plugins/{customidle => customIdle}/index.ts (100%) rename src/plugins/{emoteCloner => expressionCloner}/index.tsx (97%) rename src/plugins/{lastfm => lastfmRichPresence}/index.tsx (100%) diff --git a/src/plugins/arRPC.web/index.tsx b/src/plugins/arRPC.web/index.tsx index 94c507f3..426b9a9b 100644 --- a/src/plugins/arRPC.web/index.tsx +++ b/src/plugins/arRPC.web/index.tsx @@ -42,6 +42,7 @@ export default definePlugin({ description: "Client plugin for arRPC to enable RPC on Discord Web (experimental)", authors: [Devs.Ducko], reporterTestable: ReporterTestable.None, + hidden: IS_VESKTOP || "legcord" in window, settingsAboutComponent: () => ( <> @@ -73,9 +74,6 @@ export default definePlugin({ }, async start() { - // Legcord comes with its own arRPC implementation, so this plugin just confuses users - if ("legcord" in window) return; - if (ws) ws.close(); ws = new WebSocket("ws://127.0.0.1:1337"); // try to open WebSocket diff --git a/src/plugins/customidle/README.md b/src/plugins/customIdle/README.md similarity index 100% rename from src/plugins/customidle/README.md rename to src/plugins/customIdle/README.md diff --git a/src/plugins/customidle/index.ts b/src/plugins/customIdle/index.ts similarity index 100% rename from src/plugins/customidle/index.ts rename to src/plugins/customIdle/index.ts diff --git a/src/plugins/emoteCloner/index.tsx b/src/plugins/expressionCloner/index.tsx similarity index 97% rename from src/plugins/emoteCloner/index.tsx rename to src/plugins/expressionCloner/index.tsx index ffc2307e..3a73489c 100644 --- a/src/plugins/emoteCloner/index.tsx +++ b/src/plugins/expressionCloner/index.tsx @@ -17,6 +17,7 @@ */ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; +import { migratePluginSettings } from "@api/Settings"; import { CheckedTextInput } from "@components/CheckedTextInput"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; @@ -165,7 +166,7 @@ async function doClone(guildId: string, data: Sticker | Emoji) { message = JSON.parse(e.text).message; } catch { } - new Logger("EmoteCloner").error("Failed to clone", data.name, "to", guildId, e); + new Logger("ExpressionCloner").error("Failed to clone", data.name, "to", guildId, e); Toasts.show({ message: "Failed to clone: " + message, type: Toasts.Type.FAILURE, @@ -364,10 +365,11 @@ const expressionPickerPatch: NavContextMenuPatchCallback = (children, props: { t } }; +migratePluginSettings("ExpressionCloner", "EmoteCloner"); export default definePlugin({ - name: "EmoteCloner", + name: "ExpressionCloner", description: "Allows you to clone Emotes & Stickers to your own server (right click them)", - tags: ["StickerCloner"], + tags: ["StickerCloner", "EmoteCloner", "EmojiCloner"], authors: [Devs.Ven, Devs.Nuckyz], contextMenus: { "message": messageContextMenuPatch, diff --git a/src/plugins/lastfm/index.tsx b/src/plugins/lastfmRichPresence/index.tsx similarity index 100% rename from src/plugins/lastfm/index.tsx rename to src/plugins/lastfmRichPresence/index.tsx From d7e6fcd3ae2dad93a27348e683453a4c912208e8 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 24 May 2025 18:33:55 +0200 Subject: [PATCH 05/54] Delete MoreKaomoji plugin This plugin is very niche and can easily be replicated using the TextReplace plugin --- src/plugins/moreKaomoji/index.ts | 47 -------------------------------- 1 file changed, 47 deletions(-) delete mode 100644 src/plugins/moreKaomoji/index.ts diff --git a/src/plugins/moreKaomoji/index.ts b/src/plugins/moreKaomoji/index.ts deleted file mode 100644 index 9a691fc4..00000000 --- a/src/plugins/moreKaomoji/index.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { findOption, OptionalMessageOption } from "@api/Commands"; -import { Devs } from "@utils/constants"; -import definePlugin from "@utils/types"; - -export default definePlugin({ - name: "MoreKaomoji", - description: "Adds more Kaomoji to discord. ヽ(´▽`)/", - authors: [Devs.JacobTm], - commands: [ - { name: "dissatisfaction", description: " >﹏<" }, - { name: "smug", description: "ಠ_ಠ" }, - { name: "happy", description: "ヽ(´▽`)/" }, - { name: "crying", description: "ಥ_ಥ" }, - { name: "angry", description: "ヽ(`Д´)ノ" }, - { name: "anger", description: "ヽ(o`皿′o)ノ" }, - { name: "joy", description: "<( ̄︶ ̄)>" }, - { name: "blush", description: "૮ ˶ᵔ ᵕ ᵔ˶ ა" }, - { name: "confused", description: "(•ิ_•ิ)?" }, - { name: "sleeping", description: "(ᴗ_ᴗ)" }, - { name: "laughing", description: "o(≧▽≦)o" }, - - ].map(data => ({ - ...data, - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " " + data.description - }) - })) -}); From 600a95f751c5977f47d64aaa97fdbfd3f324504e Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 24 May 2025 18:34:42 +0200 Subject: [PATCH 06/54] Delete Moyai plugin This plugin is funny but ultimately useless and leads to a lot of confusion from users enabling it by mistake --- src/plugins/moyai/index.ts | 177 ------------------------------------- 1 file changed, 177 deletions(-) delete mode 100644 src/plugins/moyai/index.ts diff --git a/src/plugins/moyai/index.ts b/src/plugins/moyai/index.ts deleted file mode 100644 index 649b1fbb..00000000 --- a/src/plugins/moyai/index.ts +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { definePluginSettings } from "@api/Settings"; -import { makeRange } from "@components/PluginSettings/components/SettingSliderComponent"; -import { Devs } from "@utils/constants"; -import { sleep } from "@utils/misc"; -import definePlugin, { OptionType } from "@utils/types"; -import { RelationshipStore, SelectedChannelStore, UserStore } from "@webpack/common"; -import { Message, ReactionEmoji } from "discord-types/general"; - -interface IMessageCreate { - type: "MESSAGE_CREATE"; - optimistic: boolean; - isPushNotification: boolean; - channelId: string; - message: Message; -} - -interface IReactionAdd { - type: "MESSAGE_REACTION_ADD"; - optimistic: boolean; - channelId: string; - messageId: string; - messageAuthorId: string; - userId: "195136840355807232"; - emoji: ReactionEmoji; -} - -interface IVoiceChannelEffectSendEvent { - type: string; - emoji?: ReactionEmoji; // Just in case... - channelId: string; - userId: string; - animationType: number; - animationId: number; -} - -const MOYAI = "🗿"; -const MOYAI_URL = - "https://raw.githubusercontent.com/MeguminSama/VencordPlugins/main/plugins/moyai/moyai.mp3"; -const MOYAI_URL_HD = - "https://raw.githubusercontent.com/MeguminSama/VencordPlugins/main/plugins/moyai/moyai_hd.wav"; - -const settings = definePluginSettings({ - volume: { - description: "Volume of the 🗿🗿🗿", - type: OptionType.SLIDER, - markers: makeRange(0, 1, 0.1), - default: 0.5, - stickToMarkers: false - }, - quality: { - description: "Quality of the 🗿🗿🗿", - type: OptionType.SELECT, - options: [ - { label: "Normal", value: "Normal", default: true }, - { label: "HD", value: "HD" } - ], - }, - triggerWhenUnfocused: { - description: "Trigger the 🗿 even when the window is unfocused", - type: OptionType.BOOLEAN, - default: true - }, - ignoreBots: { - description: "Ignore bots", - type: OptionType.BOOLEAN, - default: true - }, - ignoreBlocked: { - description: "Ignore blocked users", - type: OptionType.BOOLEAN, - default: true - } -}); - -export default definePlugin({ - name: "Moyai", - authors: [Devs.Megu, Devs.Nuckyz], - description: "🗿🗿🗿🗿🗿🗿🗿🗿", - settings, - - flux: { - async MESSAGE_CREATE({ optimistic, type, message, channelId }: IMessageCreate) { - if (optimistic || type !== "MESSAGE_CREATE") return; - if (message.state === "SENDING") return; - if (settings.store.ignoreBots && message.author?.bot) return; - if (settings.store.ignoreBlocked && RelationshipStore.isBlocked(message.author?.id)) return; - if (!message.content) return; - if (channelId !== SelectedChannelStore.getChannelId()) return; - - const moyaiCount = getMoyaiCount(message.content); - - for (let i = 0; i < moyaiCount; i++) { - boom(); - await sleep(300); - } - }, - - MESSAGE_REACTION_ADD({ optimistic, type, channelId, userId, messageAuthorId, emoji }: IReactionAdd) { - if (optimistic || type !== "MESSAGE_REACTION_ADD") return; - if (settings.store.ignoreBots && UserStore.getUser(userId)?.bot) return; - if (settings.store.ignoreBlocked && RelationshipStore.isBlocked(messageAuthorId)) return; - if (channelId !== SelectedChannelStore.getChannelId()) return; - - const name = emoji.name.toLowerCase(); - if (name !== MOYAI && !name.includes("moyai") && !name.includes("moai")) return; - - boom(); - }, - - VOICE_CHANNEL_EFFECT_SEND({ emoji }: IVoiceChannelEffectSendEvent) { - if (!emoji?.name) return; - const name = emoji.name.toLowerCase(); - if (name !== MOYAI && !name.includes("moyai") && !name.includes("moai")) return; - - boom(); - } - } -}); - -function countOccurrences(sourceString: string, subString: string) { - let i = 0; - let lastIdx = 0; - while ((lastIdx = sourceString.indexOf(subString, lastIdx) + 1) !== 0) - i++; - - return i; -} - -function countMatches(sourceString: string, pattern: RegExp) { - if (!pattern.global) - throw new Error("pattern must be global"); - - let i = 0; - while (pattern.test(sourceString)) - i++; - - return i; -} - -const customMoyaiRe = //gi; - -function getMoyaiCount(message: string) { - const count = countOccurrences(message, MOYAI) - + countMatches(message, customMoyaiRe); - - return Math.min(count, 10); -} - -function boom() { - if (!settings.store.triggerWhenUnfocused && !document.hasFocus()) return; - const audioElement = document.createElement("audio"); - - audioElement.src = settings.store.quality === "HD" - ? MOYAI_URL_HD - : MOYAI_URL; - - audioElement.volume = settings.store.volume; - audioElement.play(); -} From 0985d2c8cdc7ce88f8f5e78db18305eccffb9970 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 24 May 2025 18:36:17 +0200 Subject: [PATCH 07/54] Delete NoRPC plugin This plugin is incredibly niche and leads to a lot of confusion for users enabling it by mistake. If you want to avoid Discord scanning processes on your system, consider using third party desktop clients like Vesktop or Legcord --- src/plugins/noRPC.discordDesktop/index.ts | 35 ----------------------- 1 file changed, 35 deletions(-) delete mode 100644 src/plugins/noRPC.discordDesktop/index.ts diff --git a/src/plugins/noRPC.discordDesktop/index.ts b/src/plugins/noRPC.discordDesktop/index.ts deleted file mode 100644 index 4c6319e5..00000000 --- a/src/plugins/noRPC.discordDesktop/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { Devs } from "@utils/constants"; -import definePlugin from "@utils/types"; - -export default definePlugin({ - name: "NoRPC", - description: "Disables Discord's RPC server.", - authors: [Devs.Cyn], - patches: [ - { - find: '.ensureModule("discord_rpc")', - replacement: { - match: /\.ensureModule\("discord_rpc"\)\.then\(\(.+?\)}\)}/, - replace: '.ensureModule("discord_rpc")}', - }, - }, - ], -}); From a9dc81e60037546cab914442018ac3ca4cefc1d2 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 24 May 2025 18:39:36 +0200 Subject: [PATCH 08/54] Delete Partymode plugin This plugin is useless. You can just enable party mode via the easter egg --- src/plugins/partyMode/index.ts | 109 --------------------------------- 1 file changed, 109 deletions(-) delete mode 100644 src/plugins/partyMode/index.ts diff --git a/src/plugins/partyMode/index.ts b/src/plugins/partyMode/index.ts deleted file mode 100644 index f7cddbf9..00000000 --- a/src/plugins/partyMode/index.ts +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2023 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { definePluginSettings } from "@api/Settings"; -import { Devs } from "@utils/constants"; -import definePlugin, { OptionType, ReporterTestable } from "@utils/types"; -import { FluxDispatcher } from "@webpack/common"; - -const enum Intensity { - Normal, - Better, - ProjectX, -} - -const settings = definePluginSettings({ - superIntensePartyMode: { - description: "Party intensity", - type: OptionType.SELECT, - options: [ - { label: "Normal", value: Intensity.Normal, default: true }, - { label: "Better", value: Intensity.Better }, - { label: "Project X", value: Intensity.ProjectX }, - ], - restartNeeded: false, - onChange: setSettings - }, -}); - -export default definePlugin({ - name: "PartyMode", - description: "Allows you to use party mode cause the party never ends ✨", - authors: [Devs.UwUDev], - reporterTestable: ReporterTestable.None, - settings, - - start() { - setPoggerState(true); - setSettings(settings.store.superIntensePartyMode); - }, - - stop() { - setPoggerState(false); - }, -}); - -function setPoggerState(state: boolean) { - FluxDispatcher.dispatch({ - type: "POGGERMODE_SETTINGS_UPDATE", - settings: { - enabled: state, - settingsVisible: state - } - }); -} - -function setSettings(intensity: Intensity) { - const state = { - screenshakeEnabledLocations: { 0: true, 1: true, 2: true }, - shakeIntensity: 1, - confettiSize: 16, - confettiCount: 5, - combosRequiredCount: 1 - }; - - switch (intensity) { - case Intensity.Normal: { - Object.assign(state, { - screenshakeEnabledLocations: { 0: true, 1: false, 2: false }, - combosRequiredCount: 5 - }); - break; - } - case Intensity.Better: { - Object.assign(state, { - confettiSize: 12, - confettiCount: 8, - }); - break; - } - case Intensity.ProjectX: { - Object.assign(state, { - shakeIntensity: 20, - confettiSize: 25, - confettiCount: 15, - }); - break; - } - } - - FluxDispatcher.dispatch({ - type: "POGGERMODE_SETTINGS_UPDATE", - settings: state - }); -} From 489ed5527c6848032a9d9f36deacac84dd3d9af0 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 24 May 2025 20:54:03 -0400 Subject: [PATCH 09/54] Add Back Deleted Plugins --- README.md | 6 +- src/equicordplugins/moreKaomoji/index.ts | 216 ++++++++++++++++++ src/equicordplugins/moyai/index.ts | 175 ++++++++++++++ .../noRPC.discordDesktop/index.ts | 23 ++ .../partyMode/index.ts | 0 5 files changed, 419 insertions(+), 1 deletion(-) create mode 100644 src/equicordplugins/moreKaomoji/index.ts create mode 100644 src/equicordplugins/moyai/index.ts create mode 100644 src/equicordplugins/noRPC.discordDesktop/index.ts rename src/{plugins => equicordplugins}/partyMode/index.ts (100%) diff --git a/README.md b/README.md index 595f20ed..c3fd00c3 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
-175 additional plugins +181 additional plugins ### All Platforms @@ -109,9 +109,11 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch - MessageLoggerEnhanced by Aria - MessageTranslate by Samwich - ModalFade by Kyuuhachi +- MoreKaomoji by JacobTm & voidbbg - MoreStickers by Leko & Arjix - MoreUserTags by Cyn, TheSun, RyanCaoDev, LordElias, AutumnVN, hen - Morse by zyqunix +- Moyai by Megu & Nuckyz - NeverPausePreviews by vappstar - NewPluginsManager by Sqaaakoi - NoAppsAllowed by kvba @@ -122,7 +124,9 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch - NoNitroUpsell by thororen - NoOnboarding by omaw & Glitch - NoRoleHeaders by Samwich +- NoRPC by Cyn - NotificationTitle by Kyuuhachi +- PartyMode by UwUDev - PingNotifications by smuki - PinIcon by iamme - PlatformSpoofer by Drag diff --git a/src/equicordplugins/moreKaomoji/index.ts b/src/equicordplugins/moreKaomoji/index.ts new file mode 100644 index 00000000..986b1f33 --- /dev/null +++ b/src/equicordplugins/moreKaomoji/index.ts @@ -0,0 +1,216 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2022 Vendicated and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +import { findOption, OptionalMessageOption } from "@api/Commands"; +import { Devs, EquicordDevs } from "@utils/constants"; +import definePlugin from "@utils/types"; + +export default definePlugin({ + name: "MoreKaomoji", + description: "Adds more Kaomoji to discord. ヽ(´▽`)/", + authors: [Devs.JacobTm, EquicordDevs.voidbbg], + commands: [ + { + name: "dissatisfaction", + description: " >﹏<", + options: [OptionalMessageOption], + execute: opts => ({ + content: findOption(opts, "message", "") + " " + " >﹏<", + }), + }, + { + name: "smug", + description: "ಠ_ಠ", + options: [OptionalMessageOption], + execute: opts => ({ + content: findOption(opts, "message", "") + " " + "ಠ_ಠ", + }), + }, + { + name: "happy", + description: "ヽ(´▽`)/", + options: [OptionalMessageOption], + execute: opts => ({ + content: findOption(opts, "message", "") + " " + "ヽ(´▽`)/", + }), + }, + { + name: "crying", + description: "ಥ_ಥ", + options: [OptionalMessageOption], + execute: opts => ({ + content: findOption(opts, "message", "") + " " + "ಥ_ಥ", + }), + }, + { + name: "angry", + description: "ヽ(`Д´)ノ", + options: [OptionalMessageOption], + execute: opts => ({ + content: findOption(opts, "message", "") + " " + "ヽ(`Д´)ノ", + }), + }, + { + name: "anger", + description: "ヽ(o`皿′o)ノ", + options: [OptionalMessageOption], + execute: opts => ({ + content: findOption(opts, "message", "") + " " + "ヽ(o`皿′o)ノ", + }), + }, + { + name: "joy", + description: "<( ̄︶ ̄)>", + options: [OptionalMessageOption], + execute: opts => ({ + content: findOption(opts, "message", "") + " " + "<( ̄︶ ̄)>", + }), + }, + { + name: "blush", + description: "૮ ˶ᵔ ᵕ ᵔ˶ ა", + options: [OptionalMessageOption], + execute: opts => ({ + content: findOption(opts, "message", "") + " " + "૮ ˶ᵔ ᵕ ᵔ˶ ა", + }), + }, + { + name: "confused", + description: "(•ิ_•ิ)?", + options: [OptionalMessageOption], + execute: opts => ({ + content: findOption(opts, "message", "") + " " + "(•ิ_•ิ)?", + }), + }, + { + name: "sleeping", + description: "(ᴗ_ᴗ)", + options: [OptionalMessageOption], + execute: opts => ({ + content: findOption(opts, "message", "") + " " + "(ᴗ_ᴗ)", + }), + }, + { + name: "laughing", + description: "o(≧▽≦)o", + options: [OptionalMessageOption], + execute: opts => ({ + content: findOption(opts, "message", "") + " " + "o(≧▽≦)o", + }), + }, + /* + even more kaomoji + */ + { + name: "giving", + description: "(ノ◕ヮ◕)ノ*:・゚✧", + options: [OptionalMessageOption], + execute: opts => ({ + content: findOption(opts, "message", "") + " " + "(ノ◕ヮ◕)ノ*:・゚✧", + }), + }, + { + name: "peace", + description: "✌(◕‿-)✌", + options: [OptionalMessageOption], + execute: opts => ({ + content: findOption(opts, "message", "") + " " + "✌(◕‿-)✌", + }), + }, + { + name: "ending1", + description: "Ꮺ ָ࣪ ۰ ͙⊹", + options: [OptionalMessageOption], + execute: opts => ({ + content: findOption(opts, "message", "") + " " + "Ꮺ ָ࣪ ۰ ͙⊹", + }), + }, + { + name: "uwu", + description: "(>⩊<)", + options: [OptionalMessageOption], + execute: opts => ({ + content: findOption(opts, "message", "") + " " + "(>⩊<)", + }), + }, + { + name: "comfy", + description: "(─‿‿─)♡", + options: [OptionalMessageOption], + execute: opts => ({ + content: findOption(opts, "message", "") + " " + "(─‿‿─)♡", + }), + }, + { + name: "lovehappy", + description: "(*≧ω≦*)", + options: [OptionalMessageOption], + execute: opts => ({ + content: findOption(opts, "message", "") + " " + "(*≧ω≦*)", + }), + }, + { + name: "loveee", + description: "(⁄ ⁄>⁄ ▽ ⁄<⁄ ⁄)", + options: [OptionalMessageOption], + execute: opts => ({ + content: findOption(opts, "message", "") + " " + "(⁄ ⁄>⁄ ▽ ⁄<⁄ ⁄)", + }), + }, + { + name: "give", + description: "(ノ= ⩊ = )ノ", + options: [OptionalMessageOption], + execute: opts => ({ + content: findOption(opts, "message", "") + " " + "(ノ= ⩊ = )ノ", + }), + }, + { + name: "lovegive", + description: "ღゝ◡╹)ノ♡", + options: [OptionalMessageOption], + execute: opts => ({ + content: findOption(opts, "message", "") + " " + "ღゝ◡╹)ノ♡", + }), + }, + { + name: "music", + description: "( ̄▽ ̄)/♫•¨•.¸¸♪", + options: [OptionalMessageOption], + execute: opts => ({ + content: findOption(opts, "message", "") + " " + "( ̄▽ ̄)/♫•¨•.¸¸♪", + }), + }, + { + name: "stars", + description: ".𖥔 ݁ ˖๋ ࣭ ⭑", + options: [OptionalMessageOption], + execute: opts => ({ + content: findOption(opts, "message", "") + " " + ".𖥔 ݁ ˖๋ ࣭ ⭑", + }), + }, + { + name: "lovegiving", + description: "⸜(。˃ ᵕ ˂ )⸝♡", + options: [OptionalMessageOption], + execute: opts => ({ + content: findOption(opts, "message", "") + " " + "⸜(。˃ ᵕ ˂ )⸝♡", + }), + } + ] +}); diff --git a/src/equicordplugins/moyai/index.ts b/src/equicordplugins/moyai/index.ts new file mode 100644 index 00000000..a0f76b01 --- /dev/null +++ b/src/equicordplugins/moyai/index.ts @@ -0,0 +1,175 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2022 Vendicated and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +import { definePluginSettings } from "@api/Settings"; +import { makeRange } from "@components/PluginSettings/components/SettingSliderComponent"; +import { Devs } from "@utils/constants"; +import { sleep } from "@utils/misc"; +import definePlugin, { OptionType } from "@utils/types"; +import { RelationshipStore, SelectedChannelStore, UserStore } from "@webpack/common"; +import { Message, ReactionEmoji } from "discord-types/general"; + +interface IMessageCreate { + type: "MESSAGE_CREATE"; + optimistic: boolean; + isPushNotification: boolean; + channelId: string; + message: Message; +} + +interface IReactionAdd { + type: "MESSAGE_REACTION_ADD"; + optimistic: boolean; + channelId: string; + messageId: string; + messageAuthorId: string; + userId: "195136840355807232"; + emoji: ReactionEmoji; +} + +interface IVoiceChannelEffectSendEvent { + type: string; + emoji?: ReactionEmoji; // Just in case... + channelId: string; + userId: string; + animationType: number; + animationId: number; +} + +const MOYAI = "🗿"; +const MOYAI_URL = "https://github.com/Equicord/Equibored/raw/main/sounds/moyai/moyai.mp3"; +const MOYAI_URL_HD = "https://github.com/Equicord/Equibored/raw/main/sounds/moyai/moyai.wav"; + +const settings = definePluginSettings({ + volume: { + description: "Volume of the 🗿🗿🗿", + type: OptionType.SLIDER, + markers: makeRange(0, 1, 0.1), + default: 0.5, + stickToMarkers: false + }, + quality: { + description: "Quality of the 🗿🗿🗿", + type: OptionType.SELECT, + options: [ + { label: "Normal", value: "Normal", default: true }, + { label: "HD", value: "HD" } + ], + }, + triggerWhenUnfocused: { + description: "Trigger the 🗿 even when the window is unfocused", + type: OptionType.BOOLEAN, + default: true + }, + ignoreBots: { + description: "Ignore bots", + type: OptionType.BOOLEAN, + default: true + }, + ignoreBlocked: { + description: "Ignore blocked users", + type: OptionType.BOOLEAN, + default: true + } +}); + +export default definePlugin({ + name: "Moyai", + authors: [Devs.Megu, Devs.Nuckyz], + description: "🗿🗿🗿🗿🗿🗿🗿🗿", + settings, + + flux: { + async MESSAGE_CREATE({ optimistic, type, message, channelId }: IMessageCreate) { + if (optimistic || type !== "MESSAGE_CREATE") return; + if (message.state === "SENDING") return; + if (settings.store.ignoreBots && message.author?.bot) return; + if (settings.store.ignoreBlocked && RelationshipStore.isBlocked(message.author?.id)) return; + if (!message.content) return; + if (channelId !== SelectedChannelStore.getChannelId()) return; + + const moyaiCount = getMoyaiCount(message.content); + + for (let i = 0; i < moyaiCount; i++) { + boom(); + await sleep(300); + } + }, + + MESSAGE_REACTION_ADD({ optimistic, type, channelId, userId, messageAuthorId, emoji }: IReactionAdd) { + if (optimistic || type !== "MESSAGE_REACTION_ADD") return; + if (settings.store.ignoreBots && UserStore.getUser(userId)?.bot) return; + if (settings.store.ignoreBlocked && RelationshipStore.isBlocked(messageAuthorId)) return; + if (channelId !== SelectedChannelStore.getChannelId()) return; + + const name = emoji.name.toLowerCase(); + if (name !== MOYAI && !name.includes("moyai") && !name.includes("moai")) return; + + boom(); + }, + + VOICE_CHANNEL_EFFECT_SEND({ emoji }: IVoiceChannelEffectSendEvent) { + if (!emoji?.name) return; + const name = emoji.name.toLowerCase(); + if (name !== MOYAI && !name.includes("moyai") && !name.includes("moai")) return; + + boom(); + } + } +}); + +function countOccurrences(sourceString: string, subString: string) { + let i = 0; + let lastIdx = 0; + while ((lastIdx = sourceString.indexOf(subString, lastIdx) + 1) !== 0) + i++; + + return i; +} + +function countMatches(sourceString: string, pattern: RegExp) { + if (!pattern.global) + throw new Error("pattern must be global"); + + let i = 0; + while (pattern.test(sourceString)) + i++; + + return i; +} + +const customMoyaiRe = //gi; + +function getMoyaiCount(message: string) { + const count = countOccurrences(message, MOYAI) + + countMatches(message, customMoyaiRe); + + return Math.min(count, 10); +} + +function boom() { + if (!settings.store.triggerWhenUnfocused && !document.hasFocus()) return; + const audioElement = document.createElement("audio"); + + audioElement.src = settings.store.quality === "HD" + ? MOYAI_URL_HD + : MOYAI_URL; + + audioElement.volume = settings.store.volume; + audioElement.play(); +} diff --git a/src/equicordplugins/noRPC.discordDesktop/index.ts b/src/equicordplugins/noRPC.discordDesktop/index.ts new file mode 100644 index 00000000..51ed1f96 --- /dev/null +++ b/src/equicordplugins/noRPC.discordDesktop/index.ts @@ -0,0 +1,23 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; + +export default definePlugin({ + name: "NoRPC", + description: "Disables Discord's RPC server.", + authors: [Devs.Cyn], + patches: [ + { + find: '.ensureModule("discord_rpc")', + replacement: { + match: /\.ensureModule\("discord_rpc"\)\.then\(\(.+?\)}\)}/, + replace: '.ensureModule("discord_rpc")}', + }, + }, + ], +}); diff --git a/src/plugins/partyMode/index.ts b/src/equicordplugins/partyMode/index.ts similarity index 100% rename from src/plugins/partyMode/index.ts rename to src/equicordplugins/partyMode/index.ts From 48c58a97bcd36ba85a861b2577545cc45881078f Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 24 May 2025 20:57:38 -0400 Subject: [PATCH 10/54] Fix Readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c3fd00c3..63025af4 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,6 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch - NoNitroUpsell by thororen - NoOnboarding by omaw & Glitch - NoRoleHeaders by Samwich -- NoRPC by Cyn - NotificationTitle by Kyuuhachi - PartyMode by UwUDev - PingNotifications by smuki @@ -202,6 +201,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch - ClipsEnhancements by niko - MediaDownloader by Colorman +- NoRPC by Cyn - StatusWhilePlaying by thororen ### Equicord Devbuilds Only From 7a74182addb66d5115d9e0dc5c474e0b62d1f935 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 24 May 2025 21:29:42 -0400 Subject: [PATCH 11/54] Fix arRPC For Equibop --- src/plugins/arRPC.web/index.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/plugins/arRPC.web/index.tsx b/src/plugins/arRPC.web/index.tsx index 685bb600..00fc0f16 100644 --- a/src/plugins/arRPC.web/index.tsx +++ b/src/plugins/arRPC.web/index.tsx @@ -36,13 +36,21 @@ async function lookupApp(applicationId: string): Promise { return socket.application; } +let hideSetting = false; + +if (IS_VESKTOP || IS_EQUIBOP || "legcord" in window) { + hideSetting = true; +} else if ("goofcord" in window) { + hideSetting = false; +} + let ws: WebSocket; export default definePlugin({ name: "WebRichPresence (arRPC)", description: "Client plugin for arRPC to enable RPC on Discord Web (experimental)", authors: [Devs.Ducko], reporterTestable: ReporterTestable.None, - hidden: IS_VESKTOP || "legcord" in window, + hidden: hideSetting, settingsAboutComponent: () => ( <> From 2aae71abf7a185e0c50762b106a7bd61fed37b88 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Mon, 26 May 2025 12:39:23 -0400 Subject: [PATCH 12/54] MLE: IgnoreWebhooks Setting --- src/equicordplugins/messageLoggerEnhanced/index.tsx | 3 ++- src/equicordplugins/messageLoggerEnhanced/settings.tsx | 6 ++++++ src/equicordplugins/messageLoggerEnhanced/utils/index.ts | 7 +++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/equicordplugins/messageLoggerEnhanced/index.tsx b/src/equicordplugins/messageLoggerEnhanced/index.tsx index bbf4df3a..e4c560d8 100644 --- a/src/equicordplugins/messageLoggerEnhanced/index.tsx +++ b/src/equicordplugins/messageLoggerEnhanced/index.tsx @@ -78,7 +78,8 @@ async function messageDeleteHandler(payload: MessageDeletePayload & { isBulk: bo bot: message?.bot || message?.author?.bot, flags: message?.flags, ghostPinged, - isCachedByUs: (message as LoggedMessageJSON).ourCache + isCachedByUs: (message as LoggedMessageJSON).ourCache, + webhookId: message?.webhookId }) ) { // Flogger.log("IGNORING", message, payload); diff --git a/src/equicordplugins/messageLoggerEnhanced/settings.tsx b/src/equicordplugins/messageLoggerEnhanced/settings.tsx index 00f2bee5..d21ea4b0 100644 --- a/src/equicordplugins/messageLoggerEnhanced/settings.tsx +++ b/src/equicordplugins/messageLoggerEnhanced/settings.tsx @@ -52,6 +52,12 @@ export const settings = definePluginSettings({ } }, + ignoreWebhooks: { + type: OptionType.BOOLEAN, + description: "Whether to ignore messages by webhooks", + default: false, + }, + ignoreSelf: { type: OptionType.BOOLEAN, description: "Whether to ignore messages by yourself", diff --git a/src/equicordplugins/messageLoggerEnhanced/utils/index.ts b/src/equicordplugins/messageLoggerEnhanced/utils/index.ts index cff08e2c..082e2c19 100644 --- a/src/equicordplugins/messageLoggerEnhanced/utils/index.ts +++ b/src/equicordplugins/messageLoggerEnhanced/utils/index.ts @@ -74,6 +74,7 @@ interface ShouldIgnoreArguments { bot?: boolean; ghostPinged?: boolean; isCachedByUs?: boolean; + webhookId?: string; } const EPHEMERAL = 64; @@ -87,7 +88,7 @@ const UserGuildSettingsStore = findStoreLazy("UserGuildSettingsStore"); * @param {ShouldIgnoreArguments} args - An object containing the message details. * @returns {boolean} - True if the message should be ignored, false if it should be kept. */ -export function shouldIgnore({ channelId, authorId, guildId, flags, bot, ghostPinged, isCachedByUs }: ShouldIgnoreArguments): boolean { +export function shouldIgnore({ channelId, authorId, guildId, flags, bot, ghostPinged, isCachedByUs, webhookId }: ShouldIgnoreArguments): boolean { const isEphemeral = ((flags ?? 0) & EPHEMERAL) === EPHEMERAL; if (isEphemeral) return true; // ignore @@ -96,7 +97,7 @@ export function shouldIgnore({ channelId, authorId, guildId, flags, bot, ghostPi const myId = UserStore.getCurrentUser().id; const { ignoreUsers, ignoreChannels, ignoreGuilds } = Settings.plugins.MessageLogger; - const { ignoreBots, ignoreSelf } = settings.store; + const { ignoreBots, ignoreSelf, ignoreWebhooks } = settings.store; if (ignoreSelf && authorId === myId) return true; // ignore @@ -132,6 +133,8 @@ export function shouldIgnore({ channelId, authorId, guildId, flags, bot, ghostPi if ((ignoreBots && bot) && !isAuthorWhitelisted) return true; // ignore + if ((ignoreWebhooks && webhookId) && !isAuthorWhitelisted) return true; + if (ghostPinged) return false; // keep // author has highest priority From 43e8870dbc2d76986c68d8004f09b388bbb00a2c Mon Sep 17 00:00:00 2001 From: Creation's Date: Wed, 28 May 2025 09:25:51 -0400 Subject: [PATCH 13/54] feat(timezones): add a database option (#272) * add a database option for timezones * forgot to remove wrong comments lol --- .../timezones/TimezoneModal.tsx | 50 +++++-- src/equicordplugins/timezones/database.tsx | 125 +++++++++++++++++ src/equicordplugins/timezones/index.tsx | 132 ++++++++++++++---- 3 files changed, 266 insertions(+), 41 deletions(-) create mode 100644 src/equicordplugins/timezones/database.tsx diff --git a/src/equicordplugins/timezones/TimezoneModal.tsx b/src/equicordplugins/timezones/TimezoneModal.tsx index 1f01922d..a1038eb3 100644 --- a/src/equicordplugins/timezones/TimezoneModal.tsx +++ b/src/equicordplugins/timezones/TimezoneModal.tsx @@ -8,9 +8,10 @@ import * as DataStore from "@api/DataStore"; import { classNameFactory } from "@api/Styles"; import { Margins } from "@utils/margins"; import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot } from "@utils/modal"; -import { Button, Forms, SearchableSelect, useMemo, useState } from "@webpack/common"; +import { Button, Forms, SearchableSelect, useEffect, useMemo, useState } from "@webpack/common"; -import { DATASTORE_KEY, timezones } from "."; +import { DATASTORE_KEY, settings, timezones } from "."; +import { getTimezone, setTimezone, setUserDatabaseTimezone } from "./database"; export async function setUserTimezone(userId: string, timezone: string | null) { timezones[userId] = timezone; @@ -19,9 +20,24 @@ export async function setUserTimezone(userId: string, timezone: string | null) { const cl = classNameFactory("vc-timezone-"); -export function SetTimezoneModal({ userId, modalProps }: { userId: string, modalProps: ModalProps; }) { +export function SetTimezoneModal({ userId, modalProps, database }: { userId: string, modalProps: ModalProps; database?: boolean; }) { const [currentValue, setCurrentValue] = useState(timezones[userId] ?? null); + useEffect(() => { + if (!database) return; + + const localTimezone = timezones[userId]; + const shouldUseDatabase = + settings.store.useDatabase && + (settings.store.preferDatabaseOverLocal || localTimezone == null); + + if (shouldUseDatabase) { + getTimezone(userId).then(setCurrentValue); + } else { + setCurrentValue(localTimezone); + } + }, [userId, settings.store.useDatabase, settings.store.preferDatabaseOverLocal, database]); + const options = useMemo(() => { return Intl.supportedValuesOf("timeZone").map(timezone => { const offset = new Intl.DateTimeFormat(undefined, { timeZone: timezone, timeZoneName: "short" }) @@ -59,20 +75,28 @@ export function SetTimezoneModal({ userId, modalProps }: { userId: string, modal - + {!database && ( + + )} + ) + }, + + resetDatabaseTimezone: { + description: "Reset your timezone on the database", + type: OptionType.COMPONENT, + component: () => ( + + ) } }); @@ -66,23 +112,33 @@ interface Props { } const TimestampComponent = ErrorBoundary.wrap(({ userId, timestamp, type }: Props) => { const [currentTime, setCurrentTime] = useState(timestamp || Date.now()); - const timezone = timezones[userId]; + const [timezone, setTimezone] = useState(null); useEffect(() => { - let timer: NodeJS.Timeout; + const localTimezone = timezones[userId]; + const shouldUseDatabase = + settings.store.useDatabase && + (settings.store.preferDatabaseOverLocal || localTimezone == null); - if (type === "profile") { - setCurrentTime(Date.now()); - - const now = new Date(); - const delay = (60 - now.getSeconds()) * 1000 + 1000 - now.getMilliseconds(); - - timer = setTimeout(() => { - setCurrentTime(Date.now()); - }, delay); + if (shouldUseDatabase) { + getTimezone(userId).then(setTimezone); + } else { + setTimezone(localTimezone); } + }, [userId, settings.store.useDatabase, settings.store.preferDatabaseOverLocal]); - return () => timer && clearTimeout(timer); + useEffect(() => { + if (type !== "profile") return; + + setCurrentTime(Date.now()); + + const now = new Date(); + const delay = (60 - now.getSeconds()) * 1000 + 1000 - now.getMilliseconds(); + const timer = setTimeout(() => { + setCurrentTime(Date.now()); + }, delay); + + return () => clearTimeout(timer); }, [type, currentTime]); if (!timezone) return null; @@ -94,8 +150,9 @@ const TimestampComponent = ErrorBoundary.wrap(({ userId, timestamp, type }: Prop month: "long", day: "numeric", hour: "numeric", - minute: "numeric", + minute: "numeric" }); + return ( - {toolTipProps => { - return ( - - { - type === "message" ? `(${shortTime})` : shortTime - } - - ); - }} + {toolTipProps => ( + + {type === "message" ? `(${shortTime})` : shortTime} + + )} ); }, { noop: true }); + const userContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: { user: User; }) => { if (user?.id == null) return; @@ -136,11 +190,33 @@ const userContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: { children.push(, setTimezoneItem); + if (settings.store.useDatabase) { + const refreshTimezoneItem = ( + { + showToast("Refreshing timezone...", Toasts.Type.CLOCK); + + try { + const timezone = await getTimezone(user.id); + setUserDatabaseTimezone(user.id, timezone); + timezones[user.id] = timezone; + showToast("Timezone refreshed successfully!", Toasts.Type.SUCCESS); + } catch (error) { + console.error("Failed to refresh timezone:", error); + showToast("Failed to refresh timezone.", Toasts.Type.FAILURE); + } + }} + /> + ); + children.push(refreshTimezoneItem); + } }; export default definePlugin({ name: "Timezones", - authors: [Devs.Aria], + authors: [Devs.Aria, EquicordDevs.creations], description: "Shows the local time of users in profiles and message headers", contextMenus: { "user-context": userContextMenuPatch From f99327caa18c18c2cd310ef24f89f9b7803a96e3 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 28 May 2025 09:28:07 -0400 Subject: [PATCH 14/54] Timezones: Default To Use DB --- src/equicordplugins/splitLargeMessages/index.ts | 3 +-- src/equicordplugins/timezones/index.tsx | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/equicordplugins/splitLargeMessages/index.ts b/src/equicordplugins/splitLargeMessages/index.ts index cb19fe70..50dadff5 100644 --- a/src/equicordplugins/splitLargeMessages/index.ts +++ b/src/equicordplugins/splitLargeMessages/index.ts @@ -61,8 +61,7 @@ const listener: MessageSendListener = async (channelId, msg) => { if (hardSplit || splitIndex === -1) { chunks.push(msg.content.slice(0, maxLength)); msg.content = msg.content.slice(maxLength); - } - else { + } else { chunks.push(msg.content.slice(0, splitIndex)); msg.content = msg.content.slice(splitIndex); } diff --git a/src/equicordplugins/timezones/index.tsx b/src/equicordplugins/timezones/index.tsx index c8095bf2..526f8fb0 100644 --- a/src/equicordplugins/timezones/index.tsx +++ b/src/equicordplugins/timezones/index.tsx @@ -52,7 +52,7 @@ export const settings = definePluginSettings({ useDatabase: { type: OptionType.BOOLEAN, description: "Enable database for getting user timezones", - default: false + default: true }, preferDatabaseOverLocal: { From 76b1fe9a872586121dd3f98fb28cf2fd1c8b1a9f Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 28 May 2025 10:38:20 -0300 Subject: [PATCH 15/54] Delete MoreCommands plugin This plugin is useless and the commands it adds have almost no use or can be replaced using the TextReplace plugin --- src/plugins/moreCommands/index.ts | 65 ------------------------------- 1 file changed, 65 deletions(-) delete mode 100644 src/plugins/moreCommands/index.ts diff --git a/src/plugins/moreCommands/index.ts b/src/plugins/moreCommands/index.ts deleted file mode 100644 index 02f3c373..00000000 --- a/src/plugins/moreCommands/index.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated, Samu and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { ApplicationCommandInputType, findOption, OptionalMessageOption, RequiredMessageOption, sendBotMessage } from "@api/Commands"; -import { Devs } from "@utils/constants"; -import definePlugin from "@utils/types"; - - -function mock(input: string): string { - let output = ""; - for (let i = 0; i < input.length; i++) { - output += i % 2 ? input[i].toUpperCase() : input[i].toLowerCase(); - } - return output; -} - -export default definePlugin({ - name: "MoreCommands", - description: "echo, lenny, mock", - authors: [Devs.Arjix, Devs.echo, Devs.Samu], - commands: [ - { - name: "echo", - description: "Sends a message as Clyde (locally)", - options: [OptionalMessageOption], - inputType: ApplicationCommandInputType.BOT, - execute: (opts, ctx) => { - const content = findOption(opts, "message", ""); - - sendBotMessage(ctx.channel.id, { content }); - }, - }, - { - name: "lenny", - description: "Sends a lenny face", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " ( ͡° ͜ʖ ͡°)" - }), - }, - { - name: "mock", - description: "mOcK PeOpLe", - options: [RequiredMessageOption], - execute: opts => ({ - content: mock(findOption(opts, "message", "")) - }), - }, - ] -}); From c9ec915dcc0e9a5185f7d5fdade542c1aeb466c4 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 28 May 2025 09:45:37 -0400 Subject: [PATCH 16/54] Timezones: If DB Fails Use Local Timezone --- .../timezones/TimezoneModal.tsx | 4 ++-- src/equicordplugins/timezones/database.tsx | 11 ++-------- src/equicordplugins/timezones/index.tsx | 20 ++++++++++++++----- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/equicordplugins/timezones/TimezoneModal.tsx b/src/equicordplugins/timezones/TimezoneModal.tsx index a1038eb3..4b9f859c 100644 --- a/src/equicordplugins/timezones/TimezoneModal.tsx +++ b/src/equicordplugins/timezones/TimezoneModal.tsx @@ -29,10 +29,10 @@ export function SetTimezoneModal({ userId, modalProps, database }: { userId: str const localTimezone = timezones[userId]; const shouldUseDatabase = settings.store.useDatabase && - (settings.store.preferDatabaseOverLocal || localTimezone == null); + (settings.store.preferDatabaseOverLocal || !localTimezone); if (shouldUseDatabase) { - getTimezone(userId).then(setCurrentValue); + getTimezone(userId).then(e => setCurrentValue(e ?? localTimezone)); } else { setCurrentValue(localTimezone); } diff --git a/src/equicordplugins/timezones/database.tsx b/src/equicordplugins/timezones/database.tsx index cda64d60..45e13b58 100644 --- a/src/equicordplugins/timezones/database.tsx +++ b/src/equicordplugins/timezones/database.tsx @@ -4,24 +4,17 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -type CacheEntry = { - value: string | null; - expires: number; -}; - import { DataStore } from "@api/index"; import { openModal } from "@utils/modal"; import { OAuth2AuthorizeModal, showToast, Toasts } from "@webpack/common"; +import { databaseTimezones } from "."; + export const DOMAIN = "https://timezone.creations.works"; export const REDIRECT_URI = `${DOMAIN}/auth/discord/callback`; export const CLIENT_ID = "1377021506810417173"; export const DATASTORE_KEY = "vencord-database-timezones"; -export let databaseTimezones: Record = {}; -(async () => { - databaseTimezones = await DataStore.get>(DATASTORE_KEY) || {}; -})(); const pendingRequests: Record> = {}; diff --git a/src/equicordplugins/timezones/index.tsx b/src/equicordplugins/timezones/index.tsx index 526f8fb0..74c49c22 100644 --- a/src/equicordplugins/timezones/index.tsx +++ b/src/equicordplugins/timezones/index.tsx @@ -22,10 +22,14 @@ import { SetTimezoneModal } from "./TimezoneModal"; export const DATASTORE_KEY = "vencord-timezones"; +type CacheEntry = { + value: string | null; + expires: number; +}; + +export let databaseTimezones: Record = {}; + export let timezones: Record = {}; -(async () => { - timezones = await DataStore.get>(DATASTORE_KEY) || {}; -})(); const classes = findByPropsLazy("timestamp", "compact", "contentOnly"); const locale = findByPropsLazy("getLocale"); @@ -118,10 +122,10 @@ const TimestampComponent = ErrorBoundary.wrap(({ userId, timestamp, type }: Prop const localTimezone = timezones[userId]; const shouldUseDatabase = settings.store.useDatabase && - (settings.store.preferDatabaseOverLocal || localTimezone == null); + (settings.store.preferDatabaseOverLocal || !localTimezone); if (shouldUseDatabase) { - getTimezone(userId).then(setTimezone); + getTimezone(userId).then(e => setTimezone(e ?? localTimezone)); } else { setTimezone(localTimezone); } @@ -240,6 +244,12 @@ export default definePlugin({ } } ], + + async start() { + databaseTimezones = await DataStore.get>(DATASTORE_KEY) || {}; + timezones = await DataStore.get>(DATASTORE_KEY) || {}; + }, + settings, getTime, From ea9fa095640fbd7120a28624abe8fdacd4073d19 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 28 May 2025 09:49:37 -0400 Subject: [PATCH 17/54] Fix Line Formatting --- src/equicordplugins/timezones/index.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/equicordplugins/timezones/index.tsx b/src/equicordplugins/timezones/index.tsx index 74c49c22..6597ea4a 100644 --- a/src/equicordplugins/timezones/index.tsx +++ b/src/equicordplugins/timezones/index.tsx @@ -20,16 +20,14 @@ import { Message, User } from "discord-types/general"; import { authModal, deleteTimezone, getTimezone, setUserDatabaseTimezone } from "./database"; import { SetTimezoneModal } from "./TimezoneModal"; -export const DATASTORE_KEY = "vencord-timezones"; - type CacheEntry = { value: string | null; expires: number; }; export let databaseTimezones: Record = {}; - export let timezones: Record = {}; +export const DATASTORE_KEY = "vencord-timezones"; const classes = findByPropsLazy("timestamp", "compact", "contentOnly"); const locale = findByPropsLazy("getLocale"); From b706d53998c5e9e097b8d7a8fca4a5e34e1c69a9 Mon Sep 17 00:00:00 2001 From: Ataraxis <190674462+atar4xis@users.noreply.github.com> Date: Wed, 28 May 2025 15:24:48 +0100 Subject: [PATCH 18/54] Fix not respecting contributor badge preference (#3443) --- src/plugins/_api/badges/index.tsx | 4 ++-- src/utils/misc.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index a30f41ce..8745584e 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -27,7 +27,7 @@ import { openContributorModal } from "@components/PluginSettings/ContributorModa import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; -import { isPluginDev } from "@utils/misc"; +import { shouldShowContributorBadge } from "@utils/misc"; import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal"; import definePlugin from "@utils/types"; import { Forms, Toasts, UserStore } from "@webpack/common"; @@ -39,7 +39,7 @@ const ContributorBadge: ProfileBadge = { description: "Vencord Contributor", image: CONTRIBUTOR_BADGE, position: BadgePosition.START, - shouldShow: ({ userId }) => isPluginDev(userId), + shouldShow: ({ userId }) => shouldShowContributorBadge(userId), onClick: (_, { userId }) => openContributorModal(UserStore.getUser(userId)) }; diff --git a/src/utils/misc.ts b/src/utils/misc.ts index 7f9f6e59..7028e00b 100644 --- a/src/utils/misc.ts +++ b/src/utils/misc.ts @@ -92,6 +92,7 @@ export function identity(value: T): T { export const isMobile = navigator.userAgent.includes("Mobi"); export const isPluginDev = (id: string) => Object.hasOwn(DevsById, id); +export const shouldShowContributorBadge = (id: string) => isPluginDev(id) && DevsById[id].badge !== false; export function pluralise(amount: number, singular: string, plural = singular + "s") { return amount === 1 ? `${amount} ${singular}` : `${amount} ${plural}`; From 13827acb8941353752a6e7ce4baad1b1d3fc645f Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 28 May 2025 11:08:45 -0400 Subject: [PATCH 19/54] Rename MoreUserTags to ExpandedUserTags Hopefully stops crashing people --- src/equicordplugins/moreUserTags/index.tsx | 5 ++++- src/equicordplugins/moreUserTags/settings.tsx | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/equicordplugins/moreUserTags/index.tsx b/src/equicordplugins/moreUserTags/index.tsx index b45dac04..b37f597f 100644 --- a/src/equicordplugins/moreUserTags/index.tsx +++ b/src/equicordplugins/moreUserTags/index.tsx @@ -6,6 +6,7 @@ import "./styles.css"; +import { migratePluginSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; import { Devs, EquicordDevs } from "@utils/constants"; import { getCurrentChannel, getIntlMessage } from "@utils/discord"; @@ -31,11 +32,13 @@ const genTagTypes = () => { return obj; }; +migratePluginSettings("ExpandedUserTags", "MoreUserTags"); export default definePlugin({ - name: "MoreUserTags", + name: "ExpandedUserTags", description: "Adds tags for webhooks and moderative roles (owner, admin, etc.)", authors: [Devs.Cyn, Devs.TheSun, Devs.RyanCaoDev, Devs.LordElias, Devs.AutumnVN, EquicordDevs.Hen], dependencies: ["MemberListDecoratorsAPI", "NicknameIconsAPI", "MessageDecorationsAPI"], + tags: ["MoreUserTags"], settings, patches: [ // Make discord actually use our tags diff --git a/src/equicordplugins/moreUserTags/settings.tsx b/src/equicordplugins/moreUserTags/settings.tsx index e3853571..2aec12e6 100644 --- a/src/equicordplugins/moreUserTags/settings.tsx +++ b/src/equicordplugins/moreUserTags/settings.tsx @@ -14,7 +14,7 @@ import { TagSettings } from "./types"; function SettingsComponent() { const tagSettings = settings.store.tagSettings as TagSettings; - const { localTags } = Vencord.Plugins.plugins.MoreUserTags as any; + const { localTags } = Vencord.Plugins.plugins.ExpandedUserTags as any; return ( From 39cde32f0e64e02ebcb057c73926e16f4d16449a Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 28 May 2025 11:42:17 -0400 Subject: [PATCH 20/54] Add A Dev --- src/utils/constants.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/utils/constants.ts b/src/utils/constants.ts index fa32700b..bf4dd6a9 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1074,6 +1074,10 @@ export const EquicordDevs = Object.freeze({ name: "sliwka", id: 1165286199628419129n, }, + bbgaming25k: { + name: "bbgaming25k", + id: 851222385528274964n, + }, } satisfies Record); // iife so #__PURE__ works correctly From f3646bd1ab94d0fcbb77d592987bd1925e678ee6 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 28 May 2025 11:47:06 -0400 Subject: [PATCH 21/54] Fix Badges Link --- src/plugins/_api/badges/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index c3ef0598..81a60d9e 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -77,7 +77,7 @@ async function loadBadges(url: string, noCache = false) { async function loadAllBadges(noCache = false) { const vencordBadges = await loadBadges("https://badges.vencord.dev/badges.json", noCache); - const equicordBadges = await loadBadges("https://equicord.org/badges", noCache); + const equicordBadges = await loadBadges("https://equicord.org/badges.json", noCache); DonorBadges = vencordBadges; EquicordDonorBadges = equicordBadges; From b96f18d076eb44b8009459bb07f984466a692413 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 28 May 2025 14:00:07 -0400 Subject: [PATCH 22/54] ExpandedUserTags: Some Misc Fixes --- .../{moreUserTags => expandedUserTags}/consts.ts | 7 ++++++- .../{moreUserTags => expandedUserTags}/index.tsx | 12 +++++++----- .../{moreUserTags => expandedUserTags}/settings.tsx | 7 ++++++- .../{moreUserTags => expandedUserTags}/styles.css | 0 .../{moreUserTags => expandedUserTags}/types.ts | 0 5 files changed, 19 insertions(+), 7 deletions(-) rename src/equicordplugins/{moreUserTags => expandedUserTags}/consts.ts (89%) rename src/equicordplugins/{moreUserTags => expandedUserTags}/index.tsx (94%) rename src/equicordplugins/{moreUserTags => expandedUserTags}/settings.tsx (95%) rename src/equicordplugins/{moreUserTags => expandedUserTags}/styles.css (100%) rename src/equicordplugins/{moreUserTags => expandedUserTags}/types.ts (100%) diff --git a/src/equicordplugins/moreUserTags/consts.ts b/src/equicordplugins/expandedUserTags/consts.ts similarity index 89% rename from src/equicordplugins/moreUserTags/consts.ts rename to src/equicordplugins/expandedUserTags/consts.ts index 14ea3dd9..8d820637 100644 --- a/src/equicordplugins/moreUserTags/consts.ts +++ b/src/equicordplugins/expandedUserTags/consts.ts @@ -9,9 +9,14 @@ import { GuildStore } from "@webpack/common"; import { RC } from "@webpack/types"; import { Channel, Guild, Message, User } from "discord-types/general"; +import { settings } from "./settings"; import type { ITag } from "./types"; -export const isWebhook = (message: Message, user: User) => !!message?.webhookId && user.isNonUserBot(); +export const isWebhook = (message: Message, user: User) => { + const isFollowed = message?.type === 0 && !!message?.messageReference && !settings.store.showWebhookTagFully; + return !!message?.webhookId && user.isNonUserBot() && !isFollowed; +}; + export const tags = [ { name: "WEBHOOK", diff --git a/src/equicordplugins/moreUserTags/index.tsx b/src/equicordplugins/expandedUserTags/index.tsx similarity index 94% rename from src/equicordplugins/moreUserTags/index.tsx rename to src/equicordplugins/expandedUserTags/index.tsx index b37f597f..3948b4a1 100644 --- a/src/equicordplugins/moreUserTags/index.tsx +++ b/src/equicordplugins/expandedUserTags/index.tsx @@ -76,7 +76,7 @@ export default definePlugin({ renderNicknameIcon(props) { const tagId = this.getTag({ user: UserStore.getUser(props.userId), - channel: ChannelStore.getChannel(this.getChannelId()), + channel: getCurrentChannel(), channelId: this.getChannelId(), isChat: false }); @@ -92,7 +92,7 @@ export default definePlugin({ message: props.message, user: UserStore.getUser(props.message.author.id), channelId: props.message.channel_id, - isChat: false + isChat: true }); return tagId && Date: Wed, 28 May 2025 14:21:23 -0400 Subject: [PATCH 23/54] Timezones: Fix Refresh --- src/equicordplugins/timezones/database.tsx | 4 ++-- src/equicordplugins/timezones/index.tsx | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/equicordplugins/timezones/database.tsx b/src/equicordplugins/timezones/database.tsx index 45e13b58..643f760b 100644 --- a/src/equicordplugins/timezones/database.tsx +++ b/src/equicordplugins/timezones/database.tsx @@ -26,11 +26,11 @@ export async function setUserDatabaseTimezone(userId: string, timezone: string | await DataStore.set(DATASTORE_KEY, databaseTimezones); } -export async function getTimezone(userId: string): Promise { +export async function getTimezone(userId: string, force?: boolean): Promise { const now = Date.now(); const cached = databaseTimezones[userId]; - if (cached && now < cached.expires) return cached.value; + if (cached && now < cached.expires && !force) return cached.value; if (!pendingRequests[userId]) { pendingRequests[userId] = (async () => { diff --git a/src/equicordplugins/timezones/index.tsx b/src/equicordplugins/timezones/index.tsx index 6597ea4a..7fca1b34 100644 --- a/src/equicordplugins/timezones/index.tsx +++ b/src/equicordplugins/timezones/index.tsx @@ -201,9 +201,7 @@ const userContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: { showToast("Refreshing timezone...", Toasts.Type.CLOCK); try { - const timezone = await getTimezone(user.id); - setUserDatabaseTimezone(user.id, timezone); - timezones[user.id] = timezone; + timezones[user.id] = await getTimezone(user.id, true); showToast("Timezone refreshed successfully!", Toasts.Type.SUCCESS); } catch (error) { console.error("Failed to refresh timezone:", error); From ffbc286b4b0e2602067c774aa3a93c3313605da8 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 28 May 2025 14:30:17 -0400 Subject: [PATCH 24/54] :scream: --- src/equicordplugins/timezones/index.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/equicordplugins/timezones/index.tsx b/src/equicordplugins/timezones/index.tsx index 7fca1b34..12d58f72 100644 --- a/src/equicordplugins/timezones/index.tsx +++ b/src/equicordplugins/timezones/index.tsx @@ -201,8 +201,13 @@ const userContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: { showToast("Refreshing timezone...", Toasts.Type.CLOCK); try { - timezones[user.id] = await getTimezone(user.id, true); - showToast("Timezone refreshed successfully!", Toasts.Type.SUCCESS); + const timezone = await getTimezone(user.id, true); + + if (timezone) { + showToast("Timezone refreshed successfully!", Toasts.Type.SUCCESS); + } else { + showToast("Timezone reset successfully!", Toasts.Type.SUCCESS); + } } catch (error) { console.error("Failed to refresh timezone:", error); showToast("Failed to refresh timezone.", Toasts.Type.FAILURE); From 94dc90a7731078da08c3868cc5296861cafb8c6c Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 28 May 2025 15:19:52 -0400 Subject: [PATCH 25/54] MediaDownloader Stop Full Crash --- .../mediaDownloader.desktop/native.ts | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/equicordplugins/mediaDownloader.desktop/native.ts b/src/equicordplugins/mediaDownloader.desktop/native.ts index d35d3abd..896f8457 100644 --- a/src/equicordplugins/mediaDownloader.desktop/native.ts +++ b/src/equicordplugins/mediaDownloader.desktop/native.ts @@ -103,12 +103,21 @@ export async function stop(_: IpcMainInvokeEvent) { } async function metadata(options: DownloadOptions) { - stdout_global = ""; - const metadata = JSON.parse(await ytdlp(["-J", options.url, "--no-warnings"])); - if (metadata.is_live) throw "Live streams are not supported."; - stdout_global = ""; - return { videoTitle: `${metadata.title || "video"} (${metadata.id})` }; + try { + stdout_global = ""; + const output = await ytdlp(["-J", options.url, "--no-warnings"]); + const metadata = JSON.parse(output); + + if (metadata.is_live) throw new Error("Live streams are not supported."); + + stdout_global = ""; + return { videoTitle: `${metadata.title || "video"} (${metadata.id})` }; + + } catch (err) { + throw err; + } } + function genFormat({ videoTitle }: { videoTitle: string; }, { maxFileSize, format }: DownloadOptions) { const HAS_LIMIT = !!maxFileSize; const MAX_VIDEO_SIZE = HAS_LIMIT ? maxFileSize * 0.8 : 0; @@ -161,8 +170,11 @@ async function download({ format, videoTitle }: { format: string; videoTitle: st : [] : []; const customArgs = ytdlpArgs?.filter(Boolean) || []; - - await ytdlp([url, ...baseArgs, ...remuxArgs, ...customArgs]); + try { + await ytdlp([url, ...baseArgs, ...remuxArgs, ...customArgs]); + } catch (err) { + console.error("Error during yt-dlp execution:", err); + } const file = fs.readdirSync(getdir()).find(f => f.startsWith("download.")); if (!file) throw "No video file was found!"; return { file, videoTitle }; From 4e348fba177cbd94d90f9e20f38478630bc80040 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 29 May 2025 09:50:07 -0400 Subject: [PATCH 26/54] Add Back MoreCommands --- README.md | 3 +- src/equicordplugins/moreCommands/index.ts | 65 +++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/equicordplugins/moreCommands/index.ts diff --git a/README.md b/README.md index 63025af4..92ba6668 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
-181 additional plugins +182 additional plugins ### All Platforms @@ -109,6 +109,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch - MessageLoggerEnhanced by Aria - MessageTranslate by Samwich - ModalFade by Kyuuhachi +- MoreCommands by Arjix, echo, Samu - MoreKaomoji by JacobTm & voidbbg - MoreStickers by Leko & Arjix - MoreUserTags by Cyn, TheSun, RyanCaoDev, LordElias, AutumnVN, hen diff --git a/src/equicordplugins/moreCommands/index.ts b/src/equicordplugins/moreCommands/index.ts new file mode 100644 index 00000000..02f3c373 --- /dev/null +++ b/src/equicordplugins/moreCommands/index.ts @@ -0,0 +1,65 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2022 Vendicated, Samu and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +import { ApplicationCommandInputType, findOption, OptionalMessageOption, RequiredMessageOption, sendBotMessage } from "@api/Commands"; +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; + + +function mock(input: string): string { + let output = ""; + for (let i = 0; i < input.length; i++) { + output += i % 2 ? input[i].toUpperCase() : input[i].toLowerCase(); + } + return output; +} + +export default definePlugin({ + name: "MoreCommands", + description: "echo, lenny, mock", + authors: [Devs.Arjix, Devs.echo, Devs.Samu], + commands: [ + { + name: "echo", + description: "Sends a message as Clyde (locally)", + options: [OptionalMessageOption], + inputType: ApplicationCommandInputType.BOT, + execute: (opts, ctx) => { + const content = findOption(opts, "message", ""); + + sendBotMessage(ctx.channel.id, { content }); + }, + }, + { + name: "lenny", + description: "Sends a lenny face", + options: [OptionalMessageOption], + execute: opts => ({ + content: findOption(opts, "message", "") + " ( ͡° ͜ʖ ͡°)" + }), + }, + { + name: "mock", + description: "mOcK PeOpLe", + options: [RequiredMessageOption], + execute: opts => ({ + content: mock(findOption(opts, "message", "")) + }), + }, + ] +}); From 196d1f3b80c18c49dbc8ce60ab863b199447473b Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 29 May 2025 13:56:25 -0400 Subject: [PATCH 27/54] Fix SHC Patch --- src/plugins/showHiddenChannels/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index 3f4f524c..722b5c74 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -299,7 +299,7 @@ export default definePlugin({ { find: '"MessageManager"', replacement: { - match: /"Skipping fetch because channelId is a static route"\);return}(?=.+?getChannel\((\i)\))/, + match: /(?<=\(\i\)\)return;)(?=.+?getChannel\((\i)\))/, replace: (m, channelId) => `${m}if($self.isHiddenChannel({channelId:${channelId}}))return;` } }, From 2a4314efc9700cd1185a4a625c2e3d1eafa84f8b Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Thu, 29 May 2025 17:01:08 -0300 Subject: [PATCH 28/54] ShowHiddenChannels: Fix incorrectly fetching messages --- src/plugins/showHiddenChannels/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index cd1b19ba..5dad87c5 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -267,7 +267,7 @@ export default definePlugin({ { find: '"MessageManager"', replacement: { - match: /"Skipping fetch because channelId is a static route"\);return}(?=.+?getChannel\((\i)\))/, + match: /forceFetch:\i,isPreload:.+?}=\i;(?=.+?getChannel\((\i)\))/, replace: (m, channelId) => `${m}if($self.isHiddenChannel({channelId:${channelId}}))return;` } }, From eafbc0d15a89bae8af403d9aef6b10eddfa8e78a Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 29 May 2025 23:10:53 +0200 Subject: [PATCH 29/54] WhoReacted: fix crashing (app reloading) --- src/plugins/whoReacted/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/whoReacted/index.tsx b/src/plugins/whoReacted/index.tsx index aea57fef..f14de9f5 100644 --- a/src/plugins/whoReacted/index.tsx +++ b/src/plugins/whoReacted/index.tsx @@ -66,7 +66,7 @@ function fetchReactions(msg: Message, emoji: ReactionEmoji, type: number) { function getReactionsWithQueue(msg: Message, e: ReactionEmoji, type: number) { const key = `${msg.id}:${e.name}:${e.id ?? ""}:${type}`; - const cache = reactions[key] ??= { fetched: false, users: {} }; + const cache = reactions[key] ??= { fetched: false, users: new Map() }; if (!cache.fetched) { queue.unshift(() => fetchReactions(msg, e, type)); cache.fetched = true; @@ -159,7 +159,7 @@ export default definePlugin({ }, [message.id, forceUpdate]); const reactions = getReactionsWithQueue(message, emoji, type); - const users = Object.values(reactions).filter(Boolean) as User[]; + const users = [...reactions.values()].filter(Boolean); return (
; + users: Map; } interface RootObject { From 64a429cfbdb6a0a02f039f066eca554e7dc082b3 Mon Sep 17 00:00:00 2001 From: Creation's Date: Thu, 29 May 2025 19:30:17 -0400 Subject: [PATCH 30/54] update(timezones) (#273) * move to a list on load instead of each reqest, move refresh and set database to toolbox * add option to hide your own timezone from showing * fix auth cancel, add fist run toast, fix label, add default to sys timezone when showing if nothing at all is set * Update index.tsx --- .../timezones/TimezoneModal.tsx | 14 +- src/equicordplugins/timezones/database.tsx | 68 +++++----- src/equicordplugins/timezones/index.tsx | 124 +++++++++++------- 3 files changed, 110 insertions(+), 96 deletions(-) diff --git a/src/equicordplugins/timezones/TimezoneModal.tsx b/src/equicordplugins/timezones/TimezoneModal.tsx index 4b9f859c..bd90d378 100644 --- a/src/equicordplugins/timezones/TimezoneModal.tsx +++ b/src/equicordplugins/timezones/TimezoneModal.tsx @@ -24,19 +24,17 @@ export function SetTimezoneModal({ userId, modalProps, database }: { userId: str const [currentValue, setCurrentValue] = useState(timezones[userId] ?? null); useEffect(() => { - if (!database) return; - const localTimezone = timezones[userId]; const shouldUseDatabase = settings.store.useDatabase && (settings.store.preferDatabaseOverLocal || !localTimezone); - if (shouldUseDatabase) { - getTimezone(userId).then(e => setCurrentValue(e ?? localTimezone)); - } else { - setCurrentValue(localTimezone); - } - }, [userId, settings.store.useDatabase, settings.store.preferDatabaseOverLocal, database]); + const value = shouldUseDatabase + ? getTimezone(userId) ?? localTimezone + : localTimezone; + + setCurrentValue(value ?? Intl.DateTimeFormat().resolvedOptions().timeZone); + }, [userId, settings.store.useDatabase, settings.store.preferDatabaseOverLocal]); const options = useMemo(() => { return Intl.supportedValuesOf("timeZone").map(timezone => { diff --git a/src/equicordplugins/timezones/database.tsx b/src/equicordplugins/timezones/database.tsx index 643f760b..f382c917 100644 --- a/src/equicordplugins/timezones/database.tsx +++ b/src/equicordplugins/timezones/database.tsx @@ -4,55 +4,45 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { DataStore } from "@api/index"; -import { openModal } from "@utils/modal"; +import { openModal } from "@utils/index"; import { OAuth2AuthorizeModal, showToast, Toasts } from "@webpack/common"; -import { databaseTimezones } from "."; +const databaseTimezones: Record = {}; -export const DOMAIN = "https://timezone.creations.works"; -export const REDIRECT_URI = `${DOMAIN}/auth/discord/callback`; -export const CLIENT_ID = "1377021506810417173"; - -export const DATASTORE_KEY = "vencord-database-timezones"; - -const pendingRequests: Record> = {}; +const DOMAIN = "https://timezone.creations.works"; +const REDIRECT_URI = `${DOMAIN}/auth/discord/callback`; +const CLIENT_ID = "1377021506810417173"; export async function setUserDatabaseTimezone(userId: string, timezone: string | null) { - databaseTimezones[userId] = { - value: timezone, - expires: Date.now() + 60 * 60 * 1000 // 1 hour - }; - await DataStore.set(DATASTORE_KEY, databaseTimezones); + databaseTimezones[userId] = { value: timezone }; } -export async function getTimezone(userId: string, force?: boolean): Promise { - const now = Date.now(); +export function getTimezone(userId: string): string | null { + return databaseTimezones[userId]?.value ?? null; +} - const cached = databaseTimezones[userId]; - if (cached && now < cached.expires && !force) return cached.value; +export async function loadDatabaseTimezones(): Promise { + try { + const res = await fetch(`${DOMAIN}/list`, { + headers: { Accept: "application/json" } + }); - if (!pendingRequests[userId]) { - pendingRequests[userId] = (async () => { - const res = await fetch(`${DOMAIN}/get?id=${userId}`, { - headers: { Accept: "application/json" } - }); - - let value: string | null = null; - if (res.ok) { - const json = await res.json(); - if (json?.timezone && typeof json.timezone === "string") { - value = json.timezone; - } + if (res.ok) { + const json = await res.json(); + for (const id in json) { + databaseTimezones[id] = { + value: json[id]?.timezone ?? null + }; } - setUserDatabaseTimezone(userId, value); - delete pendingRequests[userId]; - return value; - })(); - } + return true; + } - return pendingRequests[userId]; + return false; + } catch (e) { + console.error("Failed to fetch timezones list:", e); + return false; + } } export async function setTimezone(timezone: string): Promise { @@ -92,6 +82,8 @@ export function authModal(callback?: () => void) { permissions={0n} cancelCompletesFlow={false} callback={async (res: any) => { + if (!res || !res.location) return; + try { const url = new URL(res.location); @@ -109,10 +101,10 @@ export function authModal(callback?: () => void) { showToast("Authorization successful!", Toasts.Type.SUCCESS); callback?.(); } catch (e) { + console.error("Error during authorization:", e); showToast("Unexpected error during authorization", Toasts.Type.FAILURE); } }} /> )); } - diff --git a/src/equicordplugins/timezones/index.tsx b/src/equicordplugins/timezones/index.tsx index 12d58f72..c16b432d 100644 --- a/src/equicordplugins/timezones/index.tsx +++ b/src/equicordplugins/timezones/index.tsx @@ -1,6 +1,6 @@ /* * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors + * Copyright (c) 2025 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -17,15 +17,9 @@ import { findByPropsLazy } from "@webpack"; import { Button, Menu, showToast, Toasts, Tooltip, useEffect, UserStore, useState } from "@webpack/common"; import { Message, User } from "discord-types/general"; -import { authModal, deleteTimezone, getTimezone, setUserDatabaseTimezone } from "./database"; +import { authModal, deleteTimezone, getTimezone, loadDatabaseTimezones, setUserDatabaseTimezone } from "./database"; import { SetTimezoneModal } from "./TimezoneModal"; -type CacheEntry = { - value: string | null; - expires: number; -}; - -export let databaseTimezones: Record = {}; export let timezones: Record = {}; export const DATASTORE_KEY = "vencord-timezones"; @@ -33,6 +27,12 @@ const classes = findByPropsLazy("timestamp", "compact", "contentOnly"); const locale = findByPropsLazy("getLocale"); export const settings = definePluginSettings({ + "Show Own Timezone": { + type: OptionType.BOOLEAN, + description: "Show your own timezone in profiles and message headers", + default: true + }, + "24h Time": { type: OptionType.BOOLEAN, description: "Show time in 24h format", @@ -70,8 +70,7 @@ export const settings = definePluginSettings({ @@ -94,6 +93,13 @@ export const settings = definePluginSettings({ Reset Database Timezones ) + }, + + askedTimezone: { + type: OptionType.BOOLEAN, + description: "Whether the user has been asked to set their timezone", + hidden: true, + default: false } }); @@ -112,6 +118,7 @@ interface Props { timestamp?: string; type: "message" | "profile"; } + const TimestampComponent = ErrorBoundary.wrap(({ userId, timestamp, type }: Props) => { const [currentTime, setCurrentTime] = useState(timestamp || Date.now()); const [timezone, setTimezone] = useState(null); @@ -123,7 +130,7 @@ const TimestampComponent = ErrorBoundary.wrap(({ userId, timestamp, type }: Prop (settings.store.preferDatabaseOverLocal || !localTimezone); if (shouldUseDatabase) { - getTimezone(userId).then(e => setTimezone(e ?? localTimezone)); + setTimezone(getTimezone(userId) ?? localTimezone); } else { setTimezone(localTimezone); } @@ -178,45 +185,18 @@ const TimestampComponent = ErrorBoundary.wrap(({ userId, timestamp, type }: Prop ); }, { noop: true }); - const userContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: { user: User; }) => { if (user?.id == null) return; const setTimezoneItem = ( openModal(modalProps => )} /> ); children.push(, setTimezoneItem); - - if (settings.store.useDatabase) { - const refreshTimezoneItem = ( - { - showToast("Refreshing timezone...", Toasts.Type.CLOCK); - - try { - const timezone = await getTimezone(user.id, true); - - if (timezone) { - showToast("Timezone refreshed successfully!", Toasts.Type.SUCCESS); - } else { - showToast("Timezone reset successfully!", Toasts.Type.SUCCESS); - } - } catch (error) { - console.error("Failed to refresh timezone:", error); - showToast("Failed to refresh timezone.", Toasts.Type.FAILURE); - } - }} - /> - ); - children.push(refreshTimezoneItem); - } }; export default definePlugin({ @@ -246,31 +226,75 @@ export default definePlugin({ } ], + toolboxActions: { + "Set Database Timezone": () => { + authModal(async () => { + openModal(modalProps => ); + }); + }, + "Refresh Database Timezones": async () => { + try { + const good = await loadDatabaseTimezones(); + + if (good) { + showToast("Timezones refreshed successfully!", Toasts.Type.SUCCESS); + } else { + showToast("Timezones Failed to refresh!", Toasts.Type.FAILURE); + } + } + catch (error) { + console.error("Failed to refresh timezone:", error); + showToast("Failed to refresh timezones.", Toasts.Type.FAILURE); + } + } + }, + async start() { - databaseTimezones = await DataStore.get>(DATASTORE_KEY) || {}; timezones = await DataStore.get>(DATASTORE_KEY) || {}; + + if (settings.store.useDatabase) { + await loadDatabaseTimezones(); + + if (!settings.store.askedTimezone) { + showToast( + "", + Toasts.Type.MESSAGE, + { + duration: 10000, + component: ( + + ), + position: Toasts.Position.BOTTOM + } + ); + settings.store.askedTimezone = true; + } + } }, settings, getTime, - renderProfileTimezone: (props?: { user?: User; }) => { if (!settings.store.showProfileTime || !props?.user?.id) return null; + if (props.user.id === UserStore.getCurrentUser().id && !settings.store["Show Own Timezone"]) return null; - return ; + return ; }, renderMessageTimezone: (props?: { message?: Message; }) => { if (!settings.store.showMessageHeaderTime || !props?.message) return null; + if (props.message.author.id === UserStore.getCurrentUser().id && !settings.store["Show Own Timezone"]) return null; - return ; + return ; } }); From f0fcaf734e7c11bd2fd02ff6efd95fcd7191d8ce Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 30 May 2025 11:49:32 -0300 Subject: [PATCH 31/54] ShowHiddenChannels: Fix erroring when avoiding fetching channel messages --- src/plugins/showHiddenChannels/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index 5dad87c5..aed6066d 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -494,10 +494,10 @@ export default definePlugin({ isHiddenChannel(channel: Channel & { channelId?: string; }, checkConnect = false) { try { - if (!channel) return false; + if (channel == null || Object.hasOwn(channel, "channelId") && channel.channelId == null) return false; - if (channel.channelId) channel = ChannelStore.getChannel(channel.channelId); - if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return false; + if (channel.channelId != null) channel = ChannelStore.getChannel(channel.channelId); + if (channel == null || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return false; return !PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel) || checkConnect && !PermissionStore.can(PermissionsBits.CONNECT, channel); } catch (e) { From db7447a93d02215fd6b55b4a2c984e061e300ad1 Mon Sep 17 00:00:00 2001 From: vMohammad <62218284+vMohammad24@users.noreply.github.com> Date: Fri, 30 May 2025 19:12:34 +0300 Subject: [PATCH 32/54] add folder icons by sadan (#274) * fixed and added custom folder icons by sadan * better regex * better regex * Fixes --------- Co-authored-by: thororen1234 <78185467+thororen1234@users.noreply.github.com> --- README.md | 3 +- .../customFolderIcons/components.tsx | 124 ++++++++++++++++++ .../customFolderIcons/index.tsx | 58 ++++++++ .../customFolderIcons/settings.tsx | 28 ++++ .../customFolderIcons/util.tsx | 31 +++++ 5 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 src/equicordplugins/customFolderIcons/components.tsx create mode 100644 src/equicordplugins/customFolderIcons/index.tsx create mode 100644 src/equicordplugins/customFolderIcons/settings.tsx create mode 100644 src/equicordplugins/customFolderIcons/util.tsx diff --git a/README.md b/README.md index 92ba6668..3bfac801 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
-182 additional plugins +183 additional plugins ### All Platforms @@ -41,6 +41,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch - CommandPalette by Ethan - CopyStickerLinks by Byeoon - CopyUserMention by Cortex & castdrian +- CustomFolderIcons by sadan - CustomSounds by TheKodeToad & SpikeHD - CustomTimestamps by Rini & nvhrr - CustomUserColors by mochienya diff --git a/src/equicordplugins/customFolderIcons/components.tsx b/src/equicordplugins/customFolderIcons/components.tsx new file mode 100644 index 00000000..b5feb52a --- /dev/null +++ b/src/equicordplugins/customFolderIcons/components.tsx @@ -0,0 +1,124 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 sadan + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { closeModal, ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal"; +import { Button, Menu, Slider, TextInput, useState } from "@webpack/common"; + +import { folderIconsData, settings } from "./settings"; +import { folderProp, int2rgba, setFolderData } from "./util"; + +export function ImageModal(folderProps: folderProp) { + const [data, setData] = useState(((settings.store.folderIcons ?? {}) as folderIconsData)[folderProps.folderId]?.url ?? ""); + const [size, setSize] = useState(100); + return ( + <> + { + setData(val); + }} + placeholder="https://example.com/image.png" + > + + + {data && <> +
Change the size of the folder icon
+ { + setSize(v); + }} + maxValue={200} + minValue={25} + // [25, 200] + markers={Array.apply(0, Array(176)).map((_, i) => i + 25)} + stickToMarkers={true} + keyboardStep={1} + renderMarker={() => null} /> + } + +
+ +
+ + ); +} +export function RenderPreview({ folderProps, url, size }: { folderProps: folderProp; url: string; size: number; }) { + if (!url) return null; + return ( +
+ +
+ ); +} + +export function makeContextItem(a: folderProp) { + return ( + { + openModalLazy(async () => { + return props => ( + + +
+ Set a New Icon. +
+
+ + + +
+ You might have to hover the folder after setting in order for it to refresh. +
+
+ ); + }, + { + modalKey: "custom-folder-icon" + }); + }} /> + ); +} diff --git a/src/equicordplugins/customFolderIcons/index.tsx b/src/equicordplugins/customFolderIcons/index.tsx new file mode 100644 index 00000000..5da72533 --- /dev/null +++ b/src/equicordplugins/customFolderIcons/index.tsx @@ -0,0 +1,58 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 sadan + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { EquicordDevs } from "@utils/constants"; +import definePlugin from "@utils/types"; + +import { makeContextItem } from "./components"; +import { folderIconsData, settings } from "./settings"; +import { folderProp, int2rgba } from "./util"; + +export default definePlugin({ + name: "CustomFolderIcons", + description: "Customize folder icons with any png", + authors: [EquicordDevs.sadan], + settings, + patches: [ + { + find: ".folderButtonInner", + replacement: { + match: /(\(0,\i\.jsx\)\(\i,\{folderNode:(\i),hovered:\i,sorting:\i\}\))/, + replace: "($self.shouldReplace({folderNode:$2})?$self.replace({folderNode:$2}):$1)" + } + }, + ], + contextMenus: { + "guild-context": (menuItems, props: folderProp) => { + if (!("folderId" in props)) return; + menuItems.push(makeContextItem(props)); + } + }, + shouldReplace(props: any): boolean { + return !!((settings.store.folderIcons as folderIconsData)?.[props.folderNode.id]?.url); + }, + replace(props: any) { + const folderSettings = (settings.store.folderIcons as folderIconsData); + if (folderSettings && folderSettings[props.folderNode.id]) { + const data = folderSettings[props.folderNode.id]; + return ( +
+ +
+ ); + } + } +}); diff --git a/src/equicordplugins/customFolderIcons/settings.tsx b/src/equicordplugins/customFolderIcons/settings.tsx new file mode 100644 index 00000000..83c98e76 --- /dev/null +++ b/src/equicordplugins/customFolderIcons/settings.tsx @@ -0,0 +1,28 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 sadan + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { definePluginSettings } from "@api/Settings"; +import { OptionType } from "@utils/types"; + +export interface folderIcon { + url: string, + size: number, +} +export type folderIconsData = Record; + +export const settings = definePluginSettings({ + solidIcon: { + type: OptionType.BOOLEAN, + default: false, + description: "Use a solid background on the background of the image" + }, + folderIcons: { + type: OptionType.COMPONENT, + hidden: true, + description: "folder icon settings", + component: () => <> + } +}); diff --git a/src/equicordplugins/customFolderIcons/util.tsx b/src/equicordplugins/customFolderIcons/util.tsx new file mode 100644 index 00000000..959b35fb --- /dev/null +++ b/src/equicordplugins/customFolderIcons/util.tsx @@ -0,0 +1,31 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 sadan + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { folderIcon, folderIconsData, settings } from "./settings"; + +export async function setFolderData(props: folderProp, newData: folderIcon) { + if (!settings.store.folderIcons) { + settings.store.folderIcons = {}; + } + const folderSettings = (settings.store.folderIcons as folderIconsData); + folderSettings[props.folderId] = newData; +} + +export interface folderProp { + folderId: string; + folderColor: number; +} + +/** + * @param rgbVal RGB value + * @param alpha alpha bewteen zero and 1 +*/ +export function int2rgba(rgbVal: number, alpha: number = 1) { + const b = rgbVal & 0xFF, + g = (rgbVal & 0xFF00) >>> 8, + r = (rgbVal & 0xFF0000) >>> 16; + return `rgba(${[r, g, b].join(",")},${alpha})`; +} From 98d90756393ccd5708b7ada18ebf67e7775d2f50 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 31 May 2025 11:56:29 -0400 Subject: [PATCH 33/54] Fix Double Plugins --- src/plugins/moreCommands/index.tsx | 332 ----------------------------- src/plugins/moreKaomoji/index.ts | 216 ------------------- src/plugins/moyai/index.ts | 177 --------------- 3 files changed, 725 deletions(-) delete mode 100644 src/plugins/moreCommands/index.tsx delete mode 100644 src/plugins/moreKaomoji/index.ts delete mode 100644 src/plugins/moyai/index.ts diff --git a/src/plugins/moreCommands/index.tsx b/src/plugins/moreCommands/index.tsx deleted file mode 100644 index c167fe2f..00000000 --- a/src/plugins/moreCommands/index.tsx +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated, Samu and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, OptionalMessageOption, RequiredMessageOption, sendBotMessage } from "@api/Commands"; -import { Devs } from "@utils/constants"; -import definePlugin from "@utils/types"; - - -function mock(input: string): string { - let output = ""; - for (let i = 0; i < input.length; i++) { - output += i % 2 ? input[i].toUpperCase() : input[i].toLowerCase(); - } - return output; -} - -export default definePlugin({ - name: "MoreCommands", - description: "Echo, Lenny, Mock, and More", - authors: [Devs.Arjix, Devs.echo, Devs.Samu], - commands: [ - { - name: "echo", - description: "Sends a message as Clyde (locally)", - options: [OptionalMessageOption], - inputType: ApplicationCommandInputType.BOT, - execute: (opts, ctx) => { - const content = findOption(opts, "message", ""); - - sendBotMessage(ctx.channel.id, { content }); - }, - }, - { - name: "lenny", - description: "Sends a lenny face", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " ( ͡° ͜ʖ ͡°)" - }), - }, - { - name: "mock", - description: "mOcK PeOpLe", - options: [RequiredMessageOption], - execute: opts => ({ - content: mock(findOption(opts, "message", "")) - }), - }, - { - name: "reverse", - description: "Reverses the input message", - options: [RequiredMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "").split("").reverse().join("") - }), - }, - { - name: "uppercase", - description: "Converts the message to uppercase", - options: [RequiredMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "").toUpperCase() - }), - }, - { - name: "lowercase", - description: "Converts the message to lowercase", - options: [RequiredMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "").toLowerCase() - }), - }, - { - name: "wordcount", - description: "Counts the number of words in a message", - options: [RequiredMessageOption], - inputType: ApplicationCommandInputType.BOT, - execute: (opts, ctx) => { - const message = findOption(opts, "message", ""); - const wordCount = message.trim().split(/\s+/).length; - sendBotMessage(ctx.channel.id, { - content: `The message contains ${wordCount} words.` - }); - }, - }, - { - name: "ping", - description: "Pings the bot to check if it's responding", - options: [], - inputType: ApplicationCommandInputType.BOT, - execute: (opts, ctx) => { - sendBotMessage(ctx.channel.id, { - content: "Pong!" - }); - }, - }, - { - name: "rolldice", - description: "Roll a die with the specified number of sides", - options: [RequiredMessageOption], - execute: opts => { - const sides = parseInt(findOption(opts, "message", "6")); - const roll = Math.floor(Math.random() * sides) + 1; - return { - content: `You rolled a ${roll}!` - }; - }, - }, - { - name: "flipcoin", - description: "Flips a coin and returns heads or tails", - options: [], - execute: (opts, ctx) => { - const flip = Math.random() < 0.5 ? "Heads" : "Tails"; - return { - content: `The coin landed on: ${flip}` - }; - }, - }, - { - name: "ask", - description: "Ask a yes/no question and get an answer", - options: [RequiredMessageOption], - execute: opts => { - const question = findOption(opts, "message", ""); - const responses = [ - "Yes", "No", "Maybe", "Ask again later", "Definitely not", "It is certain" - ]; - const response = responses[Math.floor(Math.random() * responses.length)]; - return { - content: `${question} - ${response}` - }; - }, - }, - { - name: "randomcat", - description: "Get a random cat picture", - options: [], - execute: (opts, ctx) => { - return (async () => { - try { - const response = await fetch("https://api.thecatapi.com/v1/images/search"); - if (!response.ok) throw new Error("Failed to fetch cat image"); - const data = await response.json(); - return { - content: data[0].url - }; - } catch (err) { - sendBotMessage(ctx.channel.id, { - content: "Sorry, couldn't fetch a cat picture right now 😿" - }); - } - })(); - }, - }, - { - name: "randomdog", - description: "Get a random ddog picture", - options: [], - execute: (opts, ctx) => { - return (async () => { - try { - const response = await fetch("https://api.thedogapi.com/v1/images/search"); - if (!response.ok) throw new Error("Failed to fetch dog image"); - const data = await response.json(); - return { - content: data[0].url - }; - } catch (err) { - sendBotMessage(ctx.channel.id, { - content: "Sorry, couldn't fetch a cat picture right now 🐶" - }); - } - })(); - }, - }, - { - name: "randomnumber", - description: "Generates a random number between two values", - options: [ - { - name: "min", - description: "Minimum value", - type: ApplicationCommandOptionType.INTEGER, - required: true - }, - { - name: "max", - description: "Maximum value", - type: ApplicationCommandOptionType.INTEGER, - required: true - } - ], - execute: opts => { - const min = parseInt(findOption(opts, "min", "0")); - const max = parseInt(findOption(opts, "max", "100")); - const number = Math.floor(Math.random() * (max - min + 1)) + min; - return { - content: `Random number between ${min} and ${max}: ${number}` - }; - } - }, - { - name: "countdown", - description: "Starts a countdown from a specified number", - options: [ - { - name: "number", - description: "Number to countdown from (max 10)", - type: ApplicationCommandOptionType.INTEGER, - required: true - } - ], - inputType: ApplicationCommandInputType.BOT, - execute: async (opts, ctx) => { - const number = Math.min(parseInt(findOption(opts, "number", "5")), 10); - if (isNaN(number) || number < 1) { - sendBotMessage(ctx.channel.id, { - content: "Please provide a valid number between 1 and 10!" - }); - return; - } - sendBotMessage(ctx.channel.id, { - content: `Starting countdown from ${number}...` - }); - for (let i = number; i >= 0; i--) { - await new Promise(resolve => setTimeout(resolve, 1000)); - sendBotMessage(ctx.channel.id, { - content: i === 0 ? "🎉 Go! 🎉" : `${i}...` - }); - } - }, - }, - { - name: "choose", - description: "Randomly chooses from provided options", - options: [ - { - name: "choices", - description: "Comma-separated list of choices", - type: ApplicationCommandOptionType.STRING, - required: true - } - ], - execute: opts => { - const choices = findOption(opts, "choices", "").split(",").map(c => c.trim()); - const choice = choices[Math.floor(Math.random() * choices.length)]; - return { - content: `I choose: ${choice}` - }; - } - }, - { - name: "systeminfo", - description: "Shows system information", - options: [], - execute: async (opts, ctx) => { - try { - const { userAgent, hardwareConcurrency, onLine, languages } = navigator; - const { width, height, colorDepth } = window.screen; - const { deviceMemory, connection }: { deviceMemory: any, connection: any; } = navigator as any; - const platform = userAgent.includes("Windows") ? "Windows" : - userAgent.includes("Mac") ? "MacOS" : - userAgent.includes("Linux") ? "Linux" : "Unknown"; - const isMobile = /Mobile|Android|iPhone/i.test(userAgent); - const deviceType = isMobile ? "Mobile" : "Desktop"; - const browserInfo = userAgent.match(/(?:chrome|firefox|safari|edge|opr)\/?\s*(\d+)/i)?.[0] || "Unknown"; - const networkInfo = connection ? `${connection.effectiveType || "Unknown"}` : "Unknown"; - const info = [ - `> **Platform**: ${platform}`, - `> **Device Type**: ${deviceType}`, - `> **Browser**: ${browserInfo}`, - `> **CPU Cores**: ${hardwareConcurrency || "N/A"}`, - `> **Memory**: ${deviceMemory ? `${deviceMemory}GB` : "N/A"}`, - `> **Screen**: ${width}x${height} (${colorDepth}bit)`, - `> **Languages**: ${languages?.join(", ")}`, - `> **Network**: ${networkInfo} (${onLine ? "Online" : "Offline"})` - ].join("\n"); - return { content: info }; - } catch (err) { - sendBotMessage(ctx.channel.id, { content: "Failed to fetch system information" }); - } - }, - }, - { - name: "getUptime", - description: "Returns the system uptime", - execute: async (opts, ctx) => { - const uptime = performance.now() / 1000; - const uptimeInfo = `> **System Uptime**: ${Math.floor(uptime / 60)} minutes`; - return { content: uptimeInfo }; - }, - }, - { - name: "getTime", - description: "Returns the current server time", - execute: async (opts, ctx) => { - const currentTime = new Date().toLocaleString(); - return { content: `> **Current Time**: ${currentTime}` }; - }, - }, - { - name: "getLocation", - description: "Returns the user's approximate location based on IP", - execute: async (opts, ctx) => { - try { - const response = await fetch("https://ipapi.co/json/"); - const data = await response.json(); - const locationInfo = `> **Country**: ${data.country_name}\n> **Region**: ${data.region}\n> **City**: ${data.city}`; - return { content: locationInfo }; - } catch (err) { - sendBotMessage(ctx.channel.id, { content: "Failed to fetch location information" }); - } - }, - } - ] -}); diff --git a/src/plugins/moreKaomoji/index.ts b/src/plugins/moreKaomoji/index.ts deleted file mode 100644 index 986b1f33..00000000 --- a/src/plugins/moreKaomoji/index.ts +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { findOption, OptionalMessageOption } from "@api/Commands"; -import { Devs, EquicordDevs } from "@utils/constants"; -import definePlugin from "@utils/types"; - -export default definePlugin({ - name: "MoreKaomoji", - description: "Adds more Kaomoji to discord. ヽ(´▽`)/", - authors: [Devs.JacobTm, EquicordDevs.voidbbg], - commands: [ - { - name: "dissatisfaction", - description: " >﹏<", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " " + " >﹏<", - }), - }, - { - name: "smug", - description: "ಠ_ಠ", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " " + "ಠ_ಠ", - }), - }, - { - name: "happy", - description: "ヽ(´▽`)/", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " " + "ヽ(´▽`)/", - }), - }, - { - name: "crying", - description: "ಥ_ಥ", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " " + "ಥ_ಥ", - }), - }, - { - name: "angry", - description: "ヽ(`Д´)ノ", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " " + "ヽ(`Д´)ノ", - }), - }, - { - name: "anger", - description: "ヽ(o`皿′o)ノ", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " " + "ヽ(o`皿′o)ノ", - }), - }, - { - name: "joy", - description: "<( ̄︶ ̄)>", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " " + "<( ̄︶ ̄)>", - }), - }, - { - name: "blush", - description: "૮ ˶ᵔ ᵕ ᵔ˶ ა", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " " + "૮ ˶ᵔ ᵕ ᵔ˶ ა", - }), - }, - { - name: "confused", - description: "(•ิ_•ิ)?", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " " + "(•ิ_•ิ)?", - }), - }, - { - name: "sleeping", - description: "(ᴗ_ᴗ)", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " " + "(ᴗ_ᴗ)", - }), - }, - { - name: "laughing", - description: "o(≧▽≦)o", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " " + "o(≧▽≦)o", - }), - }, - /* - even more kaomoji - */ - { - name: "giving", - description: "(ノ◕ヮ◕)ノ*:・゚✧", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " " + "(ノ◕ヮ◕)ノ*:・゚✧", - }), - }, - { - name: "peace", - description: "✌(◕‿-)✌", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " " + "✌(◕‿-)✌", - }), - }, - { - name: "ending1", - description: "Ꮺ ָ࣪ ۰ ͙⊹", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " " + "Ꮺ ָ࣪ ۰ ͙⊹", - }), - }, - { - name: "uwu", - description: "(>⩊<)", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " " + "(>⩊<)", - }), - }, - { - name: "comfy", - description: "(─‿‿─)♡", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " " + "(─‿‿─)♡", - }), - }, - { - name: "lovehappy", - description: "(*≧ω≦*)", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " " + "(*≧ω≦*)", - }), - }, - { - name: "loveee", - description: "(⁄ ⁄>⁄ ▽ ⁄<⁄ ⁄)", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " " + "(⁄ ⁄>⁄ ▽ ⁄<⁄ ⁄)", - }), - }, - { - name: "give", - description: "(ノ= ⩊ = )ノ", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " " + "(ノ= ⩊ = )ノ", - }), - }, - { - name: "lovegive", - description: "ღゝ◡╹)ノ♡", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " " + "ღゝ◡╹)ノ♡", - }), - }, - { - name: "music", - description: "( ̄▽ ̄)/♫•¨•.¸¸♪", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " " + "( ̄▽ ̄)/♫•¨•.¸¸♪", - }), - }, - { - name: "stars", - description: ".𖥔 ݁ ˖๋ ࣭ ⭑", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " " + ".𖥔 ݁ ˖๋ ࣭ ⭑", - }), - }, - { - name: "lovegiving", - description: "⸜(。˃ ᵕ ˂ )⸝♡", - options: [OptionalMessageOption], - execute: opts => ({ - content: findOption(opts, "message", "") + " " + "⸜(。˃ ᵕ ˂ )⸝♡", - }), - } - ] -}); diff --git a/src/plugins/moyai/index.ts b/src/plugins/moyai/index.ts deleted file mode 100644 index 9d095c47..00000000 --- a/src/plugins/moyai/index.ts +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { definePluginSettings } from "@api/Settings"; -import { makeRange } from "@components/PluginSettings/components/SettingSliderComponent"; -import { Devs } from "@utils/constants"; -import { sleep } from "@utils/misc"; -import definePlugin, { OptionType } from "@utils/types"; -import { RelationshipStore, SelectedChannelStore, UserStore } from "@webpack/common"; -import { Message, ReactionEmoji } from "discord-types/general"; - -interface IMessageCreate { - type: "MESSAGE_CREATE"; - optimistic: boolean; - isPushNotification: boolean; - channelId: string; - message: Message; -} - -interface IReactionAdd { - type: "MESSAGE_REACTION_ADD"; - optimistic: boolean; - channelId: string; - messageId: string; - messageAuthorId: string; - userId: "195136840355807232"; - emoji: ReactionEmoji; -} - -interface IVoiceChannelEffectSendEvent { - type: string; - emoji?: ReactionEmoji; // Just in case... - channelId: string; - userId: string; - animationType: number; - animationId: number; -} - -const MOYAI = "🗿"; -const MOYAI_URL = - "https://github.com/Equicord/Equibored/raw/main/sounds/moyai/moyai.mp3"; -const MOYAI_URL_HD = - "https://github.com/Equicord/Equibored/raw/main/sounds/moyai/moyai.wav"; - -const settings = definePluginSettings({ - volume: { - description: "Volume of the 🗿🗿🗿", - type: OptionType.SLIDER, - markers: makeRange(0, 1, 0.1), - default: 0.5, - stickToMarkers: false - }, - quality: { - description: "Quality of the 🗿🗿🗿", - type: OptionType.SELECT, - options: [ - { label: "Normal", value: "Normal", default: true }, - { label: "HD", value: "HD" } - ], - }, - triggerWhenUnfocused: { - description: "Trigger the 🗿 even when the window is unfocused", - type: OptionType.BOOLEAN, - default: true - }, - ignoreBots: { - description: "Ignore bots", - type: OptionType.BOOLEAN, - default: true - }, - ignoreBlocked: { - description: "Ignore blocked users", - type: OptionType.BOOLEAN, - default: true - } -}); - -export default definePlugin({ - name: "Moyai", - authors: [Devs.Megu, Devs.Nuckyz], - description: "🗿🗿🗿🗿🗿🗿🗿🗿", - settings, - - flux: { - async MESSAGE_CREATE({ optimistic, type, message, channelId }: IMessageCreate) { - if (optimistic || type !== "MESSAGE_CREATE") return; - if (message.state === "SENDING") return; - if (settings.store.ignoreBots && message.author?.bot) return; - if (settings.store.ignoreBlocked && RelationshipStore.isBlocked(message.author?.id)) return; - if (!message.content) return; - if (channelId !== SelectedChannelStore.getChannelId()) return; - - const moyaiCount = getMoyaiCount(message.content); - - for (let i = 0; i < moyaiCount; i++) { - boom(); - await sleep(300); - } - }, - - MESSAGE_REACTION_ADD({ optimistic, type, channelId, userId, messageAuthorId, emoji }: IReactionAdd) { - if (optimistic || type !== "MESSAGE_REACTION_ADD") return; - if (settings.store.ignoreBots && UserStore.getUser(userId)?.bot) return; - if (settings.store.ignoreBlocked && RelationshipStore.isBlocked(messageAuthorId)) return; - if (channelId !== SelectedChannelStore.getChannelId()) return; - - const name = emoji.name.toLowerCase(); - if (name !== MOYAI && !name.includes("moyai") && !name.includes("moai")) return; - - boom(); - }, - - VOICE_CHANNEL_EFFECT_SEND({ emoji }: IVoiceChannelEffectSendEvent) { - if (!emoji?.name) return; - const name = emoji.name.toLowerCase(); - if (name !== MOYAI && !name.includes("moyai") && !name.includes("moai")) return; - - boom(); - } - } -}); - -function countOccurrences(sourceString: string, subString: string) { - let i = 0; - let lastIdx = 0; - while ((lastIdx = sourceString.indexOf(subString, lastIdx) + 1) !== 0) - i++; - - return i; -} - -function countMatches(sourceString: string, pattern: RegExp) { - if (!pattern.global) - throw new Error("pattern must be global"); - - let i = 0; - while (pattern.test(sourceString)) - i++; - - return i; -} - -const customMoyaiRe = //gi; - -function getMoyaiCount(message: string) { - const count = countOccurrences(message, MOYAI) - + countMatches(message, customMoyaiRe); - - return Math.min(count, 10); -} - -function boom() { - if (!settings.store.triggerWhenUnfocused && !document.hasFocus()) return; - const audioElement = document.createElement("audio"); - - audioElement.src = settings.store.quality === "HD" - ? MOYAI_URL_HD - : MOYAI_URL; - - audioElement.volume = settings.store.volume; - audioElement.play(); -} From dc24a32d6a23ccd639650b6489a13d9ac9e28d0a Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 31 May 2025 12:07:51 -0400 Subject: [PATCH 34/54] Fix GithubRepos Button Setting Context --- src/api/Settings.ts | 2 +- src/equicordplugins/githubRepos/index.tsx | 2 +- src/plugins/noBlockedMessages/index.ts | 2 +- src/plugins/platformIndicators/index.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/api/Settings.ts b/src/api/Settings.ts index e4dc21c5..adc15401 100644 --- a/src/api/Settings.ts +++ b/src/api/Settings.ts @@ -236,7 +236,7 @@ export function migratePluginSettings(name: string, ...oldNames: string[]) { } } -export function migratePluginSetting(pluginName: string, oldSetting: string, newSetting: string) { +export function migratePluginSetting(pluginName: string, newSetting: string, oldSetting: string) { const settings = SettingsStore.plain.plugins[pluginName]; if (!settings) return; diff --git a/src/equicordplugins/githubRepos/index.tsx b/src/equicordplugins/githubRepos/index.tsx index 65eccb64..9d8595c2 100644 --- a/src/equicordplugins/githubRepos/index.tsx +++ b/src/equicordplugins/githubRepos/index.tsx @@ -30,7 +30,7 @@ export const settings = definePluginSettings({ }, showInMiniProfile: { type: OptionType.BOOLEAN, - description: "Only show a button in the mini profile", + description: "Show full ui in the mini profile instead of just a button", default: true }, }); diff --git a/src/plugins/noBlockedMessages/index.ts b/src/plugins/noBlockedMessages/index.ts index 0d19726c..d4d7b3f9 100644 --- a/src/plugins/noBlockedMessages/index.ts +++ b/src/plugins/noBlockedMessages/index.ts @@ -33,7 +33,7 @@ interface MessageDeleteProps { } // Remove this migration once enough time has passed -migratePluginSetting("NoBlockedMessages", "ignoreBlockedMessages", "ignoreMessages"); +migratePluginSetting("NoBlockedMessages", "ignoreMessages", "ignoreBlockedMessages"); const settings = definePluginSettings({ ignoreMessages: { description: "Completely ignores incoming messages from blocked and ignored (if enabled) users", diff --git a/src/plugins/platformIndicators/index.tsx b/src/plugins/platformIndicators/index.tsx index 2ecae517..afe0ad44 100644 --- a/src/plugins/platformIndicators/index.tsx +++ b/src/plugins/platformIndicators/index.tsx @@ -201,7 +201,7 @@ function toggleMessageDecorators(enabled: boolean) { } } -migratePluginSetting("PlatformIndicators", "badges", "profiles"); +migratePluginSetting("PlatformIndicators", "profiles", "badges"); const settings = definePluginSettings({ list: { type: OptionType.BOOLEAN, From 7a0df3bcf0d47d55959b73eedeefd261dcbf42bb Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 31 May 2025 22:11:59 -0400 Subject: [PATCH 35/54] MessageFetchTimer --- README.md | 1 + .../messageFetchTimer/index.tsx | 170 ++++++++++++++++++ src/utils/constants.ts | 4 + 3 files changed, 175 insertions(+) create mode 100644 src/equicordplugins/messageFetchTimer/index.tsx diff --git a/README.md b/README.md index 3bfac801..f9f7103e 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch - Meow by Samwich - MessageBurst by port - MessageColors by Hen +- MessageFetchTimer by GroupXyz - MessageLinkTooltip by Kyuuhachi - MessageLoggerEnhanced by Aria - MessageTranslate by Samwich diff --git a/src/equicordplugins/messageFetchTimer/index.tsx b/src/equicordplugins/messageFetchTimer/index.tsx new file mode 100644 index 00000000..40a6a7bf --- /dev/null +++ b/src/equicordplugins/messageFetchTimer/index.tsx @@ -0,0 +1,170 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons"; +import { definePluginSettings } from "@api/Settings"; +import { EquicordDevs } from "@utils/constants"; +import { getCurrentChannel } from "@utils/discord"; +import definePlugin, { OptionType } from "@utils/types"; +import { FluxDispatcher, React } from "@webpack/common"; + +interface FetchTiming { + channelId: string; + startTime: number; + endTime?: number; + duration?: number; + timestamp?: Date; +} + +let currentFetch: FetchTiming | null = null; +let currentChannelId: string | null = null; +const channelTimings: Map = new Map(); + +const settings = definePluginSettings({ + showIcon: { + type: OptionType.BOOLEAN, + description: "Show fetch time icon in message bar", + default: true, + }, + showMs: { + type: OptionType.BOOLEAN, + description: "Show milliseconds in timing", + default: true, + }, + iconColor: { + type: OptionType.STRING, + description: "Icon color (CSS color value)", + default: "#00d166", + } +}); + +const FetchTimeButton: ChatBarButtonFactory = ({ isMainChat }) => { + const { showMs, iconColor } = settings.use(["showMs", "iconColor"]); + + if (!isMainChat || !settings.store.showIcon || !currentChannelId) { + return null; + } + + const channelData = channelTimings.get(currentChannelId); + if (!channelData) { + return null; + } + + const { time, timestamp } = channelData; + const displayTime = showMs ? `${Math.round(time)}ms` : `${Math.round(time / 1000)}s`; + + if (!showMs && Math.round(time / 1000) === 0) { + return null; + } + + const timeAgo = formatTimeAgo(timestamp); + + return ( + { }} + > +
+ + + + + {displayTime} + +
+
+ ); +}; + +function formatTimeAgo(timestamp: Date): string { + const now = new Date(); + const diff = now.getTime() - timestamp.getTime(); + const seconds = Math.floor(diff / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + + if (days > 0) { + return `${days} day${days > 1 ? "s" : ""} ago`; + } else if (hours > 0) { + return `${hours} hour${hours > 1 ? "s" : ""} ago`; + } else if (minutes > 0) { + return `${minutes} minute${minutes > 1 ? "s" : ""} ago`; + } else { + return "just now"; + } +} + +function handleChannelSelect(data: any) { + if (data.channelId && data.channelId !== currentChannelId) { + currentChannelId = data.channelId; + currentFetch = { + channelId: data.channelId, + startTime: performance.now() + }; + } +} + +function handleMessageLoad(data: any) { + if (!currentFetch || data.channelId !== currentFetch.channelId) return; + + const existing = channelTimings.get(currentFetch.channelId); + if (existing) return; + + const endTime = performance.now(); + const duration = endTime - currentFetch.startTime; + + channelTimings.set(currentFetch.channelId, { + time: duration, + timestamp: new Date() + }); + + currentFetch = null; +} + + +export default definePlugin({ + name: "MessageFetchTimer", + description: "Shows how long it took to fetch messages for the current channel", + authors: [EquicordDevs.GroupXyz], + settings, + + start() { + FluxDispatcher.subscribe("CHANNEL_SELECT", handleChannelSelect); + FluxDispatcher.subscribe("LOAD_MESSAGES_SUCCESS", handleMessageLoad); + FluxDispatcher.subscribe("MESSAGE_CREATE", handleMessageLoad); + + const currentChannel = getCurrentChannel(); + if (currentChannel) { + currentChannelId = currentChannel.id; + } + }, + + stop() { + FluxDispatcher.unsubscribe("CHANNEL_SELECT", handleChannelSelect); + FluxDispatcher.unsubscribe("LOAD_MESSAGES_SUCCESS", handleMessageLoad); + FluxDispatcher.unsubscribe("MESSAGE_CREATE", handleMessageLoad); + + currentFetch = null; + channelTimings.clear(); + currentChannelId = null; + }, + + renderChatBarButton: FetchTimeButton, +}); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index bf4dd6a9..27d163af 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1078,6 +1078,10 @@ export const EquicordDevs = Object.freeze({ name: "bbgaming25k", id: 851222385528274964n, }, + GroupXyz: { + name: "GroupXyz", + id: 950033410229944331n + }, } satisfies Record); // iife so #__PURE__ works correctly From 88c03c79bc289bdd0fb5744614344408974ba545 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sun, 1 Jun 2025 10:40:11 -0400 Subject: [PATCH 36/54] ShowConnections Open Xbox Connection --- src/plugins/showConnections/index.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/plugins/showConnections/index.tsx b/src/plugins/showConnections/index.tsx index f99c0be9..4a3dc028 100644 --- a/src/plugins/showConnections/index.tsx +++ b/src/plugins/showConnections/index.tsx @@ -151,7 +151,13 @@ function CompactConnectionComponent({ connection, theme }: { connection: Connect : From 70286e4867642be5cef5db458b66fceb2fd8670c Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Tue, 3 Jun 2025 20:25:33 -0400 Subject: [PATCH 37/54] InstantScreenshare: Mass Rewrite Settings --- .../instantScreenshare/index.tsx | 49 ++------- .../instantScreenshare/utils.tsx | 102 ++++++++++++++++++ 2 files changed, 112 insertions(+), 39 deletions(-) create mode 100644 src/equicordplugins/instantScreenshare/utils.tsx diff --git a/src/equicordplugins/instantScreenshare/index.tsx b/src/equicordplugins/instantScreenshare/index.tsx index 4a97276c..931d62ae 100644 --- a/src/equicordplugins/instantScreenshare/index.tsx +++ b/src/equicordplugins/instantScreenshare/index.tsx @@ -4,60 +4,31 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { definePluginSettings } from "@api/Settings"; import { Devs, EquicordDevs } from "@utils/constants"; -import definePlugin, { OptionType } from "@utils/types"; -import { findByCode, findByProps } from "@webpack"; +import definePlugin from "@utils/types"; +import { findByCodeLazy } from "@webpack"; import { ChannelStore, PermissionsBits, PermissionStore, SelectedChannelStore, UserStore } from "@webpack/common"; import { VoiceState } from "@webpack/types"; -const settings = definePluginSettings({ - streamType: { - description: "Stream screen or window", - type: OptionType.SELECT, - options: [ - { label: "Screen", value: "screen" }, - { label: "Window", value: "window" } - ], - default: "screen" - }, - streamWindowKeyword: { - description: "Keyword to search for in window title", - type: OptionType.STRING, - default: "", - placeholder: "Enter keyword" - } -}); +import { getCurrentMedia, settings } from "./utils"; + +const startStream = findByCodeLazy('type:"STREAM_START"'); let hasStreamed; -let sources; -let source; -async function startStream() { - const startStream = findByCode('type:"STREAM_START"'); - const mediaEngine = findByProps("getMediaEngine").getMediaEngine(); - const getDesktopSources = findByCode("desktop sources"); +async function autoStartStream() { const selected = SelectedChannelStore.getVoiceChannelId(); if (!selected) return; const channel = ChannelStore.getChannel(selected); if (channel.type === 13 || !PermissionStore.can(PermissionsBits.STREAM, channel)) return; - if (settings.store.streamType === "screen") { - sources = await getDesktopSources(mediaEngine, ["screen"], null); - source = sources[0]; - } else if (settings.store.streamType === "window") { - const keyword = settings.store.streamWindowKeyword?.toLowerCase(); - sources = await getDesktopSources(mediaEngine, ["window", "application"], null); - source = sources.find(s => s.name?.toLowerCase().includes(keyword)); - } - - if (!source) return; + const streamMedia = await getCurrentMedia(); startStream(channel.guild_id, selected, { "pid": null, - "sourceId": source.id, - "sourceName": source.name, + "sourceId": streamMedia.id, + "sourceName": streamMedia.name, "audioSourceId": null, "sound": true, "previewDisabled": false @@ -78,7 +49,7 @@ export default definePlugin({ if (channelId && !hasStreamed) { hasStreamed = true; - await startStream(); + await autoStartStream(); } if (!channelId) { diff --git a/src/equicordplugins/instantScreenshare/utils.tsx b/src/equicordplugins/instantScreenshare/utils.tsx new file mode 100644 index 00000000..cfb69580 --- /dev/null +++ b/src/equicordplugins/instantScreenshare/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 { definePluginSettings } from "@api/Settings"; +import { Logger } from "@utils/Logger"; +import { OptionType } from "@utils/types"; +import { findByCodeLazy, findByPropsLazy } from "@webpack"; +import { Forms, SearchableSelect, useEffect, useState } from "@webpack/common"; + +interface PickerProps { + streamMediaSelection: any[]; + streamMedia: any[]; +} + +const mediaEngine = findByPropsLazy("getMediaEngine"); +const getDesktopSources = findByCodeLazy("desktop sources"); + +export const settings = definePluginSettings({ + streamMedia: { + description: "Media source to stream resets to main screen if not found", + type: OptionType.COMPONENT, + component: SettingSection, + }, +}); + +export async function getCurrentMedia() { + const media = mediaEngine.getMediaEngine(); + const sources = [ + ...(await getDesktopSources(media, ["screen"], null) ?? []), + ...(await getDesktopSources(media, ["window", "application"], null) ?? []) + ]; + const streamMedia = sources.find(screen => screen.id === settings.store.streamMedia); + console.log(sources); + + if (streamMedia) return streamMedia; + + new Logger("InstantScreenShare").error(`Stream Media "${settings.store.streamMedia}" not found. Resetting to default.`); + + settings.store.streamMedia = sources[0]; + return sources[0]; +} + +function StreamSimplePicker({ streamMediaSelection, streamMedia }: PickerProps) { + const options = streamMediaSelection.map(screen => ({ + label: screen.name, + value: screen.id, + default: streamMediaSelection[0], + })); + + return ( + o.value === streamMedia)} + onChange={v => settings.store.streamMedia = v} + closeOnSelect + /> + ); +} + +function ScreenSetting() { + const { streamMedia } = settings.use(["streamMedia"]); + const media = mediaEngine.getMediaEngine(); + const [streamMediaSelection, setStreamMediaSelection] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + let active = true; + async function fetchMedia() { + setLoading(true); + const sources = [ + ...(await getDesktopSources(media, ["screen"], null) ?? []), + ...(await getDesktopSources(media, ["window", "application"], null) ?? []) + ]; + + if (active) { + setStreamMediaSelection(sources); + setLoading(false); + } + } + fetchMedia(); + return () => { active = false; }; + }, []); + + if (loading) return Loading media sources...; + if (!streamMediaSelection.length) return No Media found.; + + return ; +} + +function SettingSection() { + return ( + + Stream Media + + + ); +} From 608ffa0764005e54c52a500b274d7321d966fe0c Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Tue, 3 Jun 2025 20:27:56 -0400 Subject: [PATCH 38/54] InstantScreenshare: Fix Settings Warning --- src/equicordplugins/instantScreenshare/utils.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/equicordplugins/instantScreenshare/utils.tsx b/src/equicordplugins/instantScreenshare/utils.tsx index cfb69580..7f85ab4a 100644 --- a/src/equicordplugins/instantScreenshare/utils.tsx +++ b/src/equicordplugins/instantScreenshare/utils.tsx @@ -20,7 +20,6 @@ const getDesktopSources = findByCodeLazy("desktop sources"); export const settings = definePluginSettings({ streamMedia: { - description: "Media source to stream resets to main screen if not found", type: OptionType.COMPONENT, component: SettingSection, }, @@ -52,7 +51,7 @@ function StreamSimplePicker({ streamMediaSelection, streamMedia }: PickerProps) return ( o.value === streamMedia)} @@ -95,7 +94,8 @@ function ScreenSetting() { function SettingSection() { return ( - Stream Media + Media source to stream + Resets to main screen if not found ); From c4e3b0b116cebaa3734e6b5784c3d173e3ef9f74 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 4 Jun 2025 10:54:20 -0400 Subject: [PATCH 39/54] Fix Patches --- src/equicordplugins/fixFileExtensions/index.tsx | 6 +++--- src/plugins/anonymiseFileNames/index.tsx | 6 +++--- src/plugins/webContextMenus.web/index.ts | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/equicordplugins/fixFileExtensions/index.tsx b/src/equicordplugins/fixFileExtensions/index.tsx index 9e3fd34d..01024396 100644 --- a/src/equicordplugins/fixFileExtensions/index.tsx +++ b/src/equicordplugins/fixFileExtensions/index.tsx @@ -43,10 +43,10 @@ export default definePlugin({ }, // Also taken from AnonymiseFileNames { - find: 'addFilesTo:"message.attachments"', + find: "FirstThreadMessage,options", replacement: { - match: /(\i.uploadFiles\((\i),)/, - replace: "$2.forEach(f=>f.filename=$self.fixExt(f)),$1", + match: /\i.uploadFilesSimple\((\i)\);/, + replace: "$&$1.forEach($self.fixExt);", }, predicate: () => !Settings.plugins.AnonymiseFileNames.enabled, } diff --git a/src/plugins/anonymiseFileNames/index.tsx b/src/plugins/anonymiseFileNames/index.tsx index 84b42518..9d0f1145 100644 --- a/src/plugins/anonymiseFileNames/index.tsx +++ b/src/plugins/anonymiseFileNames/index.tsx @@ -86,10 +86,10 @@ export default definePlugin({ }, }, { - find: 'addFilesTo:"message.attachments"', + find: "FirstThreadMessage,options", replacement: { - match: /\i.uploadFiles\((\i),/, - replace: "$1.forEach($self.anonymise),$&" + match: /\i.uploadFilesSimple\((\i)\);/, + replace: "$&$1.forEach($self.anonymise);" } }, { diff --git a/src/plugins/webContextMenus.web/index.ts b/src/plugins/webContextMenus.web/index.ts index aee65252..41a5552c 100644 --- a/src/plugins/webContextMenus.web/index.ts +++ b/src/plugins/webContextMenus.web/index.ts @@ -133,7 +133,7 @@ export default definePlugin({ { find: "Copy image not supported", replacement: { - match: /(?<=(?:canSaveImage|canCopyImage)\(\i?\)\{.{0,50})!\i\.isPlatformEmbedded/g, + match: /(?<=(?:canSaveImage|canCopyImage)\((\i,\i)?\)\{.{0,150})!\i\.isPlatformEmbedded/g, replace: "false" } }, @@ -147,7 +147,7 @@ export default definePlugin({ replace: "false" }, { - match: /return\s*?\[.{0,50}?(?=\?.{0,100}?id:"copy-image")/, + match: /return\s*?\[.{0,50}?(?=\?.{0,25}?id:"copy-image")/, replace: "return [true" }, { From db7e0060c531d46bfb32f52ea3c20ef56a8c3377 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 4 Jun 2025 15:27:46 -0400 Subject: [PATCH 40/54] Remove CanCopyImage From Match --- src/plugins/webContextMenus.web/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/webContextMenus.web/index.ts b/src/plugins/webContextMenus.web/index.ts index 41a5552c..568961c2 100644 --- a/src/plugins/webContextMenus.web/index.ts +++ b/src/plugins/webContextMenus.web/index.ts @@ -133,7 +133,7 @@ export default definePlugin({ { find: "Copy image not supported", replacement: { - match: /(?<=(?:canSaveImage|canCopyImage)\((\i,\i)?\)\{.{0,150})!\i\.isPlatformEmbedded/g, + match: /(?<=canSaveImage\((\i,\i)?\)\{.{0,150})!\i\.isPlatformEmbedded/g, replace: "false" } }, @@ -147,7 +147,7 @@ export default definePlugin({ replace: "false" }, { - match: /return\s*?\[.{0,50}?(?=\?.{0,25}?id:"copy-image")/, + match: /return\s*?\[.{0,50}?\)(?=\?.{0,100}?id:"copy-image")/, replace: "return [true" }, { From 95451ff80ac1e6c838f4841520d0ab8dcbef2bf3 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 4 Jun 2025 15:49:08 -0400 Subject: [PATCH 41/54] Update BetterActivities --- .../betterActivities/patch-helpers/activityList.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/equicordplugins/betterActivities/patch-helpers/activityList.tsx b/src/equicordplugins/betterActivities/patch-helpers/activityList.tsx index 9e4b7893..16a1bc46 100644 --- a/src/equicordplugins/betterActivities/patch-helpers/activityList.tsx +++ b/src/equicordplugins/betterActivities/patch-helpers/activityList.tsx @@ -16,8 +16,8 @@ import { settings } from "../settings"; import { ActivityListIcon, ActivityListProps, ApplicationIcon, IconCSSProperties } from "../types"; import { cl, getApplicationIcons } from "../utils"; -// if discord one day decides to change their icon this needs to be updated -const DefaultActivityIcon = findComponentByCodeLazy("M6,7 L2,7 L2,6 L6,6 L6,7 Z M8,5 L2,5 L2,4 L8,4 L8,5 Z M8,3 L2,3 L2,2 L8,2 L8,3 Z M8.88888889,0 L1.11111111,0 C0.494444444,0 0,0.494444444 0,1.11111111 L0,8.88888889 C0,9.50253861 0.497461389,10 1.11111111,10 L8.88888889,10 C9.50253861,10 10,9.50253861 10,8.88888889 L10,1.11111111 C10,0.494444444 9.5,0 8.88888889,0 Z"); +// Discord no longer shows an icon here by default but we use the one from the popout now here +const DefaultActivityIcon = findComponentByCodeLazy("M5 2a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h14a3 3 0 0 0 3-3V5a3 3 0 0 0-3-3H5Zm6.81 7c-.54 0-1 .26-1.23.61A1 1 0 0 1 8.92 8.5 3.49 3.49 0 0 1 11.82 7c1.81 0 3.43 1.38 3.43 3.25 0 1.45-.98 2.61-2.27 3.06a1 1 0 0 1-1.96.37l-.19-1a1 1 0 0 1 .98-1.18c.87 0 1.44-.63 1.44-1.25S12.68 9 11.81 9ZM13 16a1 1 0 1 1-2 0 1 1 0 0 1 2 0Zm7-10.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0ZM18.5 20a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM7 18.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0ZM5.5 7a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"); export function patchActivityList({ activities, user, hideTooltip }: ActivityListProps): JSX.Element | null { const icons: ActivityListIcon[] = []; @@ -86,7 +86,7 @@ export function patchActivityList({ activities, user, hideTooltip }: ActivityLis // We need to filter out custom statuses const shouldShow = activities.filter(a => a.type !== 4).length !== icons.length; if (shouldShow) { - return ; + return ; } } From 0e90bda3c73dd5a568c4e14b8e922b0657ad43ba Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 4 Jun 2025 20:53:43 -0300 Subject: [PATCH 42/54] Fix AnonymiseFileNames Also fix issues with User Profile Modal v2 MutualGroupDMs patches --- src/plugins/anonymiseFileNames/index.tsx | 23 +++++++++++------------ src/plugins/mutualGroupDMs/index.tsx | 9 ++------- src/plugins/mutualGroupDMs/style.css | 2 +- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/plugins/anonymiseFileNames/index.tsx b/src/plugins/anonymiseFileNames/index.tsx index 34b5a5fa..d24de222 100644 --- a/src/plugins/anonymiseFileNames/index.tsx +++ b/src/plugins/anonymiseFileNames/index.tsx @@ -72,18 +72,17 @@ export default definePlugin({ patches: [ { - find: 'type:"UPLOAD_START"', - replacement: { - match: /await \i\.uploadFiles\((\i),/, - replace: "$1.forEach($self.anonymise),$&" - }, - }, - { - find: 'addFilesTo:"message.attachments"', - replacement: { - match: /\i.uploadFiles\((\i),/, - replace: "$1.forEach($self.anonymise),$&" - } + find: "async uploadFiles(", + replacement: [ + { + match: /async uploadFiles\((\i),\i\){/, + replace: "$&$1.forEach($self.anonymise);" + }, + { + match: /async uploadFilesSimple\((\i)\){/, + replace: "$&$1.forEach($self.anonymise);" + } + ], }, { find: "#{intl::ATTACHMENT_UTILITIES_SPOILER}", diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index e46824b4..1058410f 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -121,14 +121,9 @@ export default definePlugin({ }, // Make the gap between each item smaller so our tab can fit. { - match: /className:\i\.tabBar/, - replace: '$& + " vc-mutual-gdms-modal-v2-tab-bar"' + match: /type:"top",/, + replace: '$&className:"vc-mutual-gdms-modal-v2-tab-bar",' }, - // Make the tab bar item text smaller so our tab can fit. - { - match: /(\.tabBarItem.+?variant:)"heading-md\/normal"/, - replace: '$1"heading-sm/normal"' - } ] }, { diff --git a/src/plugins/mutualGroupDMs/style.css b/src/plugins/mutualGroupDMs/style.css index f0ad3c60..b6d992ad 100644 --- a/src/plugins/mutualGroupDMs/style.css +++ b/src/plugins/mutualGroupDMs/style.css @@ -3,5 +3,5 @@ } .vc-mutual-gdms-modal-v2-tab-bar { - gap: 12px; + --space-xl: 16px; } From db0bcf7da39102c3502eb9a206eab55a2fbffa02 Mon Sep 17 00:00:00 2001 From: Elvyra <88881326+EepyElvyra@users.noreply.github.com> Date: Thu, 5 Jun 2025 02:18:20 +0200 Subject: [PATCH 43/54] WebContextMenus: Fix copying and saving images (#3459) --- src/plugins/webContextMenus.web/index.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/plugins/webContextMenus.web/index.ts b/src/plugins/webContextMenus.web/index.ts index 45e6fa00..666e51be 100644 --- a/src/plugins/webContextMenus.web/index.ts +++ b/src/plugins/webContextMenus.web/index.ts @@ -122,10 +122,16 @@ export default definePlugin({ { find: "Copy image not supported", - replacement: { - match: /(?<=(?:canSaveImage|canCopyImage)\(\i?\)\{.{0,50})!\i\.isPlatformEmbedded/g, - replace: "false" - } + replacement: [ + { + match: /(?<=(?:canSaveImage|canCopyImage)\(.{0,120}?)!\i\.isPlatformEmbedded/g, + replace: "false" + }, + { + match: /canCopyImage\(.+?(?=return"function"==typeof \i\.clipboard\.copyImage)/, + replace: "$&return true;" + } + ] }, // Add back Copy & Save Image { @@ -137,7 +143,7 @@ export default definePlugin({ replace: "false" }, { - match: /return\s*?\[.{0,50}?(?=\?.{0,100}?id:"copy-image")/, + match: /return\s*?\[.{0,50}?(?=\?\(0,\i\.jsxs?.{0,100}?id:"copy-image")/, replace: "return [true" }, { From 503c90c201e9aeca9db622bde8fa9366edfd2a0d Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 4 Jun 2025 19:24:04 -0500 Subject: [PATCH 44/54] ShowHiddenChannels: Fix showing lock icon for special channels (#3460) --- src/plugins/showHiddenChannels/index.tsx | 1 + src/utils/constants.ts | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index aed6066d..149d422b 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -498,6 +498,7 @@ export default definePlugin({ if (channel.channelId != null) channel = ChannelStore.getChannel(channel.channelId); if (channel == null || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return false; + if (["browse", "customize", "guide"].includes(channel.id)) return false; return !PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel) || checkConnect && !PermissionStore.can(PermissionsBits.CONNECT, channel); } catch (e) { diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 65c73bd8..7bcadb42 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -589,6 +589,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "samsam", id: 836452332387565589n, }, + Cootshk: { + name: "Cootshk", + id: 921605971577548820n + }, } satisfies Record); // iife so #__PURE__ works correctly From 5dd3d60cd73c3a642a2d0c7026bb64a9e3e881d9 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 4 Jun 2025 20:46:38 -0400 Subject: [PATCH 45/54] Fix Patch --- .../fixFileExtensions/index.tsx | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/equicordplugins/fixFileExtensions/index.tsx b/src/equicordplugins/fixFileExtensions/index.tsx index 01024396..1b98d138 100644 --- a/src/equicordplugins/fixFileExtensions/index.tsx +++ b/src/equicordplugins/fixFileExtensions/index.tsx @@ -34,22 +34,19 @@ export default definePlugin({ patches: [ // Taken from AnonymiseFileNames { - find: 'type:"UPLOAD_START"', - replacement: { - match: /await \i\.uploadFiles\((\i),/, - replace: "$1.forEach($self.fixExt),$&" - }, + find: "async uploadFiles(", + replacement: [ + { + match: /async uploadFiles\((\i),\i\){/, + replace: "$&$1.forEach($self.anonymise);" + }, + { + match: /async uploadFilesSimple\((\i)\){/, + replace: "$&$1.forEach($self.anonymise);" + } + ], predicate: () => !Settings.plugins.AnonymiseFileNames.enabled, }, - // Also taken from AnonymiseFileNames - { - find: "FirstThreadMessage,options", - replacement: { - match: /\i.uploadFilesSimple\((\i)\);/, - replace: "$&$1.forEach($self.fixExt);", - }, - predicate: () => !Settings.plugins.AnonymiseFileNames.enabled, - } ], fixExt(upload: ExtUpload) { const file = upload.filename; From 87d8bfb3741331bfb12fcbed0f12ce53f9ea7dc9 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 4 Jun 2025 22:09:30 -0400 Subject: [PATCH 46/54] Fix Crashing --- src/equicordplugins/fixFileExtensions/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/equicordplugins/fixFileExtensions/index.tsx b/src/equicordplugins/fixFileExtensions/index.tsx index 1b98d138..541de5ef 100644 --- a/src/equicordplugins/fixFileExtensions/index.tsx +++ b/src/equicordplugins/fixFileExtensions/index.tsx @@ -38,11 +38,11 @@ export default definePlugin({ replacement: [ { match: /async uploadFiles\((\i),\i\){/, - replace: "$&$1.forEach($self.anonymise);" + replace: "$&$1.forEach($self.fixExt);" }, { match: /async uploadFilesSimple\((\i)\){/, - replace: "$&$1.forEach($self.anonymise);" + replace: "$&$1.forEach($self.fixExt);" } ], predicate: () => !Settings.plugins.AnonymiseFileNames.enabled, From 973148d86d5deaddae6b3d584959eda8eac9a926 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 5 Jun 2025 13:45:28 -0400 Subject: [PATCH 47/54] Update & Fix QuestCompleter --- .../fixFileExtensions/index.tsx | 4 +-- .../questCompleter.discordDesktop/index.tsx | 32 +++++++++---------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/equicordplugins/fixFileExtensions/index.tsx b/src/equicordplugins/fixFileExtensions/index.tsx index 541de5ef..e4664a7e 100644 --- a/src/equicordplugins/fixFileExtensions/index.tsx +++ b/src/equicordplugins/fixFileExtensions/index.tsx @@ -24,8 +24,6 @@ export const reverseExtensionMap = Object.entries(extensionMap).reduce((acc, [ta return acc; }, {} as Record); -type ExtUpload = Upload & { fixExtension?: boolean; }; - export default definePlugin({ name: "FixFileExtensions", authors: [EquicordDevs.thororen], @@ -48,7 +46,7 @@ export default definePlugin({ predicate: () => !Settings.plugins.AnonymiseFileNames.enabled, }, ], - fixExt(upload: ExtUpload) { + fixExt(upload: Upload) { const file = upload.filename; const tarMatch = tarExtMatcher.exec(file); const extIdx = tarMatch?.index ?? file.lastIndexOf("."); diff --git a/src/equicordplugins/questCompleter.discordDesktop/index.tsx b/src/equicordplugins/questCompleter.discordDesktop/index.tsx index 19a13533..3580051a 100644 --- a/src/equicordplugins/questCompleter.discordDesktop/index.tsx +++ b/src/equicordplugins/questCompleter.discordDesktop/index.tsx @@ -51,26 +51,27 @@ async function openCompleteQuestUI() { const applicationId = quest.config.application.id; const applicationName = quest.config.application.name; const taskName = ["WATCH_VIDEO", "PLAY_ON_DESKTOP", "STREAM_ON_DESKTOP", "PLAY_ACTIVITY"].find(x => quest.config.taskConfig.tasks[x] != null); + const icon = `https://cdn.discordapp.com/quests/${quest.id}/${theme}/${quest.config.assets.gameTile}`; // @ts-ignore const secondsNeeded = quest.config.taskConfig.tasks[taskName].target; // @ts-ignore - const secondsDone = quest.userStatus?.progress?.[taskName]?.value ?? 0; - const icon = `https://cdn.discordapp.com/assets/quests/${quest.id}/${theme}/${quest.config.assets.gameTile}`; + let secondsDone = quest.userStatus?.progress?.[taskName]?.value ?? 0; if (taskName === "WATCH_VIDEO") { - const tolerance = 2, speed = 10; - const diff = Math.floor((Date.now() - new Date(quest.userStatus.enrolledAt).getTime()) / 1000); - const startingPoint = Math.min(Math.max(Math.ceil(secondsDone), diff), secondsNeeded); + const maxFuture = 10, speed = 7, interval = 1; + const enrolledAt = new Date(quest.userStatus.enrolledAt).getTime(); const fn = async () => { - for (let i = startingPoint; i <= secondsNeeded; i += speed) { - try { - await RestAPI.post({ url: `/quests/${quest.id}/video-progress`, body: { timestamp: Math.min(secondsNeeded, i + Math.random()) } }); - } catch (ex) { - console.log("Failed to send increment of", i, ex); + while (true) { + const maxAllowed = Math.floor((Date.now() - enrolledAt) / 1000) + maxFuture; + const diff = maxAllowed - secondsDone; + const timestamp = secondsDone + speed; + if (diff >= speed) { + await RestAPI.post({ url: `/quests/${quest.id}/video-progress`, body: { timestamp: Math.min(secondsNeeded, timestamp + Math.random()) } }); + secondsDone = Math.min(secondsNeeded, timestamp); } - await new Promise(resolve => setTimeout(resolve, tolerance * 1000)); - } - if ((secondsNeeded - secondsDone) % speed !== 0) { - await RestAPI.post({ url: `/quests/${quest.id}/video-progress`, body: { timestamp: secondsNeeded } }); + if (timestamp >= secondsNeeded) { + break; + } + await new Promise(resolve => setTimeout(resolve, interval * 1000)); showNotification({ title: `${applicationName} - Quest Completer`, body: "Quest Completed.", @@ -81,10 +82,9 @@ async function openCompleteQuestUI() { fn(); showNotification({ title: `${applicationName} - Quest Completer`, - body: `Wait for ${Math.ceil((secondsNeeded - startingPoint) / speed * tolerance)} more seconds.`, + body: `Spoofing video for ${applicationName}.`, icon: icon, }); - console.log(`Spoofing video for ${applicationName}.`); } else if (taskName === "PLAY_ON_DESKTOP") { RestAPI.get({ url: `/applications/public?application_ids=${applicationId}` }).then(res => { const appData = res.body[0]; From 8162a628a1d2fc6152abdbb8daac498190489d0d Mon Sep 17 00:00:00 2001 From: davidkra230 Date: Thu, 5 Jun 2025 13:53:35 -0400 Subject: [PATCH 48/54] create the StreamingCodecDisabler plugin (#277) * create StreamingCodecDisabler * fixes * fix typo for av1 * Fixes --------- Co-authored-by: thororen1234 <78185467+thororen1234@users.noreply.github.com> --- README.md | 3 +- .../streamingCodecDisabler/index.ts | 81 +++++++++++++++++++ src/utils/constants.ts | 4 + 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 src/equicordplugins/streamingCodecDisabler/index.ts diff --git a/README.md b/README.md index f9f7103e..330c7b5d 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
-183 additional plugins +184 additional plugins ### All Platforms @@ -160,6 +160,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch - StatusPresets by iamme - SteamStatusSync by niko - StickerBlocker by Samwich +- StreamingCodecDisabler by davidkra230 - TalkInReverse by Tolgchu - TeX by Kyuuhachi - TextToSpeech by Samwich diff --git a/src/equicordplugins/streamingCodecDisabler/index.ts b/src/equicordplugins/streamingCodecDisabler/index.ts new file mode 100644 index 00000000..47a38112 --- /dev/null +++ b/src/equicordplugins/streamingCodecDisabler/index.ts @@ -0,0 +1,81 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { definePluginSettings, Settings } from "@api/Settings"; +import { EquicordDevs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; +import { findStoreLazy } from "@webpack"; + +const mediaEngine = findStoreLazy("MediaEngineStore"); + +const originalCodecStatuses: { + AV1: boolean, + H265: boolean, + H264: boolean; +} = { + AV1: true, + H265: true, + H264: true +}; + +const settings = definePluginSettings({ + disableAv1Codec: { + description: "Make Discord not consider using AV1 for streaming.", + type: OptionType.BOOLEAN, + default: false + }, + disableH265Codec: { + description: "Make Discord not consider using H265 for streaming.", + type: OptionType.BOOLEAN, + default: false + }, + disableH264Codec: { + description: "Make Discord not consider using H264 for streaming.", + type: OptionType.BOOLEAN, + default: false + }, +}); + +export default definePlugin({ + name: "StreamingCodecDisabler", + description: "Disable codecs for streaming of your choice", + authors: [EquicordDevs.davidkra230], + settings, + + patches: [ + { + find: "setVideoBroadcast(this.shouldConnectionBroadcastVideo", + replacement: { + match: /setGoLiveSource\(.,.\)\{/, + replace: "$&$self.updateDisabledCodecs();" + }, + } + ], + + async updateDisabledCodecs() { + mediaEngine.setAv1Enabled(originalCodecStatuses.AV1 && !Settings.plugins.StreamingCodecDisabler.disableAv1Codec); + mediaEngine.setH265Enabled(originalCodecStatuses.H265 && !Settings.plugins.StreamingCodecDisabler.disableH265Codec); + mediaEngine.setH264Enabled(originalCodecStatuses.H264 && !Settings.plugins.StreamingCodecDisabler.disableH264Codec); + }, + + async start() { + const engine = mediaEngine.getMediaEngine(); + const options = Object.keys(originalCodecStatuses); + // [{"codec":"","decode":false,"encode":false}] + const CodecCapabilities = JSON.parse(await new Promise(res => engine.getCodecCapabilities(res))); + CodecCapabilities.forEach((codec: { codec: string; encode: boolean; }) => { + if (options.includes(codec.codec)) { + originalCodecStatuses[codec.codec] = codec.encode; + } + }); + }, + + async stop() { + mediaEngine.setAv1Enabled(originalCodecStatuses.AV1); + mediaEngine.setH265Enabled(originalCodecStatuses.H265); + mediaEngine.setH264Enabled(originalCodecStatuses.H264); + } +}); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 87efce46..0fcdf007 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1082,6 +1082,10 @@ export const EquicordDevs = Object.freeze({ name: "bbgaming25k", id: 851222385528274964n, }, + davidkra230: { + name: "davidkra230", + id: 652699312631054356n, + }, GroupXyz: { name: "GroupXyz", id: 950033410229944331n From 0ce7772500f97bba6c7a9954901401b8c3bb879a Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Thu, 5 Jun 2025 15:42:00 -0300 Subject: [PATCH 49/54] FakeNitro: Fix sending animated stickers --- src/webpack/common/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index 05eb5729..5c54c95a 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -138,7 +138,7 @@ export const UserUtils = { export const UploadManager = findByPropsLazy("clearAll", "addFile"); export const UploadHandler = { - promptToUpload: findByCodeLazy("#{intl::ATTACHMENT_TOO_MANY_ERROR_TITLE}") as (files: File[], channel: Channel, draftType: Number) => void + promptToUpload: findByCodeLazy("=!0,showLargeMessageDialog:") as (files: File[], channel: Channel, draftType: Number) => void }; export const ApplicationAssetUtils = mapMangledModuleLazy("getAssetImage: size must === [", { From 1933b1f1a49c88f6d8727e3a5aad49dd8b3f8973 Mon Sep 17 00:00:00 2001 From: thororen <78185467+thororen1234@users.noreply.github.com> Date: Thu, 5 Jun 2025 15:28:16 -0400 Subject: [PATCH 50/54] Fix PR --- src/equicordplugins/streamingCodecDisabler/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/equicordplugins/streamingCodecDisabler/index.ts b/src/equicordplugins/streamingCodecDisabler/index.ts index 47a38112..e5f4b0b7 100644 --- a/src/equicordplugins/streamingCodecDisabler/index.ts +++ b/src/equicordplugins/streamingCodecDisabler/index.ts @@ -9,7 +9,7 @@ import { EquicordDevs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { findStoreLazy } from "@webpack"; -const mediaEngine = findStoreLazy("MediaEngineStore"); +let mediaEngine = findStoreLazy("MediaEngineStore"); const originalCodecStatuses: { AV1: boolean, @@ -62,7 +62,7 @@ export default definePlugin({ }, async start() { - const engine = mediaEngine.getMediaEngine(); + mediaEngine = mediaEngine.getMediaEngine(); const options = Object.keys(originalCodecStatuses); // [{"codec":"","decode":false,"encode":false}] const CodecCapabilities = JSON.parse(await new Promise(res => engine.getCodecCapabilities(res))); From 582487a2ba01cb293ad193d8cba92dd469e25f82 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 5 Jun 2025 18:26:33 -0400 Subject: [PATCH 51/54] Fix Lint --- src/equicordplugins/streamingCodecDisabler/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equicordplugins/streamingCodecDisabler/index.ts b/src/equicordplugins/streamingCodecDisabler/index.ts index e5f4b0b7..f26113e9 100644 --- a/src/equicordplugins/streamingCodecDisabler/index.ts +++ b/src/equicordplugins/streamingCodecDisabler/index.ts @@ -65,7 +65,7 @@ export default definePlugin({ mediaEngine = mediaEngine.getMediaEngine(); const options = Object.keys(originalCodecStatuses); // [{"codec":"","decode":false,"encode":false}] - const CodecCapabilities = JSON.parse(await new Promise(res => engine.getCodecCapabilities(res))); + const CodecCapabilities = JSON.parse(await new Promise(res => mediaEngine.getCodecCapabilities(res))); CodecCapabilities.forEach((codec: { codec: string; encode: boolean; }) => { if (options.includes(codec.codec)) { originalCodecStatuses[codec.codec] = codec.encode; From e7076f5aeeae02ae7f1793408a1b2995b199ee2b Mon Sep 17 00:00:00 2001 From: Vending Machine Date: Fri, 6 Jun 2025 18:30:19 +0200 Subject: [PATCH 52/54] Use much stricter, whitelist based CSP (#3162) --- src/components/ErrorCard.css | 4 + src/components/Link.tsx | 3 + src/components/VencordSettings/ThemesTab.tsx | 31 +++++ src/main/csp.ts | 138 +++++++++++++++++++ src/main/index.ts | 68 +-------- src/plugins/_api/badges/index.tsx | 2 +- src/plugins/devCompanion.dev/index.tsx | 2 +- src/plugins/reverseImageSearch/index.tsx | 6 +- src/utils/constants.ts | 6 +- src/utils/cspViolations.ts | 34 +++++ src/utils/index.ts | 1 + 11 files changed, 221 insertions(+), 74 deletions(-) create mode 100644 src/main/csp.ts create mode 100644 src/utils/cspViolations.ts diff --git a/src/components/ErrorCard.css b/src/components/ErrorCard.css index 5146aa03..6401c59c 100644 --- a/src/components/ErrorCard.css +++ b/src/components/ErrorCard.css @@ -4,4 +4,8 @@ border: 1px solid #e78284; border-radius: 5px; color: var(--text-normal, white); + + & a:hover { + text-decoration: underline; + } } diff --git a/src/components/Link.tsx b/src/components/Link.tsx index 0f4eb07d..2eb7ab00 100644 --- a/src/components/Link.tsx +++ b/src/components/Link.tsx @@ -28,6 +28,9 @@ export function Link(props: React.PropsWithChildren) { props.style.pointerEvents = "none"; props["aria-disabled"] = true; } + + props.rel ??= "noreferrer"; + return ( {props.children} diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx index f718ab11..c507ef88 100644 --- a/src/components/VencordSettings/ThemesTab.tsx +++ b/src/components/VencordSettings/ThemesTab.tsx @@ -18,13 +18,16 @@ import { Settings, useSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; +import { ErrorCard } from "@components/ErrorCard"; import { Flex } from "@components/Flex"; import { DeleteIcon, FolderIcon, PaintbrushIcon, PencilIcon, PlusIcon, RestartIcon } from "@components/Icons"; import { Link } from "@components/Link"; import { openPluginModal } from "@components/PluginSettings/PluginModal"; import type { UserThemeHeader } from "@main/themes"; +import { useCspErrors } from "@utils/cspViolations"; import { openInviteModal } from "@utils/discord"; import { Margins } from "@utils/margins"; +import { classes } from "@utils/misc"; import { showItemInFolder } from "@utils/native"; import { useAwaiter } from "@utils/react"; import { findLazy } from "@webpack"; @@ -219,6 +222,12 @@ function ThemesTab() { If using the BD site, click on "Download" and place the downloaded .theme.css file into your themes folder. + + External Resources + For security reasons, loading resources (styles, fonts, images, ...) from most sites is blocked. + Make sure all your assets are hosted on GitHub, GitLab, Codeberg, Imgur, Discord or Google Fonts. + + <> @@ -347,10 +356,32 @@ function ThemesTab() { + {currentTab === ThemeTab.LOCAL && renderLocalThemes()} {currentTab === ThemeTab.ONLINE && renderOnlineThemes()} ); } +export function CspErrorCard() { + const errors = useCspErrors(); + + if (!errors.length) return null; + + return ( + + Blocked Resources + Some images, styles, or fonts were blocked because they come from disallowed domains. + Make sure that your themes and custom css only load resources from whitelisted websites, such as GitHub, Imgur and Google Fonts. + + Blocked URLs + + {errors.map(url => ( + {url} + ))} + + + ); +} + export default wrapTab(ThemesTab, "Themes"); diff --git a/src/main/csp.ts b/src/main/csp.ts new file mode 100644 index 00000000..b35a11a8 --- /dev/null +++ b/src/main/csp.ts @@ -0,0 +1,138 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { session } from "electron"; + +type PolicyMap = Record; + +export const ConnectSrc = ["connect-src"]; +export const MediaSrc = [...ConnectSrc, "img-src", "media-src"]; +export const CssSrc = ["style-src", "font-src"]; +export const MediaAndCssSrc = [...MediaSrc, ...CssSrc]; +export const MediaScriptsAndCssSrc = [...MediaAndCssSrc, "script-src", "worker-src"]; + +// Plugins can whitelist their own domains by importing this object in their native.ts +// script and just adding to it. But generally, you should just edit this file instead + +export const CspPolicies: PolicyMap = { + "*.github.io": MediaAndCssSrc, // GitHub pages, used by most themes + "raw.githubusercontent.com": MediaAndCssSrc, // GitHub raw, used by some themes + "*.gitlab.io": MediaAndCssSrc, // GitLab pages, used by some themes + "gitlab.com": MediaAndCssSrc, // GitLab raw, used by some themes + "*.codeberg.page": MediaAndCssSrc, // Codeberg pages, used by some themes + "codeberg.org": MediaAndCssSrc, // Codeberg raw, used by some themes + + "*.githack.com": MediaAndCssSrc, // githack (namely raw.githack.com), used by some themes + "jsdelivr.net": MediaAndCssSrc, // jsDelivr, used by very few themes + + "fonts.googleapis.com": CssSrc, // Google Fonts, used by many themes + + "i.imgur.com": MediaSrc, // Imgur, used by some themes + "i.ibb.co": MediaSrc, // ImgBB, used by some themes + + "cdn.discordapp.com": MediaAndCssSrc, // Discord CDN, used by Vencord and some themes to load media + "media.discordapp.net": MediaSrc, // Discord media CDN, possible alternative to Discord CDN + + // CDNs used for some things by Vencord. + // FIXME: we really should not be using CDNs anymore + "cdnjs.cloudflare.com": MediaScriptsAndCssSrc, + "cdn.jsdelivr.net": MediaScriptsAndCssSrc, + + // Function Specific + "api.github.com": ConnectSrc, // used for updating Vencord itself + "ws.audioscrobbler.com": ConnectSrc, // Last.fm API + "translate-pa.googleapis.com": ConnectSrc, // Google Translate API + "*.vencord.dev": MediaSrc, // VenCloud (api.vencord.dev) and Badges (badges.vencord.dev) + "manti.vendicated.dev": MediaSrc, // ReviewDB API + "decor.fieryflames.dev": ConnectSrc, // Decor API + "ugc.decor.fieryflames.dev": MediaSrc, // Decor CDN + "sponsor.ajay.app": ConnectSrc, // Dearrow API + "dearrow-thumb.ajay.app": MediaSrc, // Dearrow Thumbnail CDN + "usrbg.is-hardly.online": MediaSrc, // USRBG API + "icons.duckduckgo.com": MediaSrc, // DuckDuckGo Favicon API (Reverse Image Search) +}; + +const findHeader = (headers: PolicyMap, headerName: Lowercase) => { + return Object.keys(headers).find(h => h.toLowerCase() === headerName); +}; + +const parsePolicy = (policy: string): PolicyMap => { + const result: PolicyMap = {}; + policy.split(";").forEach(directive => { + const [directiveKey, ...directiveValue] = directive.trim().split(/\s+/g); + if (directiveKey && !Object.prototype.hasOwnProperty.call(result, directiveKey)) { + result[directiveKey] = directiveValue; + } + }); + + return result; +}; + +const stringifyPolicy = (policy: PolicyMap): string => + Object.entries(policy) + .filter(([, values]) => values?.length) + .map(directive => directive.flat().join(" ")) + .join("; "); + + +const patchCsp = (headers: PolicyMap) => { + const reportOnlyHeader = findHeader(headers, "content-security-policy-report-only"); + if (reportOnlyHeader) + delete headers[reportOnlyHeader]; + + const header = findHeader(headers, "content-security-policy"); + + if (header) { + const csp = parsePolicy(headers[header][0]); + + const pushDirective = (directive: string, ...values: string[]) => { + csp[directive] ??= [...(csp["default-src"] ?? [])]; + csp[directive].push(...values); + }; + + pushDirective("style-src", "'unsafe-inline'"); + // we could make unsafe-inline safe by using strict-dynamic with a random nonce on our Vencord loader script https://content-security-policy.com/strict-dynamic/ + // HOWEVER, at the time of writing (24 Jan 2025), Discord is INSANE and also uses unsafe-inline + // Once they stop using it, we also should + pushDirective("script-src", "'unsafe-inline'", "'unsafe-eval'"); + + for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) { + pushDirective(directive, "blob:", "data:", "vencord:"); + } + + for (const [host, directives] of Object.entries(CspPolicies)) { + for (const directive of directives) { + pushDirective(directive, host); + } + } + + headers[header] = [stringifyPolicy(csp)]; + } +}; + +export function initCsp() { + session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, resourceType }, cb) => { + if (responseHeaders) { + if (resourceType === "mainFrame") + patchCsp(responseHeaders); + + // Fix hosts that don't properly set the css content type, such as + // raw.githubusercontent.com + if (resourceType === "stylesheet") { + const header = findHeader(responseHeaders, "content-type"); + if (header) + responseHeaders[header] = ["text/css"]; + } + } + + cb({ cancel: false, responseHeaders }); + }); + + // assign a noop to onHeadersReceived to prevent other mods from adding their own incompatible ones. + // For instance, OpenAsar adds their own that doesn't fix content-type for stylesheets which makes it + // impossible to load css from github raw despite our fix above + session.defaultSession.webRequest.onHeadersReceived = () => { }; +} diff --git a/src/main/index.ts b/src/main/index.ts index 4cc2e0db..a001a490 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -16,9 +16,10 @@ * along with this program. If not, see . */ -import { app, protocol, session } from "electron"; +import { app, protocol } from "electron"; import { join } from "path"; +import { initCsp } from "./csp"; import { ensureSafePath } from "./ipcMain"; import { RendererSettings } from "./settings"; import { IS_VANILLA, THEMES_DIR } from "./utils/constants"; @@ -63,70 +64,7 @@ if (IS_VESKTOP || !IS_VANILLA) { } catch { } - const findHeader = (headers: Record, headerName: Lowercase) => { - return Object.keys(headers).find(h => h.toLowerCase() === headerName); - }; - - // Remove CSP - type PolicyResult = Record; - - const parsePolicy = (policy: string): PolicyResult => { - const result: PolicyResult = {}; - policy.split(";").forEach(directive => { - const [directiveKey, ...directiveValue] = directive.trim().split(/\s+/g); - if (directiveKey && !Object.prototype.hasOwnProperty.call(result, directiveKey)) { - result[directiveKey] = directiveValue; - } - }); - - return result; - }; - const stringifyPolicy = (policy: PolicyResult): string => - Object.entries(policy) - .filter(([, values]) => values?.length) - .map(directive => directive.flat().join(" ")) - .join("; "); - - const patchCsp = (headers: Record) => { - const header = findHeader(headers, "content-security-policy"); - - if (header) { - const csp = parsePolicy(headers[header][0]); - - for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) { - csp[directive] ??= []; - csp[directive].push("*", "blob:", "data:", "vencord:", "'unsafe-inline'"); - } - - // TODO: Restrict this to only imported packages with fixed version. - // Perhaps auto generate with esbuild - csp["script-src"] ??= []; - csp["script-src"].push("'unsafe-eval'", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com"); - headers[header] = [stringifyPolicy(csp)]; - } - }; - - session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, resourceType }, cb) => { - if (responseHeaders) { - if (resourceType === "mainFrame") - patchCsp(responseHeaders); - - // Fix hosts that don't properly set the css content type, such as - // raw.githubusercontent.com - if (resourceType === "stylesheet") { - const header = findHeader(responseHeaders, "content-type"); - if (header) - responseHeaders[header] = ["text/css"]; - } - } - - cb({ cancel: false, responseHeaders }); - }); - - // assign a noop to onHeadersReceived to prevent other mods from adding their own incompatible ones. - // For instance, OpenAsar adds their own that doesn't fix content-type for stylesheets which makes it - // impossible to load css from github raw despite our fix above - session.defaultSession.webRequest.onHeadersReceived = () => { }; + initCsp(); }); } diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index 8745584e..b00df6b0 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -33,7 +33,7 @@ import definePlugin from "@utils/types"; import { Forms, Toasts, UserStore } from "@webpack/common"; import { User } from "discord-types/general"; -const CONTRIBUTOR_BADGE = "https://vencord.dev/assets/favicon.png"; +const CONTRIBUTOR_BADGE = "https://cdn.discordapp.com/emojis/1092089799109775453.png?size=64"; const ContributorBadge: ProfileBadge = { description: "Vencord Contributor", diff --git a/src/plugins/devCompanion.dev/index.tsx b/src/plugins/devCompanion.dev/index.tsx index 4ac2e993..7a9a1930 100644 --- a/src/plugins/devCompanion.dev/index.tsx +++ b/src/plugins/devCompanion.dev/index.tsx @@ -91,7 +91,7 @@ function parseNode(node: Node) { function initWs(isManual = false) { let wasConnected = isManual; let hasErrored = false; - const ws = socket = new WebSocket(`ws://localhost:${PORT}`); + const ws = socket = new WebSocket(`ws://127.0.0.1:${PORT}`); ws.addEventListener("open", () => { wasConnected = true; diff --git a/src/plugins/reverseImageSearch/index.tsx b/src/plugins/reverseImageSearch/index.tsx index 17fdb180..a2e7b7e6 100644 --- a/src/plugins/reverseImageSearch/index.tsx +++ b/src/plugins/reverseImageSearch/index.tsx @@ -53,14 +53,12 @@ function makeSearchItem(src: string) { = 3 // Do not round Google, Yandex & SauceNAO - ? "50%" - : void 0 + borderRadius: "50%", }} aria-hidden="true" height={16} width={16} - src={new URL("/favicon.ico", Engines[engine]).toString().replace("lens.", "")} + src={`https://icons.duckduckgo.com/ip3/${new URL(Engines[engine]).host}.ico`} /> {engine} diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 7bcadb42..afe1651f 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -40,7 +40,7 @@ export interface Dev { */ export const Devs = /* #__PURE__*/ Object.freeze({ Ven: { - name: "Vee", + name: "V", id: 343383572805058560n }, Arjix: { @@ -194,7 +194,7 @@ export const Devs = /* #__PURE__*/ Object.freeze({ }, axyie: { name: "'ax", - id: 273562710745284628n, + id: 929877747151548487n, }, pointy: { name: "pointy", @@ -587,7 +587,7 @@ export const Devs = /* #__PURE__*/ Object.freeze({ }, samsam: { name: "samsam", - id: 836452332387565589n, + id: 400482410279469056n, }, Cootshk: { name: "Cootshk", diff --git a/src/utils/cspViolations.ts b/src/utils/cspViolations.ts new file mode 100644 index 00000000..9477bcaa --- /dev/null +++ b/src/utils/cspViolations.ts @@ -0,0 +1,34 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { useLayoutEffect } from "@webpack/common"; + +import { useForceUpdater } from "./react"; + +const cssRelevantDirectives = ["style-src", "img-src", "font-src"] as const; + +export const CspBlockedUrls = new Set(); +const CspErrorListeners = new Set<() => void>(); + +document.addEventListener("securitypolicyviolation", ({ effectiveDirective, blockedURI }) => { + if (!blockedURI || !cssRelevantDirectives.includes(effectiveDirective as any)) return; + + CspBlockedUrls.add(blockedURI); + + CspErrorListeners.forEach(listener => listener()); +}); + +export function useCspErrors() { + const forceUpdate = useForceUpdater(); + + useLayoutEffect(() => { + CspErrorListeners.add(forceUpdate); + + return () => void CspErrorListeners.delete(forceUpdate); + }, [forceUpdate]); + + return [...CspBlockedUrls] as const; +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 70ac9d42..3e7aa0e1 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -21,6 +21,7 @@ export * from "../shared/onceDefined"; export * from "./ChangeList"; export * from "./clipboard"; export * from "./constants"; +export * from "./cspViolations"; export * from "./discord"; export * from "./guards"; export * from "./intlHash"; From fae15dbdfe6e1044b740c82c38e0f971022537f3 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 6 Jun 2025 18:48:56 +0200 Subject: [PATCH 53/54] avoid showing ugly red error cards to users --- src/api/MessageAccessories.tsx | 2 +- src/components/ErrorBoundary.tsx | 7 ++++++- src/components/PluginSettings/PluginModal.tsx | 2 +- src/plugins/decor/index.tsx | 2 +- src/plugins/favGifSearch/index.tsx | 2 +- src/plugins/mentionAvatars/index.tsx | 2 +- src/plugins/messageLinkEmbeds/index.tsx | 13 +++++-------- src/plugins/mutualGroupDMs/index.tsx | 2 +- src/plugins/pauseInvitesForever/index.tsx | 2 +- src/plugins/sortFriendRequests/index.tsx | 2 +- src/plugins/vencordToolbox/index.tsx | 2 +- 11 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/api/MessageAccessories.tsx b/src/api/MessageAccessories.tsx index 71664e93..d2bc081e 100644 --- a/src/api/MessageAccessories.tsx +++ b/src/api/MessageAccessories.tsx @@ -48,7 +48,7 @@ export function _modifyAccessories( ) { for (const [key, accessory] of accessories.entries()) { const res = ( - + ); diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx index 0ca20440..7058b5fd 100644 --- a/src/components/ErrorBoundary.tsx +++ b/src/components/ErrorBoundary.tsx @@ -75,10 +75,15 @@ const ErrorBoundary = LazyComponent(() => { logger.error(`${this.props.message || "A component threw an Error"}\n`, error, errorInfo.componentStack); } + get isNoop() { + if (IS_DEV) return false; + return this.props.noop; + } + render() { if (this.state.error === NO_ERROR) return this.props.children; - if (this.props.noop) return null; + if (this.isNoop) return null; if (this.props.fallback) return ( diff --git a/src/components/PluginSettings/PluginModal.tsx b/src/components/PluginSettings/PluginModal.tsx index 7baeba08..17ab2662 100644 --- a/src/components/PluginSettings/PluginModal.tsx +++ b/src/components/PluginSettings/PluginModal.tsx @@ -270,7 +270,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti {!!plugin.settingsAboutComponent && (
- + diff --git a/src/plugins/decor/index.tsx b/src/plugins/decor/index.tsx index 63963d09..0a6dd85d 100644 --- a/src/plugins/decor/index.tsx +++ b/src/plugins/decor/index.tsx @@ -150,5 +150,5 @@ export default definePlugin({ } }, - DecorSection: ErrorBoundary.wrap(DecorSection) + DecorSection: ErrorBoundary.wrap(DecorSection, { noop: true }) }); diff --git a/src/plugins/favGifSearch/index.tsx b/src/plugins/favGifSearch/index.tsx index d71f5679..5e302912 100644 --- a/src/plugins/favGifSearch/index.tsx +++ b/src/plugins/favGifSearch/index.tsx @@ -118,7 +118,7 @@ export default definePlugin({ renderSearchBar(instance: Instance, SearchBarComponent: TSearchBarComponent) { this.instance = instance; return ( - + ); diff --git a/src/plugins/mentionAvatars/index.tsx b/src/plugins/mentionAvatars/index.tsx index c4a3adce..5466a9e2 100644 --- a/src/plugins/mentionAvatars/index.tsx +++ b/src/plugins/mentionAvatars/index.tsx @@ -99,7 +99,7 @@ export default definePlugin({ src={`${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/role-icons/${roleId}/${role.icon}.webp?size=24&quality=lossless`} /> ); - }), + }, { noop: true }), }); function getUsernameString(username: string) { diff --git a/src/plugins/messageLinkEmbeds/index.tsx b/src/plugins/messageLinkEmbeds/index.tsx index c248167f..69727862 100644 --- a/src/plugins/messageLinkEmbeds/index.tsx +++ b/src/plugins/messageLinkEmbeds/index.tsx @@ -20,7 +20,6 @@ import { addMessageAccessory, removeMessageAccessory } from "@api/MessageAccesso import { updateMessage } from "@api/MessageUpdater"; import { definePluginSettings } from "@api/Settings"; import { getUserSettingLazy } from "@api/UserSettings"; -import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants.js"; import { classes } from "@utils/misc"; import { Queue } from "@utils/Queue"; @@ -373,7 +372,7 @@ export default definePlugin({ settings, start() { - addMessageAccessory("messageLinkEmbed", props => { + addMessageAccessory("MessageLinkEmbeds", props => { if (!messageLinkRegex.test(props.message.content)) return null; @@ -381,16 +380,14 @@ export default definePlugin({ messageLinkRegex.lastIndex = 0; return ( - - - + ); }, 4 /* just above rich embeds */); }, stop() { - removeMessageAccessory("messageLinkEmbed"); + removeMessageAccessory("MessageLinkEmbeds"); } }); diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index 1058410f..796a91db 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -204,5 +204,5 @@ export default definePlugin({ /> ); - }) + }, { noop: true }) }); diff --git a/src/plugins/pauseInvitesForever/index.tsx b/src/plugins/pauseInvitesForever/index.tsx index b648f92e..432d1c1c 100644 --- a/src/plugins/pauseInvitesForever/index.tsx +++ b/src/plugins/pauseInvitesForever/index.tsx @@ -75,5 +75,5 @@ export default definePlugin({ }}> Pause Indefinitely.}
); - }) + }, { noop: true }) }); diff --git a/src/plugins/sortFriendRequests/index.tsx b/src/plugins/sortFriendRequests/index.tsx index 5f45902e..d8b64cf9 100644 --- a/src/plugins/sortFriendRequests/index.tsx +++ b/src/plugins/sortFriendRequests/index.tsx @@ -86,5 +86,5 @@ export default definePlugin({ )}
; - }) + }, { noop: true }) }); diff --git a/src/plugins/vencordToolbox/index.tsx b/src/plugins/vencordToolbox/index.tsx index 754af009..2f671a23 100644 --- a/src/plugins/vencordToolbox/index.tsx +++ b/src/plugins/vencordToolbox/index.tsx @@ -125,7 +125,7 @@ function VencordPopoutButton() { function ToolboxFragmentWrapper({ children }: { children: ReactNode[]; }) { children.splice( children.length - 1, 0, - + ); From a6e2194804de89d6ca00437915a6a12249dd9851 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Fri, 6 Jun 2025 15:31:20 -0400 Subject: [PATCH 54/54] Fixes --- src/components/PluginSettings/index.tsx | 2 +- src/equicordplugins/furudoSpeak.dev/providers/Ollama.ts | 2 +- src/equicordplugins/tosuRPC/index.ts | 2 +- src/plugins/devCompanion.dev/initWs.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx index 7c2d3107..1bfaee37 100644 --- a/src/components/PluginSettings/index.tsx +++ b/src/components/PluginSettings/index.tsx @@ -244,7 +244,7 @@ export default function PluginSettings() { })); }, []); - const depMap = React.useMemo(() => { + const depMap = useMemo(() => { const o = {} as Record; for (const plugin in Plugins) { const deps = Plugins[plugin].dependencies; diff --git a/src/equicordplugins/furudoSpeak.dev/providers/Ollama.ts b/src/equicordplugins/furudoSpeak.dev/providers/Ollama.ts index 32d836b4..e36d40de 100644 --- a/src/equicordplugins/furudoSpeak.dev/providers/Ollama.ts +++ b/src/equicordplugins/furudoSpeak.dev/providers/Ollama.ts @@ -24,7 +24,7 @@ export default async ( }: FurudoSettings, repliedMessage?: Message ): Promise => { - const completion = await fetch("http://localhost:11434/api/chat", { + const completion = await fetch("http://127.0.0.1:11434/api/chat", { method: "POST", headers: { "Content-Type": "application/json", diff --git a/src/equicordplugins/tosuRPC/index.ts b/src/equicordplugins/tosuRPC/index.ts index b1727773..05d908b3 100644 --- a/src/equicordplugins/tosuRPC/index.ts +++ b/src/equicordplugins/tosuRPC/index.ts @@ -29,7 +29,7 @@ export default definePlugin({ authors: [Devs.AutumnVN], start() { (function connect() { - ws = new WebSocket("ws://localhost:24050/websocket/v2"); + ws = new WebSocket("ws://127.0.0.1:24050/websocket/v2"); ws.addEventListener("error", () => ws.close()); ws.addEventListener("close", () => wsReconnect = setTimeout(connect, 5000)); ws.addEventListener("message", ({ data }) => throttledOnMessage(data)); diff --git a/src/plugins/devCompanion.dev/initWs.tsx b/src/plugins/devCompanion.dev/initWs.tsx index 93b89b68..211dbbcb 100644 --- a/src/plugins/devCompanion.dev/initWs.tsx +++ b/src/plugins/devCompanion.dev/initWs.tsx @@ -29,7 +29,7 @@ export let socket: WebSocket | undefined; export function initWs(isManual = false) { let wasConnected = isManual; let hasErrored = false; - const ws = socket = new WebSocket(`ws://localhost:${PORT}`); + const ws = socket = new WebSocket(`ws://127.0.0.1:${PORT}`); function replyData(data: OutgoingMessage) { ws.send(JSON.stringify(data));