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
This commit is contained in:
prism 2025-11-16 03:26:39 +01:00 • committed by GitHub
parent 3bab98c7db
commit 452b7a50a0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 170 additions and 59 deletions

View file

@ -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<string> {
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<boolean> {

View file

@ -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() {

View file

@ -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;
},
});

View file

@ -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
}
],
});

View file

@ -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",
},
},
],
});

View file

@ -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 });
},

View file

@ -155,6 +155,8 @@ const applicationId = "1381368130164625469";
const logger = new Logger("JellyfinRichPresence");
let updateInterval: NodeJS.Timeout | undefined;
async function getApplicationAsset(key: string): Promise<string> {
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<MediaData | null> {
@ -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;

View file

@ -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 <img className={`jumpscare-img ${isPlaying ? "jumpscare-animate" : ""}`} src={settings.store.imageSource} />;
}

View file

@ -42,6 +42,8 @@ const logger = new Logger("ListenBrainzRPC");
const PresenceStore = findByPropsLazy("getLocalPresence");
let updateInterval: NodeJS.Timeout | undefined;
async function getApplicationAsset(key: string): Promise<string> {
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<TrackData | null> {

View file

@ -114,6 +114,8 @@ const logger = new Logger("StatsfmPresence");
const PresenceStore = findByPropsLazy("getLocalPresence");
let updateInterval: NodeJS.Timeout | undefined;
async function getApplicationAsset(key: string): Promise<string> {
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<TrackData | null> {

View file

@ -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" });
}
});

View file

@ -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 });
},

View file

@ -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<string> {
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<TrackData | null> {