From 452b7a50a017ed7eced8699ecccec23865aad009 Mon Sep 17 00:00:00 2001 From: prism Date: Sun, 16 Nov 2025 03:26:39 +0100 Subject: [PATCH] minor changes in plugins (#502) * BetterAudioPlayer: fix observer cleanup * TosuRPC: fix websocket reconnect * Jumpscare: fix flux subscription leak * Animalese: fix infinite recursion * GensokyoRadioRPC: fix interval cleanup * JellyfinRichPresence: fix interval cleanup * ListenBrainzRPC: fix interval cleanup * AbsRPC: fix interval cleanup * StatsfmPresence: fix interval cleanup * AppleMusic: fix interval cleanup * LastfmRichPresence: fix interval cleanup * fix: remove pointless null checks * CleanUserArea renamed to BetterUserArea and added additional features * BetterUserArea: migrate from CleanUserArea --- src/equicordplugins/absRPC/index.tsx | 6 +- src/equicordplugins/animalese/index.ts | 15 +-- .../betterAudioPlayer/index.tsx | 9 +- src/equicordplugins/betterUserArea/index.ts | 107 ++++++++++++++++++ src/equicordplugins/cleanUserArea/index.ts | 23 ---- .../gensokyoRadioRPC/index.tsx | 7 +- .../jellyfinRichPresence/index.tsx | 14 ++- src/equicordplugins/jumpscare/index.tsx | 8 +- src/equicordplugins/listenBrainzRPC/index.tsx | 7 +- src/equicordplugins/statsfmPresence/index.tsx | 7 +- src/equicordplugins/tosuRPC/index.ts | 12 +- src/plugins/appleMusic.desktop/index.tsx | 7 +- src/plugins/lastfmRichPresence/index.tsx | 7 +- 13 files changed, 170 insertions(+), 59 deletions(-) create mode 100644 src/equicordplugins/betterUserArea/index.ts delete mode 100644 src/equicordplugins/cleanUserArea/index.ts diff --git a/src/equicordplugins/absRPC/index.tsx b/src/equicordplugins/absRPC/index.tsx index 3032e860e..fcf29d36f 100644 --- a/src/equicordplugins/absRPC/index.tsx +++ b/src/equicordplugins/absRPC/index.tsx @@ -72,6 +72,7 @@ const applicationId = "1381423044907503636"; const logger = new Logger("AudioBookShelfRichPresence"); let authToken: string | null = null; +let updateInterval: NodeJS.Timeout | undefined; async function getApplicationAsset(key: string): Promise { return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0]; @@ -105,11 +106,12 @@ export default definePlugin({ start() { this.updatePresence(); - this.updateInterval = setInterval(() => { this.updatePresence(); }, 10000); + updateInterval = setInterval(() => { this.updatePresence(); }, 10000); }, stop() { - clearInterval(this.updateInterval); + clearInterval(updateInterval); + updateInterval = undefined; }, async authenticate(): Promise { diff --git a/src/equicordplugins/animalese/index.ts b/src/equicordplugins/animalese/index.ts index 265c2e0d6..1f8ccb568 100644 --- a/src/equicordplugins/animalese/index.ts +++ b/src/equicordplugins/animalese/index.ts @@ -222,16 +222,11 @@ export default definePlugin({ } }, - start() { - // Only subscribe once! - const init = async () => { - if (!audioContext) { - audioContext = new AudioContext(); - await initSoundBuffers(); - } - init(); - }; - init(); + async start() { + if (!audioContext) { + audioContext = new AudioContext(); + await initSoundBuffers(); + } }, stop() { diff --git a/src/equicordplugins/betterAudioPlayer/index.tsx b/src/equicordplugins/betterAudioPlayer/index.tsx index 6264db1f7..d23c3f094 100644 --- a/src/equicordplugins/betterAudioPlayer/index.tsx +++ b/src/equicordplugins/betterAudioPlayer/index.tsx @@ -222,6 +222,7 @@ function createObserver(targetNode: HTMLElement) { childList: true, subtree: true, }); + return observer; } function tryHexToRgb(hex) { @@ -292,6 +293,8 @@ const settings = definePluginSettings({ }, }); +let observer: MutationObserver | null = null; + export default definePlugin({ name: "BetterAudioPlayer", description: "Adds a spectrograph and oscilloscope visualizer to audio attachment players", @@ -302,11 +305,15 @@ export default definePlugin({ const targetNode = document.querySelector("[class^='content_']"); if (targetNode) { scanForAudioElements(targetNode as HTMLElement); - createObserver(targetNode as HTMLElement); + observer = createObserver(targetNode as HTMLElement); } else { requestAnimationFrame(waitForContent); } }; waitForContent(); }, + stop() { + observer?.disconnect(); + observer = null; + }, }); diff --git a/src/equicordplugins/betterUserArea/index.ts b/src/equicordplugins/betterUserArea/index.ts new file mode 100644 index 000000000..2e1261e59 --- /dev/null +++ b/src/equicordplugins/betterUserArea/index.ts @@ -0,0 +1,107 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { definePluginSettings, migratePluginToSetting } from "@api/Settings"; +import { EquicordDevs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; + +const settings = definePluginSettings({ + removeNameplate: { + type: OptionType.BOOLEAN, + description: "Remove nameplate styling.", + default: true, + restartNeeded: true + }, + removeAudioMenus: { + type: OptionType.BOOLEAN, + description: "Remove menus next to mute and deafen buttons.", + default: true, + restartNeeded: true + }, + alwaysShowUsername: { + type: OptionType.BOOLEAN, + description: "Always show username instead of status.", + default: true, + restartNeeded: true + }, + removeButtonTooltips: { + type: OptionType.BOOLEAN, + description: "Remove button tooltips.", + default: false, + restartNeeded: true + }, + removeAvatarDecoration: { + type: OptionType.BOOLEAN, + description: "Remove avatar decoration.", + default: true, + restartNeeded: true + }, + removeUsernameStyles: { + type: OptionType.BOOLEAN, + description: "Remove username colors and effects.", + default: true, + restartNeeded: true + } +}); + +migratePluginToSetting("BetterUserArea", "CleanUserArea", "removeNameplate"); + +export default definePlugin({ + name: "BetterUserArea", + description: "Customize and make the user area more clean.", + authors: [EquicordDevs.Prism], + settings, + patches: [ + { + find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}", + replacement: { + match: /,nameplate:\i,selectedGuildId:(\i),avatarDecoration/, + replace: ",nameplate:null,selectedGuildId:$1,avatarDecoration" + }, + predicate: () => settings.store.removeNameplate + }, + { + find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}", + replacement: { + match: /\{showRefreshedAudioContextMenu:(\i)\}=(?=.+?location:"\w+Button")/g, + replace: "{showRefreshedAudioContextMenu:$1}=$self.settings.store.removeAudioMenus?{showRefreshedAudioContextMenu:!1}:" + }, + predicate: () => settings.store.removeAudioMenus + }, + { + find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}", + replacement: { + match: /hoverText:(\i),forceHover:\i,children:/g, + replace: "hoverText:$1,forceHover:!0,children:" + }, + predicate: () => settings.store.alwaysShowUsername + }, + { + find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}", + replacement: { + match: /tooltipText:\i,/g, + replace: "tooltipText:void 0," + }, + predicate: () => settings.store.removeButtonTooltips + }, + { + find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}", + replacement: { + match: /avatarDecoration:(\i),size:/, + replace: "avatarDecoration:void 0,size:" + }, + predicate: () => settings.store.removeAvatarDecoration + }, + { + find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}", + replacement: { + match: /displayNameStyles:(\i),/, + replace: "displayNameStyles:void 0," + }, + predicate: () => settings.store.removeUsernameStyles + } + ], +}); diff --git a/src/equicordplugins/cleanUserArea/index.ts b/src/equicordplugins/cleanUserArea/index.ts deleted file mode 100644 index ac5b29ce7..000000000 --- a/src/equicordplugins/cleanUserArea/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2025 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { EquicordDevs } from "@utils/constants"; -import definePlugin from "@utils/types"; - -export default definePlugin({ - name: "CleanUserArea", - description: "Removes the nameplate styling from the user area.", - authors: [EquicordDevs.Prism], - patches: [ - { - find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}", - replacement: { - match: /,nameplate:\i,selectedGuildId:(\i),avatarDecoration/, - replace: ",nameplate:null,selectedGuildId:$1,avatarDecoration", - }, - }, - ], -}); diff --git a/src/equicordplugins/gensokyoRadioRPC/index.tsx b/src/equicordplugins/gensokyoRadioRPC/index.tsx index b1bc2d7f0..20a33269f 100644 --- a/src/equicordplugins/gensokyoRadioRPC/index.tsx +++ b/src/equicordplugins/gensokyoRadioRPC/index.tsx @@ -39,6 +39,8 @@ const settings = definePluginSettings({ } }); +let updateInterval: NodeJS.Timeout | undefined; + export default definePlugin({ name: "GensokyoRadioRPC", description: "Discord rich presence for Gensokyo Radio!", @@ -57,11 +59,12 @@ export default definePlugin({ start() { this.updatePresence(); - this.updateInterval = setInterval(() => { this.updatePresence(); }, settings.store.refreshInterval * 1000); + updateInterval = setInterval(() => { this.updatePresence(); }, settings.store.refreshInterval * 1000); }, stop() { - clearInterval(this.updateInterval); + clearInterval(updateInterval); + updateInterval = undefined; FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", activity: null }); }, diff --git a/src/equicordplugins/jellyfinRichPresence/index.tsx b/src/equicordplugins/jellyfinRichPresence/index.tsx index f3448c0c7..7e07a6388 100644 --- a/src/equicordplugins/jellyfinRichPresence/index.tsx +++ b/src/equicordplugins/jellyfinRichPresence/index.tsx @@ -155,6 +155,8 @@ const applicationId = "1381368130164625469"; const logger = new Logger("JellyfinRichPresence"); +let updateInterval: NodeJS.Timeout | undefined; + async function getApplicationAsset(key: string): Promise { return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0]; } @@ -200,11 +202,12 @@ export default definePlugin({ start() { this.updatePresence(); - this.updateInterval = setInterval(() => { this.updatePresence(); }, 10000); + updateInterval = setInterval(() => { this.updatePresence(); }, 10000); }, stop() { - clearInterval(this.updateInterval); + clearInterval(updateInterval); + updateInterval = undefined; }, async fetchMediaData(): Promise { @@ -236,12 +239,11 @@ export default definePlugin({ if (playState?.IsPaused && !settings.store.showPausedState) return null; const imageUrl = item.ImageTags?.Primary - ? `${baseUrl}/Items/${ - item.Type === "Episode" && + ? `${baseUrl}/Items/${item.Type === "Episode" && item.SeriesId && settings.store.coverType === "series" - ? item.SeriesId - : item.Id + ? item.SeriesId + : item.Id }/Images/Primary` : undefined; diff --git a/src/equicordplugins/jumpscare/index.tsx b/src/equicordplugins/jumpscare/index.tsx index 3357ecb97..5a8e4d531 100644 --- a/src/equicordplugins/jumpscare/index.tsx +++ b/src/equicordplugins/jumpscare/index.tsx @@ -10,7 +10,7 @@ import { createAudioPlayer } from "@api/AudioPlayer"; import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { createRoot, FluxDispatcher, useEffect, useState } from "@webpack/common"; +import { createRoot, FluxDispatcher, useCallback, useEffect, useState } from "@webpack/common"; import { Root } from "react-dom/client"; let jumpscareRoot: Root | undefined; @@ -67,7 +67,7 @@ export default definePlugin({ const [isPlaying, setIsPlaying] = useState(false); const jumpscareAudio = createAudioPlayer(settings.store.audioSource, { volume: 100, onEnded: () => { setIsPlaying(false); } }); - const jumpscare = event => { + const jumpscare = useCallback(event => { if (isPlaying) return; const chance = 1 / settings.store.chance; @@ -75,7 +75,7 @@ export default definePlugin({ setIsPlaying(true); jumpscareAudio.play(); - }; + }, [isPlaying]); useEffect(() => { FluxDispatcher.subscribe("CHANNEL_SELECT", jumpscare); @@ -83,7 +83,7 @@ export default definePlugin({ return () => { FluxDispatcher.unsubscribe("CHANNEL_SELECT", jumpscare); }; - }); + }, [jumpscare]); return ; } diff --git a/src/equicordplugins/listenBrainzRPC/index.tsx b/src/equicordplugins/listenBrainzRPC/index.tsx index 77f4d7bec..b69230856 100644 --- a/src/equicordplugins/listenBrainzRPC/index.tsx +++ b/src/equicordplugins/listenBrainzRPC/index.tsx @@ -42,6 +42,8 @@ const logger = new Logger("ListenBrainzRPC"); const PresenceStore = findByPropsLazy("getLocalPresence"); +let updateInterval: NodeJS.Timeout | undefined; + async function getApplicationAsset(key: string): Promise { return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0]; } @@ -181,13 +183,14 @@ export default definePlugin({ start() { this.updatePresence(); - this.updateInterval = setInterval(() => { + updateInterval = setInterval(() => { this.updatePresence(); }, 16000); }, stop() { - clearInterval(this.updateInterval); + clearInterval(updateInterval); + updateInterval = undefined; }, async fetchTrackData(): Promise { diff --git a/src/equicordplugins/statsfmPresence/index.tsx b/src/equicordplugins/statsfmPresence/index.tsx index df5377b7d..2d6ec4b4b 100644 --- a/src/equicordplugins/statsfmPresence/index.tsx +++ b/src/equicordplugins/statsfmPresence/index.tsx @@ -114,6 +114,8 @@ const logger = new Logger("StatsfmPresence"); const PresenceStore = findByPropsLazy("getLocalPresence"); +let updateInterval: NodeJS.Timeout | undefined; + async function getApplicationAsset(key: string): Promise { return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0]; } @@ -237,11 +239,12 @@ export default definePlugin({ start() { this.updatePresence(); - this.updateInterval = setInterval(() => { this.updatePresence(); }, 16000); + updateInterval = setInterval(() => { this.updatePresence(); }, 16000); }, stop() { - clearInterval(this.updateInterval); + clearInterval(updateInterval); + updateInterval = undefined; }, async fetchTrackData(): Promise { diff --git a/src/equicordplugins/tosuRPC/index.ts b/src/equicordplugins/tosuRPC/index.ts index 9e2a9faef..32f282372 100644 --- a/src/equicordplugins/tosuRPC/index.ts +++ b/src/equicordplugins/tosuRPC/index.ts @@ -24,22 +24,28 @@ const throttledOnMessage = throttle(onMessage, 3000, () => FluxDispatcher.dispat let ws: WebSocket; let wsReconnect: NodeJS.Timeout; +let shouldReconnect = false; export default definePlugin({ name: "TosuRPC", description: "osu! RPC with data from tosu", authors: [Devs.AutumnVN], start() { + shouldReconnect = true; (function connect() { 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("close", () => { + if (!shouldReconnect) return; + wsReconnect = setTimeout(connect, 5000); + }); ws.addEventListener("message", ({ data }) => throttledOnMessage(data)); })(); }, stop() { - ws.close(); - clearTimeout(wsReconnect); + shouldReconnect = false; + if (wsReconnect) clearTimeout(wsReconnect); + if (ws) ws.close(); FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", activity: null, socketId: "tosu" }); } }); diff --git a/src/plugins/appleMusic.desktop/index.tsx b/src/plugins/appleMusic.desktop/index.tsx index b2e0a125e..e265f4efc 100644 --- a/src/plugins/appleMusic.desktop/index.tsx +++ b/src/plugins/appleMusic.desktop/index.tsx @@ -37,6 +37,8 @@ const enum AssetImageType { const applicationId = "1239490006054207550"; +let updateInterval: NodeJS.Timeout | undefined; + function setActivity(activity: Activity | null) { FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", @@ -172,11 +174,12 @@ export default definePlugin({ start() { this.updatePresence(); - this.updateInterval = setInterval(() => { this.updatePresence(); }, settings.store.refreshInterval * 1000); + updateInterval = setInterval(() => { this.updatePresence(); }, settings.store.refreshInterval * 1000); }, stop() { - clearInterval(this.updateInterval); + clearInterval(updateInterval); + updateInterval = undefined; FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", activity: null }); }, diff --git a/src/plugins/lastfmRichPresence/index.tsx b/src/plugins/lastfmRichPresence/index.tsx index fe863a4f8..412195bb0 100644 --- a/src/plugins/lastfmRichPresence/index.tsx +++ b/src/plugins/lastfmRichPresence/index.tsx @@ -48,6 +48,8 @@ const LASTFM_PLACEHOLDER_IMAGE_HASH = "2a96cbd8b46e442fc41c2b86b821562f"; const logger = new Logger("LastFMRichPresence"); +let updateInterval: NodeJS.Timeout | undefined; + async function getApplicationAsset(key: string): Promise { return (await ApplicationAssetUtils.fetchAssetIds(DISCORD_APP_ID, [key]))[0]; } @@ -185,11 +187,12 @@ export default definePlugin({ start() { this.updatePresence(); - this.updateInterval = setInterval(() => { this.updatePresence(); }, 16000); + updateInterval = setInterval(() => { this.updatePresence(); }, 16000); }, stop() { - clearInterval(this.updateInterval); + clearInterval(updateInterval); + updateInterval = undefined; }, async fetchTrackData(): Promise {