From 746c8240206107dfc40f0995d17cd24730d40b0d Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 23 Sep 2025 22:24:58 +0200 Subject: [PATCH 01/16] Fix Debug Logging toggle not working Closes #3268 --- src/plugins/_core/noTrack.ts | 9 +++++++++ src/utils/types.ts | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/plugins/_core/noTrack.ts b/src/plugins/_core/noTrack.ts index ad1f255ba..eada58cbf 100644 --- a/src/plugins/_core/noTrack.ts +++ b/src/plugins/_core/noTrack.ts @@ -71,6 +71,15 @@ export default definePlugin({ } ], + // The TRACK event takes an optional `resolve` property that is called when the tracking event was submitted to the server. + // A few spots in Discord await this callback before continuing (most notably the Voice Debug Logging toggle). + // Since we NOOP the AnalyticsActionHandlers module, there is no handler for the TRACK event, so we have to handle it ourselves + flux: { + TRACK(event) { + event?.resolve?.(); + } + }, + startAt: StartAt.Init, start() { // Sentry is initialized in its own WebpackInstance. diff --git a/src/utils/types.ts b/src/utils/types.ts index 2f1d6396c..2293b2ffa 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -26,6 +26,7 @@ import { MessageClickListener, MessageEditListener, MessageSendListener } from " import { MessagePopoverButtonFactory } from "@api/MessagePopover"; import { Command, FluxEvents } from "@vencord/discord-types"; import { ReactNode } from "react"; +import { LiteralUnion } from "type-fest"; // exists to export default definePlugin({...}) export default function definePlugin

(p: P & Record) { @@ -151,7 +152,7 @@ export interface PluginDef { * Allows you to subscribe to Flux events */ flux?: { - [E in FluxEvents]?: (event: any) => void | Promise; + [E in LiteralUnion]?: (event: any) => void | Promise; }; /** * Allows you to manipulate context menus From cb845b522458532479c077ed82279c27e025601e Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 23 Sep 2025 23:58:38 +0200 Subject: [PATCH 02/16] PlatformIndicators: update indicators in real time if status changes Closes #3516 --- packages/discord-types/src/utils.d.ts | 8 +++-- src/plugins/index.ts | 4 +-- src/plugins/platformIndicators/index.tsx | 41 +++++++----------------- src/utils/types.ts | 6 ++-- 4 files changed, 21 insertions(+), 38 deletions(-) diff --git a/packages/discord-types/src/utils.d.ts b/packages/discord-types/src/utils.d.ts index 77b6f88b8..14b8fa507 100644 --- a/packages/discord-types/src/utils.d.ts +++ b/packages/discord-types/src/utils.d.ts @@ -6,13 +6,15 @@ import type { FluxEvents } from "./fluxEvents"; export { FluxEvents }; +type FluxEventsAutoComplete = LiteralUnion; + export interface FluxDispatcher { _actionHandlers: any; _subscriptions: any; - dispatch(event: { [key: string]: unknown; type: FluxEvents; }): Promise; + dispatch(event: { [key: string]: unknown; type: FluxEventsAutoComplete; }): Promise; isDispatching(): boolean; - subscribe(event: FluxEvents, callback: (data: any) => void): void; - unsubscribe(event: FluxEvents, callback: (data: any) => void): void; + subscribe(event: FluxEventsAutoComplete, callback: (data: any) => void): void; + unsubscribe(event: FluxEventsAutoComplete, callback: (data: any) => void): void; wait(callback: () => void): void; } diff --git a/src/plugins/index.ts b/src/plugins/index.ts index 8288935bd..5bb60a13a 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -222,7 +222,7 @@ export function subscribePluginFluxEvents(p: Plugin, fluxDispatcher: typeof Flux for (const [event, handler] of Object.entries(p.flux)) { const wrappedHandler = p.flux[event] = function () { try { - const res = handler.apply(p, arguments as any); + const res = handler!.apply(p, arguments as any); return res instanceof Promise ? res.catch(e => logger.error(`${p.name}: Error while handling ${event}\n`, e)) : res; @@ -242,7 +242,7 @@ export function unsubscribePluginFluxEvents(p: Plugin, fluxDispatcher: typeof Fl logger.debug("Unsubscribing from flux events of plugin", p.name); for (const [event, handler] of Object.entries(p.flux)) { - fluxDispatcher.unsubscribe(event as FluxEvents, handler); + fluxDispatcher.unsubscribe(event as FluxEvents, handler!); } } } diff --git a/src/plugins/platformIndicators/index.tsx b/src/plugins/platformIndicators/index.tsx index f804e987c..add930648 100644 --- a/src/plugins/platformIndicators/index.tsx +++ b/src/plugins/platformIndicators/index.tsx @@ -22,12 +22,11 @@ import { addProfileBadge, BadgePosition, BadgeUserArgs, ProfileBadge, removeProf import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators"; import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations"; import { Settings } from "@api/Settings"; -import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { User } from "@vencord/discord-types"; import { filters, findStoreLazy, mapMangledModuleLazy } from "@webpack"; -import { PresenceStore, Tooltip, UserStore } from "@webpack/common"; +import { AuthenticationStore, PresenceStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common"; export interface Session { sessionId: string; @@ -85,7 +84,7 @@ const PlatformIcon = ({ platform, status, small }: { platform: Platform, status: }; function ensureOwnStatus(user: User) { - if (user.id === UserStore.getCurrentUser().id) { + if (user.id === AuthenticationStore.getId()) { const sessions = SessionsStore.getSessions(); if (typeof sessions !== "object") return null; const sortedSessions = Object.values(sessions).sort(({ status: a }, { status: b }) => { @@ -104,7 +103,7 @@ function ensureOwnStatus(user: User) { }, {}); const { clientStatuses } = PresenceStore.getState(); - clientStatuses[UserStore.getCurrentUser().id] = ownStatus; + clientStatuses[AuthenticationStore.getId()] = ownStatus; } } @@ -115,7 +114,7 @@ function getBadges({ userId }: BadgeUserArgs): ProfileBadge[] { ensureOwnStatus(user); - const status = PresenceStore.getState()?.clientStatuses?.[user.id] as Record; + const status = PresenceStore.getClientStatus(user.id) as Record; if (!status) return []; return Object.entries(status).map(([platform, status]) => ({ @@ -134,11 +133,9 @@ function getBadges({ userId }: BadgeUserArgs): ProfileBadge[] { } const PlatformIndicator = ({ user, small = false }: { user: User; small?: boolean; }) => { - if (!user || user.bot) return null; - ensureOwnStatus(user); - const status = PresenceStore.getState()?.clientStatuses?.[user.id] as Record; + const status = useStateFromStores([PresenceStore], () => PresenceStore.getClientStatus(user.id) as Record); if (!status) return null; const icons = Object.entries(status).map(([platform, status]) => ( @@ -170,10 +167,8 @@ const badge: ProfileBadge = { const indicatorLocations = { list: { description: "In the member list", - onEnable: () => addMemberListDecorator("platform-indicator", props => - - - + onEnable: () => addMemberListDecorator("platform-indicator", ({ user }) => + user && !user.bot ? : null ), onDisable: () => removeMemberListDecorator("platform-indicator") }, @@ -184,11 +179,10 @@ const indicatorLocations = { }, messages: { description: "Inside messages", - onEnable: () => addMessageDecoration("platform-indicator", props => - - - - ), + onEnable: () => addMessageDecoration("platform-indicator", props => { + const user = props.message?.author; + return user && !user.bot ? : null; + }), onDisable: () => removeMessageDecoration("platform-indicator") } }; @@ -201,19 +195,6 @@ export default definePlugin({ start() { const settings = Settings.plugins.PlatformIndicators; - const { displayMode } = settings; - - // transfer settings from the old ones, which had a select menu instead of booleans - if (displayMode) { - if (displayMode !== "both") settings[displayMode] = true; - else { - settings.list = true; - settings.badges = true; - } - settings.messages = true; - delete settings.displayMode; - } - Object.entries(indicatorLocations).forEach(([key, value]) => { if (settings[key]) value.onEnable(); }); diff --git a/src/utils/types.ts b/src/utils/types.ts index 2293b2ffa..2d21eafff 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -151,9 +151,9 @@ export interface PluginDef { /** * Allows you to subscribe to Flux events */ - flux?: { - [E in LiteralUnion]?: (event: any) => void | Promise; - }; + flux?: Partial<{ + [E in LiteralUnion]: (event: any) => void | Promise; + }>; /** * Allows you to manipulate context menus */ From 7c839be64fbef65b4825ec1d17acd0264aadcd64 Mon Sep 17 00:00:00 2001 From: thororen <78185467+thororen1234@users.noreply.github.com> Date: Wed, 24 Sep 2025 10:49:50 -0400 Subject: [PATCH 03/16] BetterFolders: close folder if the last server is removed (#3658) Co-authored-by: V --- src/plugins/betterFolders/FolderSideBar.tsx | 23 +++++++++++++++++---- src/plugins/betterFolders/index.tsx | 5 ++--- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/plugins/betterFolders/FolderSideBar.tsx b/src/plugins/betterFolders/FolderSideBar.tsx index 40329122a..0e643958e 100644 --- a/src/plugins/betterFolders/FolderSideBar.tsx +++ b/src/plugins/betterFolders/FolderSideBar.tsx @@ -21,24 +21,39 @@ import { findComponentByCodeLazy, findStoreLazy } from "@webpack"; import { Animations, useStateFromStores } from "@webpack/common"; import type { CSSProperties } from "react"; -import { ExpandedGuildFolderStore, settings } from "."; +import { ExpandedGuildFolderStore, settings, SortedGuildStore } from "."; const ChannelRTCStore = findStoreLazy("ChannelRTCStore"); const GuildsBar = findComponentByCodeLazy('("guildsnav")'); +function getExpandedFolderIds() { + const expandedFolders = ExpandedGuildFolderStore.getExpandedFolders(); + const folders = SortedGuildStore.getGuildFolders(); + + const expandedFolderIds = new Set(); + + for (const folder of folders) { + if (expandedFolders.has(folder.folderId) && folder.guildIds?.length) { + expandedFolderIds.add(folder.folderId); + } + } + + return expandedFolderIds; +} + export default ErrorBoundary.wrap(guildsBarProps => { - const expandedFolders = useStateFromStores([ExpandedGuildFolderStore], () => ExpandedGuildFolderStore.getExpandedFolders()); + const expandedFolderIds = useStateFromStores([ExpandedGuildFolderStore, SortedGuildStore], () => getExpandedFolderIds()); const isFullscreen = useStateFromStores([ChannelRTCStore], () => ChannelRTCStore.isFullscreenInContext()); const Sidebar = ( ); - const visible = !!expandedFolders.size; + const visible = !!expandedFolderIds.size; const guilds = document.querySelector(guildsBarProps.className.split(" ").map(c => `.${c}`).join("")); // We need to display none if we are in fullscreen. Yes this seems horrible doing with css, but it's literally how Discord does it. diff --git a/src/plugins/betterFolders/index.tsx b/src/plugins/betterFolders/index.tsx index 62567f51d..5a1754d31 100644 --- a/src/plugins/betterFolders/index.tsx +++ b/src/plugins/betterFolders/index.tsx @@ -22,7 +22,7 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import { getIntlMessage } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; -import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack"; +import { findByPropsLazy, findStoreLazy } from "@webpack"; import { FluxDispatcher } from "@webpack/common"; import { ReactNode } from "react"; @@ -35,8 +35,7 @@ enum FolderIconDisplay { } export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore"); -const SortedGuildStore = findStoreLazy("SortedGuildStore"); -const GuildsTree = findLazy(m => m.prototype?.moveNextTo); +export const SortedGuildStore = findStoreLazy("SortedGuildStore"); const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand"); let lastGuildId = null as string | null; From d89aa55f32c1cc5d0306153ffeb2f60e14fb8369 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 24 Sep 2025 13:04:55 -0400 Subject: [PATCH 04/16] Animalese: Update Readme --- src/equicordplugins/animalese/README.md | 10 ++++------ src/equicordplugins/chatButtonsPlus/index.tsx | 3 +-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/equicordplugins/animalese/README.md b/src/equicordplugins/animalese/README.md index 02a59c96b..a660add88 100644 --- a/src/equicordplugins/animalese/README.md +++ b/src/equicordplugins/animalese/README.md @@ -1,18 +1,16 @@ -## vencord-animalese -animalese yapping in your discord client +## Animalese -> [!WARNING] -> don't go to Vencord support if you can't install this. +animalese yapping in your discord client ### demo -https://github.com/user-attachments/assets/3f1fa925-bbd2-451d-b63e-ea14dbe4be44 + ### settings ##### settings used in the video -![CleanShot 2024-12-26 at 22 11 49@2x](https://github.com/user-attachments/assets/35895fcc-d703-42cb-b0ef-bdef39ab15be) +![settings](https://cdn.nest.rip/uploads/32f09dd7-3cb2-4065-84ed-cbc5b193b2ec.png) you can change the settings in the settings but otherwise, enjoy the yap diff --git a/src/equicordplugins/chatButtonsPlus/index.tsx b/src/equicordplugins/chatButtonsPlus/index.tsx index 5a8836acf..9ea64a434 100644 --- a/src/equicordplugins/chatButtonsPlus/index.tsx +++ b/src/equicordplugins/chatButtonsPlus/index.tsx @@ -227,8 +227,7 @@ migratePluginSettings("ChatButtonsPlus", "Meow", "Woof"); export default definePlugin({ name: "ChatButtonsPlus", description: "Add custom chat buttons with personalized + messages and SVG icons", - authors: - [EquicordDevs.creations], + authors: [EquicordDevs.creations], settings, renderChatBarButton: ({ isMainChat }) => { From c265b89e64e9dc8014f70ac2b4de9c141068e62d Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 24 Sep 2025 19:51:08 -0400 Subject: [PATCH 05/16] WebRichPresence: Fix Migrate --- src/plugins/arRPC.web/index.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plugins/arRPC.web/index.tsx b/src/plugins/arRPC.web/index.tsx index 00fc0f16d..d39a3e95e 100644 --- a/src/plugins/arRPC.web/index.tsx +++ b/src/plugins/arRPC.web/index.tsx @@ -17,6 +17,7 @@ */ import { popNotice, showNotice } from "@api/Notices"; +import { Settings } from "@api/Settings"; import { Link } from "@components/Link"; import { Devs } from "@utils/constants"; import definePlugin, { ReporterTestable } from "@utils/types"; @@ -40,6 +41,7 @@ let hideSetting = false; if (IS_VESKTOP || IS_EQUIBOP || "legcord" in window) { hideSetting = true; + Settings.plugins["WebRichPresence (arRPC)"].enabled = false; } else if ("goofcord" in window) { hideSetting = false; } From c5a1bbd7db2472cfb7cfc62a0cec59f3135e479e Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 25 Sep 2025 15:19:34 +0200 Subject: [PATCH 06/16] GameActivityToggle: fix background colour when using nameplate --- src/plugins/gameActivityToggle/index.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plugins/gameActivityToggle/index.tsx b/src/plugins/gameActivityToggle/index.tsx index 97885177b..8959acc29 100644 --- a/src/plugins/gameActivityToggle/index.tsx +++ b/src/plugins/gameActivityToggle/index.tsx @@ -60,7 +60,7 @@ function makeIcon(showCurrentGame?: boolean) { }; } -function GameActivityToggleButton() { +function GameActivityToggleButton(props: { nameplate?: any; }) { const showCurrentGame = ShowCurrentGame.useSetting(); return ( @@ -70,6 +70,7 @@ function GameActivityToggleButton() { role="switch" aria-checked={!showCurrentGame} redGlow={!showCurrentGame} + plated={props?.nameplate != null} onClick={() => ShowCurrentGame.updateSetting(old => !old)} /> ); @@ -97,7 +98,7 @@ export default definePlugin({ find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}", replacement: { match: /className:\i\.buttons,.{0,50}children:\[/, - replace: "$&$self.GameActivityToggleButton()," + replace: "$&$self.GameActivityToggleButton(arguments[0])," } } ], From 37964ae0e53bddb82ae5871e0ab897d93f2b665d Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 25 Sep 2025 11:01:28 -0400 Subject: [PATCH 07/16] Lint --- src/components/settings/tabs/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/settings/tabs/index.ts b/src/components/settings/tabs/index.ts index ae31d26a4..0d4704c10 100644 --- a/src/components/settings/tabs/index.ts +++ b/src/components/settings/tabs/index.ts @@ -8,6 +8,7 @@ import "./styles.css"; export { default as ThemesTab } from "../../ThemeSettings/ThemesTab"; export * from "./BaseTab"; +export { default as ChangelogTab } from "./changelog"; export { default as PatchHelperTab } from "./patchHelper"; export { default as PluginsTab } from "./plugins"; export { openContributorModal } from "./plugins/ContributorModal"; @@ -16,4 +17,3 @@ export { default as BackupAndRestoreTab } from "./sync/BackupAndRestoreTab"; export { default as CloudTab } from "./sync/CloudTab"; export { openUpdaterModal, default as UpdaterTab } from "./updater"; export { default as VencordTab } from "./vencord"; -export { default as ChangelogTab } from "./changelog"; From 20b7e0a5fcbdc442a75665408e666207d74092b0 Mon Sep 17 00:00:00 2001 From: Etorix <92535668+EtorixDev@users.noreply.github.com> Date: Thu, 25 Sep 2025 08:06:40 -0700 Subject: [PATCH 08/16] Fix. (#414) --- src/equicordplugins/questify/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equicordplugins/questify/index.tsx b/src/equicordplugins/questify/index.tsx index 00dd5a987..ae0fe1cbf 100644 --- a/src/equicordplugins/questify/index.tsx +++ b/src/equicordplugins/questify/index.tsx @@ -1191,7 +1191,7 @@ export default definePlugin({ { // The Quest Accepted button is disabled by default. If the user reloads the client, they need a way // to resume the automatic completion, so patch in optionally enabling it if the feature is enabled. - match: /(START_QUEST_CTA.{0,400}?disabled:)(!0)/, + match: /(fullWidth:!0}\)}\):.{0,150}?disabled:)(!0.{0,150}?"horizontal")/, replace: "$1$self.shouldDisableQuestAcceptedButton(arguments[0].quest)??$2" }, { From db4c865eeedf6e8e557f617e73c2f2d456bfa183 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 25 Sep 2025 11:20:50 -0400 Subject: [PATCH 09/16] StatusWhilePlaying: Logic Updates --- .../statusWhilePlaying.desktop/index.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/equicordplugins/statusWhilePlaying.desktop/index.ts b/src/equicordplugins/statusWhilePlaying.desktop/index.ts index 00913ab20..15524df46 100644 --- a/src/equicordplugins/statusWhilePlaying.desktop/index.ts +++ b/src/equicordplugins/statusWhilePlaying.desktop/index.ts @@ -11,7 +11,7 @@ import definePlugin, { OptionType } from "@utils/types"; import { PresenceStore, UserStore } from "@webpack/common"; let savedStatus = ""; -const statusSettings = getUserSettingLazy("status", "status"); +const StatusSettings = getUserSettingLazy("status", "status"); const settings = definePluginSettings({ statusToSet: { type: OptionType.SELECT, @@ -44,17 +44,18 @@ export default definePlugin({ authors: [Devs.thororen], settings, flux: { - RUNNING_GAMES_CHANGE(event) { - const status = PresenceStore.getStatus(UserStore.getCurrentUser().id); - if (event.games.length > 0) { + RUNNING_GAMES_CHANGE({ games }) { + const userId = UserStore.getCurrentUser().id; + const status = PresenceStore.getStatus(userId); + + if (games.length > 0) { if (status !== settings.store.statusToSet) { savedStatus = status; - statusSettings?.updateSetting(settings.store.statusToSet); + StatusSettings?.updateSetting(settings.store.statusToSet); } - } else { - if (savedStatus !== "" && savedStatus !== settings.store.statusToSet) - statusSettings?.updateSetting(savedStatus); + } else if (savedStatus && savedStatus !== settings.store.statusToSet) { + StatusSettings?.updateSetting(savedStatus); } - }, + } } }); From 41b6038f490dbe0d96c300a731f2e8c9bb4c01a7 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 25 Sep 2025 18:35:23 -0400 Subject: [PATCH 10/16] WebRichPresence: Proper Name --- src/plugins/arRPC.web/index.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/plugins/arRPC.web/index.tsx b/src/plugins/arRPC.web/index.tsx index d39a3e95e..12f66d0c2 100644 --- a/src/plugins/arRPC.web/index.tsx +++ b/src/plugins/arRPC.web/index.tsx @@ -17,7 +17,7 @@ */ import { popNotice, showNotice } from "@api/Notices"; -import { Settings } from "@api/Settings"; +import { migratePluginSettings, Settings } from "@api/Settings"; import { Link } from "@components/Link"; import { Devs } from "@utils/constants"; import definePlugin, { ReporterTestable } from "@utils/types"; @@ -38,17 +38,18 @@ async function lookupApp(applicationId: string): Promise { } let hideSetting = false; +let ws: WebSocket; if (IS_VESKTOP || IS_EQUIBOP || "legcord" in window) { hideSetting = true; - Settings.plugins["WebRichPresence (arRPC)"].enabled = false; + Settings.plugins.WebRichPresence.enabled = false; } else if ("goofcord" in window) { hideSetting = false; } -let ws: WebSocket; +migratePluginSettings("WebRichPresence", "WebRichPresence (arRPC)"); export default definePlugin({ - name: "WebRichPresence (arRPC)", + name: "WebRichPresence", description: "Client plugin for arRPC to enable RPC on Discord Web (experimental)", authors: [Devs.Ducko], reporterTestable: ReporterTestable.None, From 1fcc55a463e1732e85915733f3730322f16a2c38 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 25 Sep 2025 18:42:23 -0400 Subject: [PATCH 11/16] Fix Patches Co-Authored-By: sadan4 <117494111+sadan4@users.noreply.github.com> --- src/plugins/_core/settings.tsx | 6 ++++-- src/plugins/implicitRelationships/index.ts | 2 +- src/webpack/common/components.ts | 8 +------- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx index fe9ba59f8..e8a5c1cea 100644 --- a/src/plugins/_core/settings.tsx +++ b/src/plugins/_core/settings.tsx @@ -77,11 +77,13 @@ export default definePlugin({ }, ] }, + // Fix the settings cog context menu to work properly { find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}", replacement: { - match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.\i\).*?(\i\.\i)\.open\()/, - replace: "$2.open($1);return;", + // Skip the check discord performs to make sure the section is valid + match: /(?<=function\((\i),(\i),\i\)\{)(?=let \i=Object.values\(\i\.\i\).*?(\(0,\i\.openUserSettings\))\()/, + replace: (_, target, section, func) => `${func}(${target},{section:${section}});return;` }, }, ], diff --git a/src/plugins/implicitRelationships/index.ts b/src/plugins/implicitRelationships/index.ts index fb0a5927f..9c732c411 100644 --- a/src/plugins/implicitRelationships/index.ts +++ b/src/plugins/implicitRelationships/index.ts @@ -48,7 +48,7 @@ export default definePlugin({ }, // Sections header { - find: "#{intl::FRIENDS_SECTION_ONLINE}", + find: "#{intl::FRIENDS_SECTION_ONLINE}),className", replacement: { match: /,{id:(\i\.\i)\.PENDING,show:.+?className:(\i\.item)/, replace: (rest, relationShipTypes, className) => `,{id:${relationShipTypes}.IMPLICIT,show:true,className:${className},content:"Implicit"}${rest}` diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts index 9715619a3..0ccb9babb 100644 --- a/src/webpack/common/components.ts +++ b/src/webpack/common/components.ts @@ -87,13 +87,7 @@ export const ListScrollerNone = LazyComponent(() => createListScroller(listScrol export const ListScrollerThin = LazyComponent(() => createListScroller(listScrollerClasses.thin, listScrollerClasses.fade, "", ResizeObserver)); export const ListScrollerAuto = LazyComponent(() => createListScroller(listScrollerClasses.auto, listScrollerClasses.fade, "", ResizeObserver)); -const { FocusLock_ } = mapMangledModuleLazy('document.getElementById("app-mount"))', { - FocusLock_: filters.componentByCode(".containerRef") -}) as { - FocusLock_: t.FocusLock; -}; - -export const FocusLock = LazyComponent(() => FocusLock_); +export const FocusLock = waitForComponent("FocusLock", filters.componentByCode("containerRef,{keyboardModeEnabled")); export let useToken: t.useToken; waitFor(m => { From 6dd595b4fd224aefeaed48a1e31a233aede45e8f Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 25 Sep 2025 18:58:46 -0400 Subject: [PATCH 12/16] PluginCard: Website Info --- src/components/settings/tabs/plugins/PluginCard.tsx | 4 ++-- src/components/settings/tabs/plugins/PluginModal.tsx | 7 ++++++- src/components/settings/tabs/plugins/index.tsx | 4 ++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/components/settings/tabs/plugins/PluginCard.tsx b/src/components/settings/tabs/plugins/PluginCard.tsx index 9cf7f74b8..25cf7c7a8 100644 --- a/src/components/settings/tabs/plugins/PluginCard.tsx +++ b/src/components/settings/tabs/plugins/PluginCard.tsx @@ -38,8 +38,8 @@ interface PluginCardProps extends React.HTMLProps { export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) { const settings = Settings.plugins[plugin.name]; const pluginMeta = PluginMeta[plugin.name]; - const isEquicordPlugin = pluginMeta?.folderName?.startsWith("src/equicordplugins/") ?? false; - const isUserplugin = pluginMeta?.userPlugin ?? false; + const isEquicordPlugin = pluginMeta.folderName.startsWith("src/equicordplugins/") ?? false; + const isUserplugin = pluginMeta.userPlugin ?? false; const isEnabled = () => isPluginEnabled(plugin.name); diff --git a/src/components/settings/tabs/plugins/PluginModal.tsx b/src/components/settings/tabs/plugins/PluginModal.tsx index 59de61aa2..239a8d145 100644 --- a/src/components/settings/tabs/plugins/PluginModal.tsx +++ b/src/components/settings/tabs/plugins/PluginModal.tsx @@ -39,7 +39,7 @@ import { PluginMeta } from "~plugins"; import { OptionComponentMap } from "./components"; import { openContributorModal } from "./ContributorModal"; -import { GithubButton } from "./LinkIconButton"; +import { GithubButton, WebsiteButton } from "./LinkIconButton"; const cl = classNameFactory("vc-plugin-modal-"); @@ -146,6 +146,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti } const pluginMeta = PluginMeta[plugin.name]; + const isEquicordPlugin = pluginMeta.folderName.startsWith("src/equicordplugins/") ?? false; return ( @@ -160,6 +161,10 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti {plugin.description} {!pluginMeta.userPlugin && (

+ Date: Fri, 26 Sep 2025 00:25:09 -0400 Subject: [PATCH 13/16] Plugin Cards (#415) --- src/components/Icons.tsx | 17 ++ .../settings/tabs/plugins/PluginCard.tsx | 6 +- .../settings/tabs/plugins/index.tsx | 36 ++-- src/equicordplugins/equicordHelper/README.md | 2 +- src/equicordplugins/equicordHelper/index.tsx | 67 +------- .../equicordHelper/pluginButtons.tsx | 71 ++++++++ .../equicordHelper/pluginCards.tsx | 155 ++++++++++++++++++ src/plugins/index.ts | 31 +++- src/plugins/messageLogger/index.tsx | 5 +- src/utils/constants.ts | 1 + 10 files changed, 301 insertions(+), 90 deletions(-) create mode 100644 src/equicordplugins/equicordHelper/pluginButtons.tsx create mode 100644 src/equicordplugins/equicordHelper/pluginCards.tsx diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index c38ba1a5c..76096ad84 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -128,6 +128,23 @@ export function InfoIcon(props: IconProps) { ); } +export function WarningIcon({ height = 32, width = 32, className }: IconProps) { + return ( + + + + ); +} + export function OwnerCrownIcon(props: IconProps) { return ( { +interface PluginCardProps { plugin: Plugin; - disabled: boolean; + disabled?: boolean; onRestartNeeded(name: string, key: string): void; isNew?: boolean; + onMouseEnter?: React.MouseEventHandler; + onMouseLeave?: React.MouseEventHandler; } export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) { diff --git a/src/components/settings/tabs/plugins/index.tsx b/src/components/settings/tabs/plugins/index.tsx index deef932ab..20a747505 100644 --- a/src/components/settings/tabs/plugins/index.tsx +++ b/src/components/settings/tabs/plugins/index.tsx @@ -104,19 +104,19 @@ const enum SearchStatus { NEW, } +export const ExcludedReasons: Record<"web" | "discordDesktop" | "vesktop" | "equibop" | "desktop" | "dev", string> = { + desktop: "Discord Desktop app or Vesktop", + discordDesktop: "Discord Desktop app", + vesktop: "Vesktop & Equibop apps", + equibop: "Vesktop & Equibop apps", + web: "Vesktop & Equibop apps as well as the Web version of Discord", + dev: "Developer version of Equicord" +}; + function ExcludedPluginsList({ search }: { search: string; }) { const matchingExcludedPlugins = Object.entries(ExcludedPlugins) .filter(([name]) => name.toLowerCase().includes(search)); - const ExcludedReasons: Record<"web" | "discordDesktop" | "vesktop" | "equibop" | "desktop" | "dev", string> = { - desktop: "Discord Desktop app or Vesktop", - discordDesktop: "Discord Desktop app", - vesktop: "Vesktop & Equibop apps", - equibop: "Vesktop & Equibop apps", - web: "Vesktop & Equibop apps as well as the Web version of Discord", - dev: "Developer version of Equicord" - }; - return ( {matchingExcludedPlugins.length @@ -160,19 +160,7 @@ export default function PluginSettings() { })); }, []); - const depMap = useMemo(() => { - const o = {} as Record; - for (const plugin in Plugins) { - const deps = Plugins[plugin].dependencies; - if (deps) { - for (const dep of deps) { - o[dep] ??= []; - o[dep].push(plugin); - } - } - } - return o; - }, []); + const depMap = Vencord.Plugins.calculatePluginDependencyMap(); const sortedPlugins = useMemo(() => Object.values(Plugins) .sort((a, b) => a.name.localeCompare(b.name)), []); @@ -241,7 +229,7 @@ export default function PluginSettings() { if (isRequired) { const tooltipText = p.required || !depMap[p.name] ? "This plugin is required for Equicord to function." - : makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled)); + : settings.plugins[d].enabled)} />; requiredPlugins.push( @@ -419,7 +407,7 @@ export default function PluginSettings() { ); } -function makeDependencyList(deps: string[]) { +export function PluginDependencyList({ deps }: { deps: string[]; }) { return ( This plugin is required by: diff --git a/src/equicordplugins/equicordHelper/README.md b/src/equicordplugins/equicordHelper/README.md index 9c1dcab8a..d371ef512 100644 --- a/src/equicordplugins/equicordHelper/README.md +++ b/src/equicordplugins/equicordHelper/README.md @@ -1,3 +1,3 @@ # EquicordHelper -Fixes various issues for crashing and adds other items that might help with support +Fixes various issues for crashing and adds other items such as plugin cards and buttons for managing plugins quickly in support diff --git a/src/equicordplugins/equicordHelper/index.tsx b/src/equicordplugins/equicordHelper/index.tsx index 0d068f6b3..0f33cd1a6 100644 --- a/src/equicordplugins/equicordHelper/index.tsx +++ b/src/equicordplugins/equicordHelper/index.tsx @@ -8,13 +8,10 @@ import "@equicordplugins/_misc/styles.css"; import { definePluginSettings } from "@api/Settings"; import { Devs, EquicordDevs } from "@utils/constants"; -import { Logger } from "@utils/Logger"; -import { isEquicordGuild, isEquicordSupport } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; -import { Button, Flex, showToast, Toasts } from "@webpack/common"; -import { JSX } from "react"; -import { toggleEnabled } from "./utils"; +import { PluginButtons } from "./pluginButtons"; +import { PluginCards } from "./pluginCards"; const settings = definePluginSettings({ disableCreateDMButton: { @@ -70,59 +67,11 @@ export default definePlugin({ }, ], renderMessageAccessory(props) { - return pluginToggleButtons(props); - } -}); - -function pluginToggleButtons(props) { - const buttons = [] as JSX.Element[]; - const msg = props.message.content?.toLowerCase() ?? ""; - - const contentWords = (msg.match(/`\w+`/g) ?? []).map(e => e.slice(1, -1)); - const matchedPlugins = Object.keys(Vencord.Plugins.plugins).filter(name => contentWords.includes(name.toLowerCase())); - const matchedPlugin = matchedPlugins.sort((a, b) => b.length - a.length)[0]; - const pluginData = matchedPlugin ? Vencord.Plugins.plugins[matchedPlugin] : null; - - const isEquicord = isEquicordGuild(props.channel.id) && isEquicordSupport(props.message.author.id); - const startsWithEnabled = msg.startsWith("enable"); - const startsWithDisabled = msg.startsWith("disable"); - - const shouldAddPluginButtons = pluginData && isEquicord && (startsWithEnabled || startsWithDisabled); - - if (shouldAddPluginButtons) { - if (pluginData.required || pluginData.name.endsWith("API")) return; - const isEnabled = Vencord.Plugins.isPluginEnabled(matchedPlugin); - - let label = `${matchedPlugin} is already ${isEnabled ? "enabled" : "disabled"}`; - let disabled = true; - - if ((startsWithDisabled && isEnabled) || (startsWithEnabled && !isEnabled)) { - label = `${isEnabled ? "Disable" : "Enable"} ${matchedPlugin}`; - disabled = false; - } - - buttons.push( - + return ( + <> + + + ); } - - return buttons.length - ? {buttons} - : null; -} +}); diff --git a/src/equicordplugins/equicordHelper/pluginButtons.tsx b/src/equicordplugins/equicordHelper/pluginButtons.tsx new file mode 100644 index 000000000..a7c26e0d0 --- /dev/null +++ b/src/equicordplugins/equicordHelper/pluginButtons.tsx @@ -0,0 +1,71 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import ErrorBoundary from "@components/ErrorBoundary"; +import { Logger } from "@utils/Logger"; +import { isEquicordGuild, isEquicordSupport } from "@utils/misc"; +import { Message } from "@vencord/discord-types"; +import { Button, showToast, Toasts } from "@webpack/common"; +import { JSX } from "react"; + +import { toggleEnabled } from "./utils"; + +export const PluginButtons = ErrorBoundary.wrap(function PluginCards({ message }: { message: Message; }) { + const pluginButtons = [] as JSX.Element[]; + const msg = message.content?.toLowerCase() ?? ""; + + const contentWords = (msg.match(/`\w+`/g) ?? []).map(e => e.slice(1, -1)); + const matchedPlugins = Object.keys(Vencord.Plugins.plugins).filter(name => contentWords.includes(name.toLowerCase())); + const matchedPlugin = matchedPlugins.sort((a, b) => b.length - a.length)[0]; + const pluginData = matchedPlugin ? Vencord.Plugins.plugins[matchedPlugin] : null; + + const isEquicord = isEquicordGuild(message.channel_id) && isEquicordSupport(message.author.id); + const startsWithEnabled = msg.startsWith("enable"); + const startsWithDisabled = msg.startsWith("disable"); + + const shouldAddPluginButtons = pluginData && isEquicord && (startsWithEnabled || startsWithDisabled); + + if (shouldAddPluginButtons) { + if (pluginData.required || pluginData.name.endsWith("API")) return; + const isEnabled = Vencord.Plugins.isPluginEnabled(matchedPlugin); + + let label = `${matchedPlugin} is already ${isEnabled ? "enabled" : "disabled"}`; + let disabled = true; + + if ((startsWithDisabled && isEnabled) || (startsWithEnabled && !isEnabled)) { + label = `${isEnabled ? "Disable" : "Enable"} ${matchedPlugin}`; + disabled = false; + } + + pluginButtons.push( + + ); + } + + if (pluginButtons.length === 0) return null; + + return ( +
+ {pluginButtons} +
+ ); +}, { noop: true }); diff --git a/src/equicordplugins/equicordHelper/pluginCards.tsx b/src/equicordplugins/equicordHelper/pluginCards.tsx new file mode 100644 index 000000000..17af02327 --- /dev/null +++ b/src/equicordplugins/equicordHelper/pluginCards.tsx @@ -0,0 +1,155 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { useSettings } from "@api/Settings"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { WarningIcon } from "@components/Icons"; +import { AddonCard } from "@components/settings"; +import { ExcludedReasons, PluginDependencyList } from "@components/settings/tabs/plugins"; +import { PluginCard } from "@components/settings/tabs/plugins/PluginCard"; +import { EQUIBOT_USER_ID } from "@utils/constants"; +import { isEquicordGuild, isEquicordSupport } from "@utils/misc"; +import { Message } from "@vencord/discord-types"; +import { showToast, Tooltip, TooltipContainer } from "@webpack/common"; +import { JSX } from "react"; + +import plugins, { ExcludedPlugins } from "~plugins"; + + +export function ChatPluginCard({ url, description }: { url: string, description: string; }) { + const pluginNameFromUrl = new URL(url).pathname.split("/")[2]; + + const actualPluginName = Object.keys(plugins).find(name => + name.toLowerCase() === pluginNameFromUrl?.toLowerCase() + ); + + const pluginName = actualPluginName || pluginNameFromUrl; + + useSettings([`plugins.${pluginName ?? ""}.enabled`]); + + if (!pluginName) return null; + + const p = plugins[pluginName]; + const excludedPlugin = ExcludedPlugins[pluginName]; + + if (excludedPlugin || !p) { + const toolTipText = !p + ? `${pluginName} is only available on the ${ExcludedReasons[ExcludedPlugins[pluginName]]}` + : "This plugin is not on this version of Equicord. Try updating!"; + + const card = ( + { }} + disabled={true} + infoButton={} + /> + ); + + return description + ? {card} + : card; + } + + const onRestartNeeded = () => showToast("A restart is required for the change to take effect!"); + + const required = Vencord.Plugins.isPluginRequired(pluginName); + const dependents = Vencord.Plugins.calculatePluginDependencyMap()[p.name]?.filter(d => Vencord.Plugins.isPluginEnabled(d)); + + if (required) { + const tooltipText = p.required || !dependents.length + ? "This plugin is required for Equicord to function." + : ; + + return ( + + {({ onMouseLeave, onMouseEnter }) => + + } + + ); + } + + return ( + + ); +} + +export const PluginCards = ErrorBoundary.wrap(function PluginCards({ message }: { message: Message; }) { + const seenPlugins = new Set(); + const pluginCards: JSX.Element[] = []; + + // Process embeds + message.embeds?.forEach(embed => { + if (!embed.url?.startsWith("https://equicord.org/plugins/") && !embed.url?.startsWith("https://vencord.dev/plugins/")) return; + + const isEquicord = isEquicordGuild(message.channel_id) && isEquicordSupport(message.author.id); + if (!isEquicord) return; + + const pluginNameFromUrl = new URL(embed.url).pathname.split("/")[2]; + const actualPluginName = Object.keys(plugins).find(name => + name.toLowerCase() === pluginNameFromUrl?.toLowerCase() + ); + const pluginName = actualPluginName || pluginNameFromUrl; + + if (!pluginName || seenPlugins.has(pluginName)) return; + seenPlugins.add(pluginName); + + pluginCards.push( + + ); + }); + + // Process components + const components = (message.components?.[0] as any)?.components; + if (message.author.id === EQUIBOT_USER_ID && components?.length >= 4) { + const description = components[1]?.content; + const pluginUrl = components.find((c: any) => c?.components)?.components[0]?.url; + if (pluginUrl?.startsWith("https://equicord.org/plugins/") || pluginUrl?.startsWith("https://vencord.dev/plugins/")) { + const pluginNameFromUrl = new URL(pluginUrl).pathname.split("/")[2]; + const actualPluginName = Object.keys(plugins).find(name => + name.toLowerCase() === pluginNameFromUrl?.toLowerCase() + ); + const pluginName = actualPluginName || pluginNameFromUrl; + + if (pluginName && !seenPlugins.has(pluginName)) { + seenPlugins.add(pluginName); + pluginCards.push( + + ); + } + } + } + + if (pluginCards.length === 0) return null; + + return ( +
+ {pluginCards} +
+ ); +}, { noop: true }); diff --git a/src/plugins/index.ts b/src/plugins/index.ts index eb2ba73ce..7b111d630 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -28,6 +28,7 @@ import { addMessagePopoverButton, removeMessagePopoverButton } from "@api/Messag import { addNicknameIcon, removeNicknameIcon } from "@api/NicknameIcons"; import { Settings, SettingsStore } from "@api/Settings"; import { disableStyle, enableStyle } from "@api/Styles"; +import { makeLazy } from "@utils/lazy"; import { Logger } from "@utils/Logger"; import { canonicalizeFind, canonicalizeReplacement } from "@utils/patches"; import { Patch, Plugin, PluginDef, ReporterTestable, StartAt } from "@utils/types"; @@ -52,10 +53,36 @@ const subscribedFluxEventsPlugins = new Set(); const pluginsValues = Object.values(Plugins); const settings = Settings.plugins; +/** + * Whether a plugin is required (or dependency of another enabled plugin) + */ +export function isPluginRequired(name: string) { + const p = Plugins[name]; + if (!p) return false; + return p.required || p.isDependency; +} + +/** + * A map of plugin names to the plugins that depend on them + */ +export const calculatePluginDependencyMap = makeLazy(() => { + const dependencies: Record = {}; + for (const plugin in Plugins) { + const deps = Plugins[plugin].dependencies; + if (deps) { + for (const dep of deps) { + dependencies[dep] ??= []; + dependencies[dep].push(plugin); + } + } + } + + return dependencies; +}); + export function isPluginEnabled(p: string) { return ( - Plugins[p]?.required || - Plugins[p]?.isDependency || + isPluginRequired(p) || settings[p]?.enabled ) ?? false; } diff --git a/src/plugins/messageLogger/index.tsx b/src/plugins/messageLogger/index.tsx index 7805175b8..081337afc 100644 --- a/src/plugins/messageLogger/index.tsx +++ b/src/plugins/messageLogger/index.tsx @@ -14,7 +14,7 @@ import { updateMessage } from "@api/MessageUpdater"; import { definePluginSettings } from "@api/Settings"; import { disableStyle, enableStyle } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; -import { Devs, VC_SUPPORT_CATEGORY_ID, VENBOT_USER_ID } from "@utils/constants"; +import { Devs, EQUIBOT_USER_ID, SUPPORT_CHANNEL_ID, VC_SUPPORT_CATEGORY_ID, VENBOT_USER_ID } from "@utils/constants"; import { getIntlMessage } from "@utils/discord"; import { Logger } from "@utils/Logger"; import { classes } from "@utils/misc"; @@ -409,7 +409,8 @@ export default definePlugin({ (isEdit ? !logEdits : !logDeletes) || ignoreGuilds.includes(ChannelStore.getChannel(message.channel_id)?.guild_id) || // Ignore Venbot in the support channels (love you venbot!!!) - (message.author?.id === VENBOT_USER_ID && ChannelStore.getChannel(message.channel_id)?.parent_id === VC_SUPPORT_CATEGORY_ID)); + (message.author?.id === VENBOT_USER_ID && ChannelStore.getChannel(message.channel_id)?.parent_id === VC_SUPPORT_CATEGORY_ID) || + (message.author?.id === EQUIBOT_USER_ID && ChannelStore.getChannel(message.channel_id)?.id === SUPPORT_CHANNEL_ID)); }, EditMarker({ message, className, children, ...props }: any) { diff --git a/src/utils/constants.ts b/src/utils/constants.ts index b2e3e5544..629333e68 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -27,6 +27,7 @@ export const EQUICORD_TEAM = "1173520023239786538"; export const EQUICORD_HELPERS = "1326406112144265257"; export const EQUIBOP_CONTRIB_ROLE_ID = "1287079931645263968"; export const VENCORD_CONTRIB_ROLE_ID = "1173343399470964856"; +export const EQUIBOT_USER_ID = "1243063117852835941"; // Vencord export const VC_SUPPORT_CHANNEL_ID = "1026515880080842772"; From 262336f43643f4b4de6d1b0591b774e164f276da Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Fri, 26 Sep 2025 01:32:01 -0400 Subject: [PATCH 14/16] Quoter: Fix & Shrink + Remove Old Finds --- .../settings/tabs/plugins/index.tsx | 3 +- src/equicordplugins/orbolayBridge/index.tsx | 11 - src/equicordplugins/questify/index.tsx | 39 +-- src/equicordplugins/questify/settings.tsx | 2 - src/equicordplugins/quoter/index.tsx | 256 ++++++++---------- src/equicordplugins/quoter/utils.tsx | 87 ++---- src/equicordplugins/randomVoice/index.tsx | 2 +- src/equicordplugins/replyPingControl/index.ts | 2 +- src/equicordplugins/statsfmPresence/index.tsx | 3 - src/equicordplugins/tosuRPC/index.ts | 1 - src/plugins/lastfmRichPresence/index.tsx | 3 - src/plugins/messageLogger/index.tsx | 2 +- src/plugins/messageTags/index.ts | 1 - src/plugins/noBlockedMessages/index.ts | 2 +- src/plugins/pinDms/data.ts | 5 - .../components/HiddenChannelLockScreen.tsx | 1 - src/plugins/showMeYourName/index.tsx | 10 +- src/plugins/silentTyping/index.tsx | 22 +- src/plugins/userVoiceShow/components.tsx | 6 +- src/plugins/whoReacted/index.tsx | 2 - src/plugins/xsOverlay/index.tsx | 1 - 21 files changed, 161 insertions(+), 300 deletions(-) diff --git a/src/components/settings/tabs/plugins/index.tsx b/src/components/settings/tabs/plugins/index.tsx index 20a747505..02c152034 100644 --- a/src/components/settings/tabs/plugins/index.tsx +++ b/src/components/settings/tabs/plugins/index.tsx @@ -40,13 +40,12 @@ import { openWarningModal } from "./PluginModal"; import { StockPluginsCard, UserPluginsCard } from "./PluginStatCards"; // Avoid circular dependency -const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() => require("plugins")); +const { stopPlugin } = proxyLazy(() => require("plugins")); export const cl = classNameFactory("vc-plugins-"); export const logger = new Logger("PluginSettings", "#a6d189"); const InputStyles = findByPropsLazy("inputWrapper", "inputError", "error"); -const ButtonClasses = findByPropsLazy("button", "disabled", "enabled"); function showErrorToast(message: string) { Toasts.show({ diff --git a/src/equicordplugins/orbolayBridge/index.tsx b/src/equicordplugins/orbolayBridge/index.tsx index e94b6e95b..e2aaebb29 100644 --- a/src/equicordplugins/orbolayBridge/index.tsx +++ b/src/equicordplugins/orbolayBridge/index.tsx @@ -22,17 +22,6 @@ import definePlugin, { OptionType } from "@utils/types"; import { VoiceState } from "@vencord/discord-types"; import { ChannelStore, FluxDispatcher, GuildMemberStore, Toasts, UserStore, VoiceStateStore } from "@webpack/common"; -interface ChannelState { - userId: string; - channelId: string; - deaf: boolean; - mute: boolean; - stream: boolean; - selfDeaf: boolean; - selfMute: boolean; - selfStream: boolean; -} - const settings = definePluginSettings({ port: { type: OptionType.NUMBER, diff --git a/src/equicordplugins/questify/index.tsx b/src/equicordplugins/questify/index.tsx index ae0fe1cbf..5d7402e2f 100644 --- a/src/equicordplugins/questify/index.tsx +++ b/src/equicordplugins/questify/index.tsx @@ -178,12 +178,10 @@ function shouldHideBadgeOnUserProfiles(): boolean { function shouldHideQuestPopup(quest: Quest | null): boolean { const { disableQuestsPopupAboveAccountPanel, - disableQuestsEverything, - triggerQuestsRerender + disableQuestsEverything } = settings.use([ "disableQuestsPopupAboveAccountPanel", - "disableQuestsEverything", - "triggerQuestsRerender" + "disableQuestsEverything" ]); const noProgress = !quest?.userStatus?.progress || Object.keys(quest?.userStatus?.progress || {}).length === 0; @@ -251,16 +249,12 @@ function QuestTileContextMenu(children: React.ReactNode[], props: { quest: any; export function getQuestTileClasses(originalClasses: string, quest: Quest, color: number | null | undefined, gradient: string | undefined): string { const { - ignoredQuestIDs, - ignoredQuestProfile, restyleQuestsUnclaimed, restyleQuestsClaimed, restyleQuestsIgnored, restyleQuestsExpired, restyleQuestsGradient } = settings.use([ - "ignoredQuestIDs", - "ignoredQuestProfile", "restyleQuestsUnclaimed", "restyleQuestsClaimed", "restyleQuestsIgnored", @@ -326,7 +320,7 @@ export function getQuestTileClasses(originalClasses: string, quest: Quest, color } function makeDesktopCompatible(quests: Quest[]): void { - const { makeMobileQuestsDesktopCompatible, triggerQuestsRerender } = settings.use(["makeMobileQuestsDesktopCompatible", "triggerQuestsRerender"]); + const { makeMobileQuestsDesktopCompatible } = settings.use(["makeMobileQuestsDesktopCompatible", "triggerQuestsRerender"]); if (makeMobileQuestsDesktopCompatible) { quests.forEach(quest => { @@ -358,27 +352,17 @@ function makeDesktopCompatible(quests: Quest[]): void { function sortQuests(quests: Quest[], skip?: boolean): Quest[] { const { - ignoredQuestIDs, - ignoredQuestProfile, reorderQuests, unclaimedSubsort, claimedSubsort, ignoredSubsort, - expiredSubsort, - completeVideoQuestsInBackground, - completeGameQuestsInBackground, - triggerQuestsRerender + expiredSubsort } = settings.use([ - "ignoredQuestIDs", - "ignoredQuestProfile", "reorderQuests", "unclaimedSubsort", "claimedSubsort", "ignoredSubsort", - "expiredSubsort", - "completeVideoQuestsInBackground", - "completeGameQuestsInBackground", - "triggerQuestsRerender" + "expiredSubsort" ]); makeDesktopCompatible(quests); @@ -468,16 +452,6 @@ function sortQuests(quests: Quest[], skip?: boolean): Quest[] { } export function getQuestTileStyle(quest: Quest | null): Record { - const { - restyleQuests, - ignoredQuestIDs, - ignoredQuestProfile - } = settings.use([ - "restyleQuests", - "ignoredQuestIDs", - "ignoredQuestProfile" - ]); - const style: Record = {}; let themeColor: RGB | null = null; @@ -801,7 +775,6 @@ function disguiseHomeButton(location: string): boolean { } function useQuestRerender(): number { - const { triggerQuestsRerender } = settings.use(["triggerQuestsRerender"]); const [renderTrigger, setRenderTrigger] = useState(0); useEffect(() => addRerenderCallback(() => setRenderTrigger(prev => prev + 1)), []); return renderTrigger; @@ -818,12 +791,10 @@ function getLastFilterChoices(): { group: string; filter: string; }[] | null { } function setLastSortChoice(sort: string): void { - const { rememberQuestPageFilters } = settings.use(["rememberQuestPageFilters"]); settings.store.lastQuestPageSort = sort; } function setLastFilterChoices(filters: { group: string; filter: string; }[]): void { - const { rememberQuestPageFilters } = settings.use(["rememberQuestPageFilters"]); if (!filters || !Object.keys(filters).length || !Object.values(filters).every(f => f.group && f.filter)) { return; } settings.store.lastQuestPageFilters = JSON.parse(JSON.stringify(filters)).reduce((acc, item) => ({ ...acc, [item.filter]: item }), {}); } diff --git a/src/equicordplugins/questify/settings.tsx b/src/equicordplugins/questify/settings.tsx index c6c4321a1..bbc01ee62 100644 --- a/src/equicordplugins/questify/settings.tsx +++ b/src/equicordplugins/questify/settings.tsx @@ -364,7 +364,6 @@ function validateQuestButtonSetting() { function validateDisableQuestSetting() { const { - disableQuestsEverything, disableQuestsDiscoveryTab, disableQuestsFetchingQuests, disableQuestsPopupAboveAccountPanel, @@ -372,7 +371,6 @@ function validateDisableQuestSetting() { disableQuestsGiftInventoryRelocationNotice, disableFriendsListActiveNowPromotion } = settings.use([ - "disableQuestsEverything", "disableQuestsDiscoveryTab", "disableQuestsFetchingQuests", "disableQuestsPopupAboveAccountPanel", diff --git a/src/equicordplugins/quoter/index.tsx b/src/equicordplugins/quoter/index.tsx index 9e2feeba3..d82b9d04d 100644 --- a/src/equicordplugins/quoter/index.tsx +++ b/src/equicordplugins/quoter/index.tsx @@ -1,10 +1,10 @@ /* * 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 */ -import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; +import { findGroupChildrenByChildId } from "@api/ContextMenu"; import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import { getCurrentChannel } from "@utils/discord"; @@ -20,44 +20,28 @@ enum ImageStyle { inspirational } -const messagePatch: NavContextMenuPatchCallback = (children, { message }) => { - recentmessage = message; - if (!message.content) return; - - const buttonElement = - { - openModal(props => ); - }} - />; - - const group = findGroupChildrenByChildId("copy-text", children); - if (!group) { - children.push(buttonElement); - return; - } - - group.splice( - group.findIndex(c => c?.props?.id === "copy-text") + 1, 0, buttonElement - ); -}; - -let recentmessage: Message; -let grayscale; -let setStyle: ImageStyle = ImageStyle.inspirational; -let customMessage: string = ""; - enum userIDOptions { displayName, userName, userId } + +interface QuoteImageOptions { + avatarUrl: string; + quoteOld: string; + grayScale: boolean; + imageStyle: ImageStyle; + author: { + username: string; + globalName?: string; + id: string; + }; + userIdentifier: userIDOptions; +} + + const settings = definePluginSettings({ - userIdentifier: - { + userIdentifier: { type: OptionType.SELECT, description: "What the author's name should be displayed as", options: [ @@ -71,14 +55,28 @@ const settings = definePluginSettings({ export default definePlugin({ name: "Quoter", description: "Adds the ability to create an inspirational quote image from a message", - authors: [Devs.Samwich], + authors: [Devs.Samwich, Devs.thororen], + settings, contextMenus: { - "message": messagePatch - }, - settings + "message": (children, { message }) => { + if (!message.content) return; + const buttonElement = ( + openModal(props => )} + /> + ); + + const group = findGroupChildrenByChildId("copy-text", children); + if (!group) children.push(buttonElement); + else group.splice(group.findIndex(c => c?.props?.id === "copy-text") + 1, 0, buttonElement); + } + } }); -function sizeUpgrade(url) { +function sizeUpgrade(url: string) { const u = new URL(url); u.searchParams.set("size", "512"); return u.toString(); @@ -87,68 +85,49 @@ function sizeUpgrade(url) { const preparingSentence: string[] = []; const lines: string[] = []; -async function createQuoteImage(avatarUrl: string, quoteOld: string, grayScale: boolean): Promise { +async function createQuoteImage(options: QuoteImageOptions): Promise { + const { avatarUrl, quoteOld, grayScale, imageStyle, author, userIdentifier } = options; + const quote = FixUpQuote(quoteOld); const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); + if (!ctx) throw new Error("Cant get 2d rendering context :("); - if (!ctx) { - throw new Error("Cant get 2d rendering context :("); - } - - let name: string = ""; - - switch (settings.store.userIdentifier) { + let name = ""; + switch (userIdentifier) { case userIDOptions.displayName: - // @ts-ignore - const meow = recentmessage.author.globalName; - if (meow) { - name = meow; - } - else { - name = recentmessage.author.username; - } + name = author.globalName || author.username; break; case userIDOptions.userName: - name = recentmessage.author.username; + name = author.username; break; case userIDOptions.userId: - name = recentmessage.author.id; + name = author.id; break; default: - name = "MAN WTF HAPPENED"; - break; + name = "Unknown"; } - switch (setStyle) { + switch (imageStyle) { case ImageStyle.inspirational: - const cardWidth = 1200; const cardHeight = 600; - canvas.width = cardWidth; canvas.height = cardHeight; ctx.fillStyle = "#000"; - ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.fillRect(0, 0, cardWidth, cardHeight); const avatarBlob = await fetchImageAsBlob(avatarUrl); - const fadeBlob = await fetchImageAsBlob("https://raw.githubusercontent.com/Equicord/Equibored/refs/heads/main/icons/quoter/quoter.png"); + const fadeBlob = await fetchImageAsBlob("https://raw.githubusercontent.com/Equicord/Equibored/main/icons/quoter/quoter.png"); const avatar = new Image(); const fade = new Image(); - const avatarPromise = new Promise(resolve => { - avatar.onload = () => resolve(); - avatar.src = URL.createObjectURL(avatarBlob); - }); - - const fadePromise = new Promise(resolve => { - fade.onload = () => resolve(); - fade.src = URL.createObjectURL(fadeBlob); - }); - - await Promise.all([avatarPromise, fadePromise]); + await Promise.all([ + new Promise(resolve => { avatar.onload = () => resolve(); avatar.src = URL.createObjectURL(avatarBlob); }), + new Promise(resolve => { fade.onload = () => resolve(); fade.src = URL.createObjectURL(fadeBlob); }) + ]); ctx.drawImage(avatar, 0, 0, cardHeight, cardHeight); @@ -164,42 +143,69 @@ async function createQuoteImage(avatarUrl: string, quoteOld: string, grayScale: ctx.fillStyle = "#fff"; ctx.font = "italic 20px Georgia"; const quoteWidth = cardWidth / 2 - 50; - const quoteX = ((cardWidth - cardHeight)); + const quoteX = cardWidth - cardHeight; const quoteY = cardHeight / 2 - 10; wrapText(ctx, `"${quote}"`, quoteX, quoteY, quoteWidth, 20, preparingSentence, lines); const wrappedTextHeight = lines.length * 25; ctx.font = "bold 16px Georgia"; - const authorNameX = (cardHeight * 1.5) - (ctx.measureText(`- ${name}`).width / 2) - 30; + const authorNameX = cardHeight * 1.5 - ctx.measureText(`- ${name}`).width / 2 - 30; const authorNameY = quoteY + wrappedTextHeight + 30; - ctx.fillText(`- ${name}`, authorNameX, authorNameY); + preparingSentence.length = 0; lines.length = 0; + return await canvasToBlob(canvas); } } -function registerStyleChange(style) { - setStyle = style; - GeneratePreview(); +function generateFileNamePreview(message: string) { + const words = message.split(" "); + return words.length >= 6 ? words.slice(0, 6).join(" ") : words.join(" "); } -function QuoteModal(props: ModalProps) { +function QuoteModal({ message, ...props }: ModalProps & { message: Message; }) { const [gray, setGray] = useState(true); - useEffect(() => { - grayscale = gray; - GeneratePreview(); - }, [gray]); + const [style, setStyle] = useState(ImageStyle.inspirational); + const [quoteImage, setQuoteImage] = useState(null); + const { userIdentifier } = settings.store; + const safeContent = message.content ? message.content : ""; - const safeContent = recentmessage && recentmessage.content ? recentmessage.content : ""; + const generateImage = async () => { + const image = await createQuoteImage({ + avatarUrl: sizeUpgrade(message.author.getAvatarURL()), + quoteOld: safeContent, + grayScale: gray, + imageStyle: style, + author: message.author, + userIdentifier + }); + setQuoteImage(image); + document.getElementById("quoterPreview")?.setAttribute("src", URL.createObjectURL(image)); + }; - const [custom, setCustom] = useState(safeContent); - useEffect(() => { - customMessage = custom; - GeneratePreview(); - }, [custom]); + useEffect(() => { generateImage(); }, [gray, style, safeContent]); + + const Export = () => { + if (!quoteImage) return; + const link = document.createElement("a"); + const preview = generateFileNamePreview(safeContent); + link.href = URL.createObjectURL(quoteImage); + link.download = `${preview} - ${message.author.username}.png`; + link.click(); + link.remove(); + }; + + const SendInChat = () => { + if (!quoteImage) return; + const preview = generateFileNamePreview(safeContent); + const file = new File([quoteImage], `${preview} - ${message.author.username}.png`, { type: "image/png" }); + // @ts-expect-error typing issue + UploadHandler.promptToUpload([file], getCurrentChannel(), 0); + props.onClose?.(); + }; return ( @@ -210,59 +216,27 @@ function QuoteModal(props: ModalProps) { - -



+ +

+

Grayscale - +