From 4e406f818e0a4a5bc8e5f21e003b283060f974ea Mon Sep 17 00:00:00 2001 From: Nick Oates Date: Sun, 20 Jul 2025 10:51:03 -0700 Subject: [PATCH 1/3] DiscordRPC - Links and custom status (#165) --- plugins/DiscordRPC/package.json | 2 +- plugins/DiscordRPC/src/Settings.tsx | 16 +++++++- plugins/DiscordRPC/src/updateActivity.ts | 15 ++++++- pnpm-lock.yaml | 51 +++++++++++------------- 4 files changed, 52 insertions(+), 32 deletions(-) diff --git a/plugins/DiscordRPC/package.json b/plugins/DiscordRPC/package.json index f8915419..84e5e83f 100644 --- a/plugins/DiscordRPC/package.json +++ b/plugins/DiscordRPC/package.json @@ -9,6 +9,6 @@ }, "exports": "./src/index.ts", "dependencies": { - "@xhayper/discord-rpc": "^1.2.1" + "@xhayper/discord-rpc": "^1.3.0" } } \ No newline at end of file diff --git a/plugins/DiscordRPC/src/Settings.tsx b/plugins/DiscordRPC/src/Settings.tsx index b038b86f..28f154fd 100644 --- a/plugins/DiscordRPC/src/Settings.tsx +++ b/plugins/DiscordRPC/src/Settings.tsx @@ -1,4 +1,4 @@ -import { LunaSettings, LunaSwitchSetting } from "@luna/ui"; +import { LunaSelectItem, LunaSelectSetting, LunaSettings, LunaSwitchSetting } from "@luna/ui"; import { ReactiveStore } from "@luna/core"; @@ -9,11 +9,13 @@ import { updateActivity } from "./updateActivity"; export const settings = await ReactiveStore.getPluginStorage("DiscordRPC", { displayOnPause: true, displayArtistIcon: true, + status: 1, }); export const Settings = () => { const [displayOnPause, setDisplayOnPause] = React.useState(settings.displayOnPause); const [displayArtistIcon, setDisplayArtistIcon] = React.useState(settings.displayArtistIcon); + const [status, setStatus] = React.useState(settings.status); return ( @@ -35,12 +37,22 @@ export const Settings = () => { tooltip="Display artist icon" checked={displayArtistIcon} onChange={(_, checked) => { - setDisplayOnPause((settings.displayArtistIcon = checked)); + setDisplayArtistIcon((settings.displayArtistIcon = checked)); updateActivity() .then(() => (errSignal!._ = undefined)) .catch(trace.err.withContext("Failed to set activity")); }} /> + setStatus((settings.status = parseInt(e.target.value)))} + > + + + + ); }; diff --git a/plugins/DiscordRPC/src/updateActivity.ts b/plugins/DiscordRPC/src/updateActivity.ts index 0a952aee..0f22c18b 100644 --- a/plugins/DiscordRPC/src/updateActivity.ts +++ b/plugins/DiscordRPC/src/updateActivity.ts @@ -20,26 +20,36 @@ export const updateActivity = asyncDebounce(async (mediaItem?: MediaItem) => { const activity: SetActivity = { type: 2 }; // Listening type + const trackUrl = `https://tidal.com/browse/${mediaItem.tidalItem.contentType}/${mediaItem.id}?u` + activity.buttons = [ { - url: `https://tidal.com/browse/${mediaItem.tidalItem.contentType}/${mediaItem.id}?u`, + url: trackUrl, label: "Play Song", }, ]; + const artist = await mediaItem.artist(); + const artistUrl = `https://tidal.com/browse/artist/${artist?.id}?u`; + + // Status text + activity.statusDisplayType = settings.status; + // Title activity.details = await mediaItem.title().then(fmtStr); + activity.detailsUrl = trackUrl; // Artists const artistNames = await MediaItem.artistNames(await mediaItem.artists()); activity.state = fmtStr(artistNames.join(", ")) ?? "Unknown Artist"; + activity.stateUrl = artistUrl; // Pause indicator if (PlayState.playing) { // Small Artist image if (settings.displayArtistIcon) { - const artist = await mediaItem.artist(); activity.smallImageKey = artist?.coverUrl("320"); activity.smallImageText = fmtStr(artist?.name); + activity.smallImageUrl = artistUrl; } // Playback/Time @@ -58,6 +68,7 @@ export const updateActivity = asyncDebounce(async (mediaItem?: MediaItem) => { if (album) { activity.largeImageKey = album.coverUrl(); activity.largeImageText = await album.title().then(fmtStr); + activity.largeImageUrl = `https://tidal.com/browse/album/${album.id}?u`; } await setActivity(activity); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fb9636a0..ae8d7800 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,8 +62,8 @@ importers: plugins/DiscordRPC: dependencies: '@xhayper/discord-rpc': - specifier: ^1.2.1 - version: 1.2.1 + specifier: ^1.3.0 + version: 1.3.0 plugins/LastFM: {} @@ -96,6 +96,8 @@ importers: plugins/Themer: {} + plugins/TidalTags: {} + plugins/VolumeScroll: {} packages: @@ -104,8 +106,8 @@ packages: resolution: {integrity: sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==} engines: {node: '>=18'} - '@discordjs/rest@2.5.0': - resolution: {integrity: sha512-PWhchxTzpn9EV3vvPRpwS0EE2rNYB9pvzDU/eLLW3mByJl0ZHZjHI2/wA8EbH2gRMQV7nu+0FoDF84oiPl8VAQ==} + '@discordjs/rest@2.5.1': + resolution: {integrity: sha512-Tg9840IneBcbrAjcGaQzHUJWFNq1MMWZjTdjJ0WS/89IffaNKc++iOvffucPxQTF/gviO9+9r8kEPea1X5J2Dw==} engines: {node: '>=18'} '@discordjs/util@1.1.1': @@ -596,8 +598,8 @@ packages: resolution: {integrity: sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} - '@xhayper/discord-rpc@1.2.1': - resolution: {integrity: sha512-Ch04/7hq0nfV47nJzDcLIKx0SLUcPOMlkYV43faWpKtEO9SgLrTD4FAOMBBT+JORceQytnzBMPvktW2q9ZCMiw==} + '@xhayper/discord-rpc@1.3.0': + resolution: {integrity: sha512-0NmUTiODl7u3UEjmO6y0Syp3dmgVLAt2EHrH4QKTQcXRwtF8Wl7Eipdn/GSSZ8HkDwxQFvcDGJMxT9VWB0pH8g==} engines: {node: '>=18.20.7'} abort-controller@3.0.0: @@ -759,11 +761,8 @@ packages: detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} - discord-api-types@0.37.120: - resolution: {integrity: sha512-7xpNK0EiWjjDFp2nAhHXezE4OUWm7s1zhc/UXXN6hnFFU8dfoPHgV0Hx0RPiCa3ILRpdeh152icc68DGCyXYIw==} - - discord-api-types@0.38.1: - resolution: {integrity: sha512-vsjsqjAuxsPhiwbPjTBeGQaDPlizFmSkU0mTzFGMgRxqCDIRBR7iTY74HacpzrDV0QtERHRKQEk1tq7drZUtHg==} + discord-api-types@0.38.16: + resolution: {integrity: sha512-Cz42dC5WqJD17Yk0bRy7YLTJmh3NKo4FGpxZuA8MHqT0RPxKSrll5YhlODZ2z5DiEV/gpHMeTSrTFTWpSXjT1Q==} dot-case@3.0.4: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} @@ -1371,8 +1370,8 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - undici@6.21.1: - resolution: {integrity: sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==} + undici@6.21.3: + resolution: {integrity: sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==} engines: {node: '>=18.17'} union@0.5.0: @@ -1422,8 +1421,8 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.18.1: - resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==} + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -1453,17 +1452,17 @@ snapshots: '@discordjs/collection@2.1.1': {} - '@discordjs/rest@2.5.0': + '@discordjs/rest@2.5.1': dependencies: '@discordjs/collection': 2.1.1 '@discordjs/util': 1.1.1 '@sapphire/async-queue': 1.5.5 '@sapphire/snowflake': 3.5.5 '@vladfrangu/async_event_emitter': 2.4.6 - discord-api-types: 0.38.1 + discord-api-types: 0.38.16 magic-bytes.js: 1.12.1 tslib: 2.8.1 - undici: 6.21.1 + undici: 6.21.3 '@discordjs/util@1.1.1': {} @@ -1869,12 +1868,12 @@ snapshots: '@vladfrangu/async_event_emitter@2.4.6': {} - '@xhayper/discord-rpc@1.2.1': + '@xhayper/discord-rpc@1.3.0': dependencies: - '@discordjs/rest': 2.5.0 + '@discordjs/rest': 2.5.1 '@vladfrangu/async_event_emitter': 2.4.6 - discord-api-types: 0.37.120 - ws: 8.18.1 + discord-api-types: 0.38.16 + ws: 8.18.3 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -2035,9 +2034,7 @@ snapshots: detect-node@2.1.0: optional: true - discord-api-types@0.37.120: {} - - discord-api-types@0.38.1: {} + discord-api-types@0.38.16: {} dot-case@3.0.4: dependencies: @@ -2709,7 +2706,7 @@ snapshots: undici-types@6.21.0: {} - undici@6.21.1: {} + undici@6.21.3: {} union@0.5.0: dependencies: @@ -2756,7 +2753,7 @@ snapshots: wrappy@1.0.2: {} - ws@8.18.1: {} + ws@8.18.3: {} y18n@5.0.8: {} From cc787e2d2ed26270a11011941f121111b6c121a0 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Mon, 21 Jul 2025 22:27:24 -0400 Subject: [PATCH 2/3] Add Source Button --- plugins/DiscordRPC/src/updateActivity.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/DiscordRPC/src/updateActivity.ts b/plugins/DiscordRPC/src/updateActivity.ts index 0f22c18b..f957c489 100644 --- a/plugins/DiscordRPC/src/updateActivity.ts +++ b/plugins/DiscordRPC/src/updateActivity.ts @@ -1,5 +1,5 @@ import { asyncDebounce } from "@inrixia/helpers"; -import { MediaItem, PlayState } from "@luna/lib"; +import { MediaItem, PlayState, redux } from "@luna/lib"; import type { SetActivity } from "@xhayper/discord-rpc"; import { setActivity } from "./discord.native"; @@ -18,15 +18,23 @@ export const updateActivity = asyncDebounce(async (mediaItem?: MediaItem) => { mediaItem ??= await MediaItem.fromPlaybackContext(); if (mediaItem === undefined) return; + const { sourceName, sourceUrl } = redux.store.getState().playQueue; + const activity: SetActivity = { type: 2 }; // Listening type const trackUrl = `https://tidal.com/browse/${mediaItem.tidalItem.contentType}/${mediaItem.id}?u` + const trackSourceUrl = `https://tidal.com/browse${sourceUrl}`; + activity.buttons = [ { url: trackUrl, label: "Play Song", }, + { + url: trackSourceUrl, + label: `${fmtStr(sourceName) ?? "Unknown Source"}`, + } ]; const artist = await mediaItem.artist(); From f8026d970b04bc5168eda03c1ee9a5491a39e599 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Mon, 21 Jul 2025 23:08:54 -0400 Subject: [PATCH 3/3] PR Updates --- plugins/DiscordRPC/src/Settings.tsx | 16 +++++++++++++++- plugins/DiscordRPC/src/updateActivity.ts | 14 ++++++++------ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/plugins/DiscordRPC/src/Settings.tsx b/plugins/DiscordRPC/src/Settings.tsx index 28f154fd..fe6978cc 100644 --- a/plugins/DiscordRPC/src/Settings.tsx +++ b/plugins/DiscordRPC/src/Settings.tsx @@ -9,12 +9,14 @@ import { updateActivity } from "./updateActivity"; export const settings = await ReactiveStore.getPluginStorage("DiscordRPC", { displayOnPause: true, displayArtistIcon: true, + displayPlaylistButton: true, status: 1, }); export const Settings = () => { const [displayOnPause, setDisplayOnPause] = React.useState(settings.displayOnPause); const [displayArtistIcon, setDisplayArtistIcon] = React.useState(settings.displayArtistIcon); + const [displayPlaylistButton, setDisplayPlaylistButton] = React.useState(settings.displayPlaylistButton) const [status, setStatus] = React.useState(settings.status); return ( @@ -43,9 +45,21 @@ export const Settings = () => { .catch(trace.err.withContext("Failed to set activity")); }} /> + { + setDisplayPlaylistButton((settings.displayPlaylistButton = checked)); + updateActivity() + .then(() => (errSignal!._ = undefined)) + .catch(trace.err.withContext("Failed to set activity")); + }} + /> setStatus((settings.status = parseInt(e.target.value)))} > diff --git a/plugins/DiscordRPC/src/updateActivity.ts b/plugins/DiscordRPC/src/updateActivity.ts index f957c489..4f3b4b6d 100644 --- a/plugins/DiscordRPC/src/updateActivity.ts +++ b/plugins/DiscordRPC/src/updateActivity.ts @@ -18,25 +18,27 @@ export const updateActivity = asyncDebounce(async (mediaItem?: MediaItem) => { mediaItem ??= await MediaItem.fromPlaybackContext(); if (mediaItem === undefined) return; - const { sourceName, sourceUrl } = redux.store.getState().playQueue; + const { sourceUrl, sourceEntityType } = redux.store.getState().playQueue; const activity: SetActivity = { type: 2 }; // Listening type const trackUrl = `https://tidal.com/browse/${mediaItem.tidalItem.contentType}/${mediaItem.id}?u` - const trackSourceUrl = `https://tidal.com/browse${sourceUrl}`; activity.buttons = [ { url: trackUrl, label: "Play Song", - }, - { - url: trackSourceUrl, - label: `${fmtStr(sourceName) ?? "Unknown Source"}`, } ]; + if (sourceEntityType === "playlist" && settings.displayPlaylistButton) { + activity.buttons.push({ + url: trackSourceUrl, + label: "Playlist", + }); + } + const artist = await mediaItem.artist(); const artistUrl = `https://tidal.com/browse/artist/${artist?.id}?u`;