richpresence: remove listenbrainz
This commit is contained in:
parent
c0d0791961
commit
caa851ece9
6 changed files with 2 additions and 327 deletions
|
|
@ -141,29 +141,6 @@ function JellyfinSettings() {
|
|||
);
|
||||
}
|
||||
|
||||
function ListenBrainzSettings() {
|
||||
return (
|
||||
<>
|
||||
<SettingsSection id="listenbrainz-settings" name="" description="Show what you're currently listening to via ListenBrainz. The MusicBrainz API requires a meaningful user agent string (an email usually works)." />
|
||||
<TextSetting name="Username" description="ListenBrainz username." settingsKey="lb_username" placeholder="ListenBrainz username" />
|
||||
<TextSetting name="MusicBrainz Contact" description="MusicBrainz contact for user agent." settingsKey="lb_mbContact" placeholder="your@email.com" />
|
||||
<TextSetting name="Custom Status Text" description="Custom status text." settingsKey="lb_statusName" placeholder="some music" />
|
||||
<SelectSetting name="Name Format" description="Name format." settingsKey="lb_nameFormat" options={nameFormatOptions} />
|
||||
<SelectSetting name="Missing Art Fallback" description="Fallback when art is missing." settingsKey="lb_missingArt" options={[
|
||||
{ label: "Use large ListenBrainz logo", value: "listenbrainzLogo" },
|
||||
{ label: "Use generic placeholder", value: "placeholder" },
|
||||
]} />
|
||||
<SwitchSetting name="Show Listening Status" description="Show listening status." settingsKey="lb_useListeningStatus" />
|
||||
<SwitchSetting name="Show Time Bar" description="Use track duration to display a time bar." settingsKey="lb_useTimeBar" />
|
||||
<SwitchSetting name="Show ListenBrainz Logo" description="Show ListenBrainz logo on album art." settingsKey="lb_useLogo" />
|
||||
<SwitchSetting name="Show Profile Link" description="Show link to ListenBrainz profile." settingsKey="lb_shareUsername" />
|
||||
<SwitchSetting name="Show Song Link" description="Show link to song on ListenBrainz." settingsKey="lb_shareSong" />
|
||||
<SwitchSetting name="Hide With Spotify" description="Hide presence if Spotify is running." settingsKey="lb_hideWithSpotify" />
|
||||
<SwitchSetting name="Hide With Any Activity" description="Hide presence if any other presence exists." settingsKey="lb_hideWithActivity" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function GensokyoRadioSettings() {
|
||||
const [value, setValue] = useState(settings.store.gr_refreshInterval ?? 15);
|
||||
return (
|
||||
|
|
@ -239,7 +216,6 @@ const TAB_COMPONENTS: Record<ServiceTab, React.ComponentType> = {
|
|||
[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, string> = {
|
|||
[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, SettingsKey> = {
|
|||
[ServiceTab.Tosu]: "tosu_enabled",
|
||||
[ServiceTab.StatsFm]: "sfm_enabled",
|
||||
[ServiceTab.Jellyfin]: "jf_enabled",
|
||||
[ServiceTab.ListenBrainz]: "lb_enabled",
|
||||
[ServiceTab.GensokyoRadio]: "gr_enabled",
|
||||
[ServiceTab.Navidrome]: "nd_enabled",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<string, { start(): void; stop(): void; forceUpdate?(): vo
|
|||
[ServiceTab.Tosu]: tosu,
|
||||
[ServiceTab.StatsFm]: statsfm,
|
||||
[ServiceTab.Jellyfin]: jellyfin,
|
||||
[ServiceTab.ListenBrainz]: listenbrainz,
|
||||
[ServiceTab.GensokyoRadio]: gensokyoRadio,
|
||||
[ServiceTab.Navidrome]: navidrome,
|
||||
};
|
||||
|
|
@ -38,7 +36,6 @@ const enableKeys: Record<string, SettingsKey> = {
|
|||
[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,
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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<string> {
|
||||
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<LbTrackData | null> {
|
||||
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<Activity | null> {
|
||||
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);
|
||||
}
|
||||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ export enum ServiceTab {
|
|||
Tosu = "tosu",
|
||||
StatsFm = "statsfm",
|
||||
Jellyfin = "jellyfin",
|
||||
ListenBrainz = "listenbrainz",
|
||||
GensokyoRadio = "gensokyoRadio",
|
||||
Navidrome = "navidrome",
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue