diff --git a/src/equicordplugins/richPresence/SettingsPanel.tsx b/src/equicordplugins/richPresence/SettingsPanel.tsx index 40fefc4db..15793f914 100644 --- a/src/equicordplugins/richPresence/SettingsPanel.tsx +++ b/src/equicordplugins/richPresence/SettingsPanel.tsx @@ -141,29 +141,6 @@ function JellyfinSettings() { ); } -function ListenBrainzSettings() { - return ( - <> - - - - - - - - - - - - - - - ); -} - function GensokyoRadioSettings() { const [value, setValue] = useState(settings.store.gr_refreshInterval ?? 15); return ( @@ -239,7 +216,6 @@ const TAB_COMPONENTS: Record = { [ServiceTab.Tosu]: TosuSettings, [ServiceTab.StatsFm]: StatsFmSettings, [ServiceTab.Jellyfin]: JellyfinSettings, - [ServiceTab.ListenBrainz]: ListenBrainzSettings, [ServiceTab.GensokyoRadio]: GensokyoRadioSettings, [ServiceTab.Navidrome]: NavidromeSettings, }; @@ -249,7 +225,6 @@ const TAB_LABELS: Record = { [ServiceTab.Tosu]: "osu!", [ServiceTab.StatsFm]: "stats.fm", [ServiceTab.Jellyfin]: "Jellyfin", - [ServiceTab.ListenBrainz]: "ListenBrainz", [ServiceTab.GensokyoRadio]: "Gensokyo Radio", [ServiceTab.Navidrome]: "Navidrome", }; @@ -259,7 +234,6 @@ const ENABLE_KEYS: Record = { [ServiceTab.Tosu]: "tosu_enabled", [ServiceTab.StatsFm]: "sfm_enabled", [ServiceTab.Jellyfin]: "jf_enabled", - [ServiceTab.ListenBrainz]: "lb_enabled", [ServiceTab.GensokyoRadio]: "gr_enabled", [ServiceTab.Navidrome]: "nd_enabled", }; diff --git a/src/equicordplugins/richPresence/index.tsx b/src/equicordplugins/richPresence/index.tsx index ecda47809..a60412dfa 100644 --- a/src/equicordplugins/richPresence/index.tsx +++ b/src/equicordplugins/richPresence/index.tsx @@ -12,7 +12,6 @@ import { migrateOldSettings } from "./migration"; import * as abs from "./services/audiobookshelf"; import * as gensokyoRadio from "./services/gensokyoRadio"; import * as jellyfin from "./services/jellyfin"; -import * as listenbrainz from "./services/listenbrainz"; import * as navidrome from "./services/navidrome"; import * as statsfm from "./services/statsfm"; import * as tosu from "./services/tosu"; @@ -28,7 +27,6 @@ const services: Record = { [ServiceTab.Tosu]: "tosu_enabled", [ServiceTab.StatsFm]: "sfm_enabled", [ServiceTab.Jellyfin]: "jf_enabled", - [ServiceTab.ListenBrainz]: "lb_enabled", [ServiceTab.GensokyoRadio]: "gr_enabled", [ServiceTab.Navidrome]: "nd_enabled", }; @@ -77,7 +74,7 @@ function stopAllServices() { export default definePlugin({ name: "RichPresence", - description: "Unified rich presence hub for AudioBookShelf, osu!, stats.fm, Jellyfin, ListenBrainz, Navidrome, and Gensokyo Radio.", + description: "Unified rich presence hub for AudioBookShelf, osu!, stats.fm, Jellyfin, Navidrome, and Gensokyo Radio.", tags: ["Activity"], authors: [ EquicordDevs.vmohammad, diff --git a/src/equicordplugins/richPresence/migration.ts b/src/equicordplugins/richPresence/migration.ts index 49f35b2ba..fe18b5bc4 100644 --- a/src/equicordplugins/richPresence/migration.ts +++ b/src/equicordplugins/richPresence/migration.ts @@ -63,24 +63,6 @@ const migrations: MigrationMapping[] = [ privacyMode: "jf_privacyMode", }, }, - { - oldPlugin: "ListenBrainzRPC", - enableKey: "lb_enabled", - keys: { - username: "lb_username", - mbContact: "lb_mbContact", - shareUsername: "lb_shareUsername", - shareSong: "lb_shareSong", - hideWithSpotify: "lb_hideWithSpotify", - hideWithActivity: "lb_hideWithActivity", - useTimeBar: "lb_useTimeBar", - statusName: "lb_statusName", - nameFormat: "lb_nameFormat", - useListeningStatus: "lb_useListeningStatus", - missingArt: "lb_missingArt", - useLogo: "lb_useLogo", - }, - }, { oldPlugin: "GensokyoRadioRPC", enableKey: "gr_enabled", @@ -97,7 +79,7 @@ function setStoreValue(key: SettingsKey, value: boolean | string | number) { export function migrateOldSettings() { if (Settings.plugins.RichPresence._migrated) return; - migratePluginSettings("RichPresence", "AudioBookShelfRichPresence", "GensokyoRadioRPC", "JellyfinRichPresence", "ListenBrainzRPC", "StatsfmPresence", "TosuRPC"); + migratePluginSettings("RichPresence", "AudioBookShelfRichPresence", "GensokyoRadioRPC", "JellyfinRichPresence", "StatsfmPresence", "TosuRPC"); for (const migration of migrations) { const oldSettings = Settings.plugins[migration.oldPlugin]; diff --git a/src/equicordplugins/richPresence/services/listenbrainz.ts b/src/equicordplugins/richPresence/services/listenbrainz.ts deleted file mode 100644 index 72d132b64..000000000 --- a/src/equicordplugins/richPresence/services/listenbrainz.ts +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2026 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { Logger } from "@utils/Logger"; -import { Activity, ActivityButton } from "@vencord/discord-types"; -import { ActivityFlags, ActivityType } from "@vencord/discord-types/enums"; -import { findByPropsLazy } from "@webpack"; -import { ApplicationAssetUtils, FluxDispatcher } from "@webpack/common"; - -import { settings } from "../settings"; -import { NameFormat } from "../types"; -import { LbTrackData } from "../types/listenbrainz"; - -const APPLICATION_ID = "1090155131007406132"; -const PLACEHOLDER_ID = "2a96cbd8b46e442fc41c2b86b821562f"; -const SOCKET_ID = "RichPresence_LB"; -const logger = new Logger("RichPresence:ListenBrainz"); -const PresenceStore = findByPropsLazy("getLocalPresence"); - -let updateInterval: NodeJS.Timeout | undefined; -let currentRecordingMBID = ""; -let currentStart = 0; - -async function getAsset(key: string): Promise { - return (await ApplicationAssetUtils.fetchAssetIds(APPLICATION_ID, [key]))[0]; -} - -function setActivity(activity: Activity | null) { - FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", activity, socketId: SOCKET_ID }); -} - -async function fetchTrackData(): Promise { - if (!settings.store.lb_username) { - logger.warn("ListenBrainz username is not set."); - return null; - } - - try { - const lbRes = await fetch(`https://api.listenbrainz.org/1/user/${settings.store.lb_username}/playing-now`); - if (!lbRes.ok) throw `${lbRes.status} ${lbRes.statusText}`; - - const lbJson = await lbRes.json(); - if (lbJson.error) { - logger.error("Error from ListenBrainz API", `${lbJson.error}: ${lbJson.message}`); - return null; - } - - const listen = lbJson.payload?.listens?.[0]; - if (!listen?.playing_now) return null; - - const trackMetadata = listen.track_metadata; - const albumName = trackMetadata.release_name || "Unknown"; - const artistName = trackMetadata.artist_name || "Unknown"; - - const mbRes = await fetch( - `https://musicbrainz.org/ws/2/release/?query=release:${encodeURIComponent(albumName)}%20AND%20artist:${encodeURIComponent(artistName)}&fmt=json` - ); - if (!mbRes.ok) throw `${mbRes.status} ${mbRes.statusText}`; - - const mbJson = await mbRes.json(); - const releases = mbJson.releases || []; - const releaseGroup = releases[0]?.["release-group"]?.id; - - let url = ""; - let imageUrl = ""; - - if (releaseGroup) { - const caaRes = await fetch(`https://coverartarchive.org/release-group/${releaseGroup}`); - if (caaRes.ok) { - const caaJson = await caaRes.json(); - url = caaJson.release ?? ""; - for (const image of caaJson.images) { - imageUrl = image.thumbnails.large || ""; - if (imageUrl) break; - } - } - } - - return { - name: trackMetadata.track_name || "Unknown", - album: albumName, - artist: artistName, - durationMs: trackMetadata.additional_info?.duration_ms, - recordingMBID: trackMetadata.additional_info?.recording_mbid, - url, - imageUrl, - }; - } catch (e) { - logger.error("Failed to query ListenBrainz API", e); - return null; - } -} - -function getLargeImage(track: LbTrackData): string | undefined { - if (track.imageUrl && !track.imageUrl.includes(PLACEHOLDER_ID)) return track.imageUrl; - if (settings.store.lb_missingArt === "placeholder") return "placeholder"; -} - -async function getActivity(): Promise { - if (settings.store.lb_hideWithActivity) { - if (PresenceStore.getActivities().some(a => a.application_id !== APPLICATION_ID)) return null; - } - - if (settings.store.lb_hideWithSpotify) { - if (PresenceStore.getActivities().some(a => a.type === ActivityType.LISTENING && a.application_id !== APPLICATION_ID)) - return null; - } - - const trackData = await fetchTrackData(); - if (!trackData) return null; - - const largeImage = getLargeImage(trackData); - const assets = largeImage - ? { - large_image: await getAsset(largeImage), - large_text: trackData.album || undefined, - small_image: settings.store.lb_useLogo ? await getAsset("listenbrainz") : undefined, - small_text: "ListenBrainz", - } - : { - large_image: await getAsset("listenbrainz"), - large_text: trackData.album || undefined, - }; - - const buttons: ActivityButton[] = []; - if (settings.store.lb_shareUsername) - buttons.push({ label: "ListenBrainz Profile", url: `https://www.listenbrainz.org/user/${settings.store.lb_username}` }); - if (settings.store.lb_shareSong && trackData.url) - buttons.push({ label: "View Song", url: trackData.url }); - - const statusName = (() => { - switch (settings.store.lb_nameFormat) { - case NameFormat.ArtistFirst: return trackData.artist + " - " + trackData.name; - case NameFormat.SongFirst: return trackData.name + " - " + trackData.artist; - case NameFormat.ArtistOnly: return trackData.artist; - case NameFormat.SongOnly: return trackData.name; - case NameFormat.AlbumName: return trackData.album || settings.store.lb_statusName; - default: return settings.store.lb_statusName; - } - })(); - - if (trackData.recordingMBID && trackData.recordingMBID !== currentRecordingMBID) { - currentRecordingMBID = trackData.recordingMBID; - currentStart = Date.now(); - } - - return { - application_id: APPLICATION_ID, - name: statusName, - details: trackData.name, - state: trackData.artist, - assets, - timestamps: settings.store.lb_useTimeBar && currentStart - ? { start: currentStart, end: trackData.durationMs ? currentStart + trackData.durationMs : undefined } - : undefined, - buttons: buttons.length ? buttons.map(v => v.label) : undefined, - metadata: buttons.length ? { button_urls: buttons.map(v => v.url) } : undefined, - type: settings.store.lb_useListeningStatus ? ActivityType.LISTENING : ActivityType.PLAYING, - flags: ActivityFlags.INSTANCE, - }; -} - -async function updatePresence() { - try { - setActivity(await getActivity()); - } catch (e) { - logger.error("Failed to update presence", e); - setActivity(null); - } -} - -export function start() { - currentRecordingMBID = ""; - currentStart = 0; - updatePresence(); - updateInterval = setInterval(updatePresence, 16000); -} - -export function stop() { - clearInterval(updateInterval); - updateInterval = undefined; - setActivity(null); -} diff --git a/src/equicordplugins/richPresence/settings.ts b/src/equicordplugins/richPresence/settings.ts index 048bd3f69..6566bd0dd 100644 --- a/src/equicordplugins/richPresence/settings.ts +++ b/src/equicordplugins/richPresence/settings.ts @@ -56,13 +56,6 @@ export const settings = definePluginSettings({ hidden: true, onChange: () => onServiceChange?.(), }, - lb_enabled: { - description: "Enable ListenBrainz presence.", - type: OptionType.BOOLEAN, - default: false, - hidden: true, - onChange: () => onServiceChange?.(), - }, gr_enabled: { description: "Enable Gensokyo Radio presence.", type: OptionType.BOOLEAN, @@ -261,90 +254,6 @@ export const settings = definePluginSettings({ hidden: true, }, - // ListenBrainz - lb_username: { - description: "ListenBrainz username.", - type: OptionType.STRING, - default: "", - hidden: true, - }, - lb_mbContact: { - description: "MusicBrainz contact for user agent.", - type: OptionType.STRING, - default: "", - hidden: true, - }, - lb_shareUsername: { - description: "Show link to ListenBrainz profile.", - type: OptionType.BOOLEAN, - default: false, - hidden: true, - }, - lb_shareSong: { - description: "Show link to song on ListenBrainz.", - type: OptionType.BOOLEAN, - default: true, - hidden: true, - }, - lb_hideWithSpotify: { - description: "Hide presence if Spotify is running.", - type: OptionType.BOOLEAN, - default: true, - hidden: true, - }, - lb_hideWithActivity: { - description: "Hide presence if any other presence exists.", - type: OptionType.BOOLEAN, - default: false, - hidden: true, - }, - lb_useTimeBar: { - description: "Use track duration to display a time bar.", - type: OptionType.BOOLEAN, - default: true, - hidden: true, - }, - lb_statusName: { - description: "Custom status text.", - type: OptionType.STRING, - default: "some music", - hidden: true, - }, - lb_nameFormat: { - description: "Name format.", - type: OptionType.SELECT, - options: [ - { label: "Use custom status name", value: NameFormat.StatusName, default: true }, - { label: "Use format 'artist - song'", value: NameFormat.ArtistFirst }, - { label: "Use format 'song - artist'", value: NameFormat.SongFirst }, - { label: "Use artist name only", value: NameFormat.ArtistOnly }, - { label: "Use song name only", value: NameFormat.SongOnly }, - { label: "Use album name", value: NameFormat.AlbumName }, - ], - hidden: true, - }, - lb_useListeningStatus: { - description: "Show listening status.", - type: OptionType.BOOLEAN, - default: true, - hidden: true, - }, - lb_missingArt: { - description: "Fallback when art is missing.", - type: OptionType.SELECT, - options: [ - { label: "Use large ListenBrainz logo", value: "listenbrainzLogo", default: true }, - { label: "Use generic placeholder", value: "placeholder" }, - ], - hidden: true, - }, - lb_useLogo: { - description: "Show ListenBrainz logo on album art.", - type: OptionType.BOOLEAN, - default: true, - hidden: true, - }, - // Gensokyo Radio gr_refreshInterval: { description: "Refresh interval in seconds.", diff --git a/src/equicordplugins/richPresence/types/index.ts b/src/equicordplugins/richPresence/types/index.ts index bfeba0172..e6937ea61 100644 --- a/src/equicordplugins/richPresence/types/index.ts +++ b/src/equicordplugins/richPresence/types/index.ts @@ -9,7 +9,6 @@ export enum ServiceTab { Tosu = "tosu", StatsFm = "statsfm", Jellyfin = "jellyfin", - ListenBrainz = "listenbrainz", GensokyoRadio = "gensokyoRadio", Navidrome = "navidrome", }