feat: Discord Rich Presence album art via Last.fm (#341) (#817)
Some checks failed
Test / release (macos-latest) (push) Has been cancelled
Test / release (ubuntu-latest) (push) Has been cancelled
Test / release (windows-latest) (push) Has been cancelled

* feat: Discord Rich Presence album art via Last.fm

* fix: securely fetch album art
This commit is contained in:
Jack Merrill 2024-10-31 15:09:17 -04:00 committed by GitHub
parent 61d7e7c390
commit 21f4a78dd7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 51 additions and 1 deletions

View File

@ -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", "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": "language",
"language_description": "sets the language for the application ($t(common.restartRequired))", "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": "fetch lyrics from the internet",
"lyricFetch_description": "fetch lyrics from various internet sources", "lyricFetch_description": "fetch lyrics from various internet sources",
"lyricFetchProvider": "providers to fetch lyrics from", "lyricFetchProvider": "providers to fetch lyrics from",

View File

@ -5,6 +5,7 @@ import {
useCurrentSong, useCurrentSong,
useCurrentStatus, useCurrentStatus,
useDiscordSetttings, useDiscordSetttings,
useGeneralSettings,
usePlayerStore, usePlayerStore,
} from '/@/renderer/store'; } from '/@/renderer/store';
import { SetActivity } from '@xhayper/discord-rpc'; import { SetActivity } from '@xhayper/discord-rpc';
@ -16,6 +17,7 @@ const discordRpc = isElectron() ? window.electron.discordRpc : null;
export const useDiscordRpc = () => { export const useDiscordRpc = () => {
const intervalRef = useRef(0); const intervalRef = useRef(0);
const discordSettings = useDiscordSetttings(); const discordSettings = useDiscordSetttings();
const generalSettings = useGeneralSettings();
const currentSong = useCurrentSong(); const currentSong = useCurrentSong();
const currentStatus = useCurrentStatus(); const currentStatus = useCurrentStatus();
@ -67,6 +69,19 @@ export const useDiscordRpc = () => {
activity.largeImageKey = song?.imageUrl; 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 // Fall back to default icon if not set
if (!activity.largeImageKey) { if (!activity.largeImageKey) {
activity.largeImageKey = 'icon'; activity.largeImageKey = 'icon';
@ -79,6 +94,7 @@ export const useDiscordRpc = () => {
discordSettings.enableIdle, discordSettings.enableIdle,
discordSettings.showAsListening, discordSettings.showAsListening,
discordSettings.showServerImage, discordSettings.showServerImage,
generalSettings.lastfmApiKey,
]); ]);
useEffect(() => { useEffect(() => {

View File

@ -4,12 +4,17 @@ import {
SettingOption, SettingOption,
SettingsSection, SettingsSection,
} from '/@/renderer/features/settings/components/settings-section'; } 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'; import { useTranslation } from 'react-i18next';
export const DiscordSettings = () => { export const DiscordSettings = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const settings = useDiscordSetttings(); const settings = useDiscordSetttings();
const generalSettings = useGeneralSettings();
const { setSettings } = useSettingsStoreActions(); const { setSettings } = useSettingsStoreActions();
const discordOptions: SettingOption[] = [ const discordOptions: SettingOption[] = [
@ -142,6 +147,31 @@ export const DiscordSettings = () => {
postProcess: 'sentenceCase', postProcess: 'sentenceCase',
}), }),
}, },
{
control: (
<TextInput
defaultValue={generalSettings.lastfmApiKey}
onBlur={(e) => {
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 <SettingsSection options={discordOptions} />; return <SettingsSection options={discordOptions} />;

View File

@ -232,6 +232,7 @@ export interface SettingsState {
homeFeature: boolean; homeFeature: boolean;
homeItems: SortableItem<HomeItem>[]; homeItems: SortableItem<HomeItem>[];
language: string; language: string;
lastfmApiKey: string;
nativeAspectRatio: boolean; nativeAspectRatio: boolean;
passwordStore?: string; passwordStore?: string;
playButtonBehavior: Play; playButtonBehavior: Play;
@ -377,6 +378,7 @@ const initialState: SettingsState = {
homeFeature: true, homeFeature: true,
homeItems, homeItems,
language: 'en', language: 'en',
lastfmApiKey: '',
nativeAspectRatio: false, nativeAspectRatio: false,
passwordStore: undefined, passwordStore: undefined,
playButtonBehavior: Play.NOW, playButtonBehavior: Play.NOW,