From 21f4a78dd77c72ce532260a1cb484df6363957fb Mon Sep 17 00:00:00 2001 From: Jack Merrill <8814123+jackmerrill@users.noreply.github.com> Date: Thu, 31 Oct 2024 15:09:17 -0400 Subject: [PATCH] feat: Discord Rich Presence album art via Last.fm (#341) (#817) * feat: Discord Rich Presence album art via Last.fm * fix: securely fetch album art --- src/i18n/locales/en.json | 2 ++ .../features/discord-rpc/use-discord-rpc.ts | 16 ++++++++++ .../components/window/discord-settings.tsx | 32 ++++++++++++++++++- src/renderer/store/settings.store.ts | 2 ++ 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 718b2ebd..a4ca8fa3 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -582,6 +582,8 @@ "imageAspectRatio_description": "if enabled, cover art will be shown using their native aspect ratio. for art that is not 1:1, the remaining space will be empty", "language": "language", "language_description": "sets the language for the application ($t(common.restartRequired))", + "lastfmApiKey": "{{lastfm}} API key", + "lastfmApiKey_description": "the API key for {{lastfm}}. required for cover art", "lyricFetch": "fetch lyrics from the internet", "lyricFetch_description": "fetch lyrics from various internet sources", "lyricFetchProvider": "providers to fetch lyrics from", diff --git a/src/renderer/features/discord-rpc/use-discord-rpc.ts b/src/renderer/features/discord-rpc/use-discord-rpc.ts index 7b20e546..24578b84 100644 --- a/src/renderer/features/discord-rpc/use-discord-rpc.ts +++ b/src/renderer/features/discord-rpc/use-discord-rpc.ts @@ -5,6 +5,7 @@ import { useCurrentSong, useCurrentStatus, useDiscordSetttings, + useGeneralSettings, usePlayerStore, } from '/@/renderer/store'; import { SetActivity } from '@xhayper/discord-rpc'; @@ -16,6 +17,7 @@ const discordRpc = isElectron() ? window.electron.discordRpc : null; export const useDiscordRpc = () => { const intervalRef = useRef(0); const discordSettings = useDiscordSetttings(); + const generalSettings = useGeneralSettings(); const currentSong = useCurrentSong(); const currentStatus = useCurrentStatus(); @@ -67,6 +69,19 @@ export const useDiscordRpc = () => { activity.largeImageKey = song?.imageUrl; } + if (generalSettings.lastfmApiKey && song?.album && song?.artists.length) { + console.log('Fetching album info for', song.album, song.artists[0].name); + const albumInfo = await fetch( + `https://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key=${generalSettings.lastfmApiKey}&artist=${encodeURIComponent(song.artistName)}&album=${encodeURIComponent(song.album)}&format=json`, + ); + + const albumInfoJson = await albumInfo.json(); + + if (albumInfoJson.album?.image?.[3]['#text']) { + activity.largeImageKey = albumInfoJson.album.image[3]['#text']; + } + } + // Fall back to default icon if not set if (!activity.largeImageKey) { activity.largeImageKey = 'icon'; @@ -79,6 +94,7 @@ export const useDiscordRpc = () => { discordSettings.enableIdle, discordSettings.showAsListening, discordSettings.showServerImage, + generalSettings.lastfmApiKey, ]); useEffect(() => { diff --git a/src/renderer/features/settings/components/window/discord-settings.tsx b/src/renderer/features/settings/components/window/discord-settings.tsx index 6b162346..065514be 100644 --- a/src/renderer/features/settings/components/window/discord-settings.tsx +++ b/src/renderer/features/settings/components/window/discord-settings.tsx @@ -4,12 +4,17 @@ import { SettingOption, SettingsSection, } from '/@/renderer/features/settings/components/settings-section'; -import { useDiscordSetttings, useSettingsStoreActions } from '/@/renderer/store'; +import { + useDiscordSetttings, + useSettingsStoreActions, + useGeneralSettings, +} from '/@/renderer/store'; import { useTranslation } from 'react-i18next'; export const DiscordSettings = () => { const { t } = useTranslation(); const settings = useDiscordSetttings(); + const generalSettings = useGeneralSettings(); const { setSettings } = useSettingsStoreActions(); const discordOptions: SettingOption[] = [ @@ -142,6 +147,31 @@ export const DiscordSettings = () => { postProcess: 'sentenceCase', }), }, + { + control: ( + { + setSettings({ + general: { + ...generalSettings, + lastfmApiKey: e.currentTarget.value, + }, + }); + }} + /> + ), + description: t('setting.lastfmApiKey', { + context: 'description', + lastfm: 'Last.fm', + postProcess: 'sentenceCase', + }), + isHidden: !isElectron(), + title: t('setting.lastfmApiKey', { + lastfm: 'Last.fm', + postProcess: 'sentenceCase', + }), + }, ]; return ; diff --git a/src/renderer/store/settings.store.ts b/src/renderer/store/settings.store.ts index 2c0a1dca..6bdf60ce 100644 --- a/src/renderer/store/settings.store.ts +++ b/src/renderer/store/settings.store.ts @@ -232,6 +232,7 @@ export interface SettingsState { homeFeature: boolean; homeItems: SortableItem[]; language: string; + lastfmApiKey: string; nativeAspectRatio: boolean; passwordStore?: string; playButtonBehavior: Play; @@ -377,6 +378,7 @@ const initialState: SettingsState = { homeFeature: true, homeItems, language: 'en', + lastfmApiKey: '', nativeAspectRatio: false, passwordStore: undefined, playButtonBehavior: Play.NOW,