mirror of
https://github.com/antebudimir/feishin.git
synced 2025-12-31 18:13:31 +00:00
discord rpc changes (#958)
This commit is contained in:
parent
ae41fe99bb
commit
bea55d48a8
3 changed files with 113 additions and 188 deletions
|
|
@ -1,168 +1,149 @@
|
||||||
import { SetActivity } from '@xhayper/discord-rpc';
|
import { SetActivity } from '@xhayper/discord-rpc';
|
||||||
import isElectron from 'is-electron';
|
import isElectron from 'is-electron';
|
||||||
import { useCallback, useEffect, useRef } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { controller } from '/@/renderer/api/controller';
|
import { controller } from '/@/renderer/api/controller';
|
||||||
import {
|
import {
|
||||||
getServerById,
|
getServerById,
|
||||||
useCurrentSong,
|
|
||||||
useCurrentStatus,
|
|
||||||
useDiscordSetttings,
|
useDiscordSetttings,
|
||||||
useGeneralSettings,
|
useGeneralSettings,
|
||||||
usePlayerStore,
|
usePlayerStore,
|
||||||
} from '/@/renderer/store';
|
} from '/@/renderer/store';
|
||||||
import { ServerType } from '/@/shared/types/domain-types';
|
import { QueueSong, ServerType } from '/@/shared/types/domain-types';
|
||||||
import { PlayerStatus } from '/@/shared/types/types';
|
import { PlayerStatus } from '/@/shared/types/types';
|
||||||
|
|
||||||
const discordRpc = isElectron() ? window.api.discordRpc : null;
|
const discordRpc = isElectron() ? window.api.discordRpc : null;
|
||||||
|
|
||||||
export const useDiscordRpc = () => {
|
export const useDiscordRpc = () => {
|
||||||
const intervalRef = useRef(0);
|
|
||||||
const discordSettings = useDiscordSetttings();
|
const discordSettings = useDiscordSetttings();
|
||||||
const generalSettings = useGeneralSettings();
|
const generalSettings = useGeneralSettings();
|
||||||
const currentSong = useCurrentSong();
|
const [lastUniqueId, setlastUniqueId] = useState('');
|
||||||
const currentStatus = useCurrentStatus();
|
|
||||||
|
|
||||||
const setActivity = useCallback(async () => {
|
const setActivity = useCallback(
|
||||||
if (!discordSettings.enableIdle && currentStatus === PlayerStatus.PAUSED) {
|
async (
|
||||||
discordRpc?.clearActivity();
|
current: (number | PlayerStatus | QueueSong | undefined)[],
|
||||||
return;
|
previous: (number | PlayerStatus | QueueSong | undefined)[],
|
||||||
}
|
) => {
|
||||||
|
// No current song, or we switched to a new track and the player was paused (end of album, etc.)
|
||||||
|
if (!current[0] || (current[0] && current[2] === 'paused' && current[1] === 0))
|
||||||
|
return discordRpc?.clearActivity();
|
||||||
|
|
||||||
const song = currentSong?.id ? currentSong : null;
|
// Handle change detection
|
||||||
|
const song = current[0] as QueueSong;
|
||||||
|
const trackChanged = lastUniqueId !== song.uniqueId;
|
||||||
|
|
||||||
const currentTime = usePlayerStore.getState().current.time;
|
/*
|
||||||
|
1. If we jump more then 1.2 seconds from last state, update status to match
|
||||||
|
2. If the current song id is completely different, update status
|
||||||
|
3. If the player state changed, update status
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
Math.abs((current[1] as number) - (previous[1] as number)) > 1.2 ||
|
||||||
|
trackChanged ||
|
||||||
|
current[2] !== previous[2]
|
||||||
|
) {
|
||||||
|
if (trackChanged) setlastUniqueId(song.uniqueId);
|
||||||
|
|
||||||
const now = Date.now();
|
const start = Math.round(Date.now() - (current[1] as number) * 1000);
|
||||||
const start = currentTime ? Math.round(now - currentTime * 1000) : null;
|
const end = Math.round(start + song.duration);
|
||||||
const end = song?.duration && start ? Math.round(start + song.duration) : null;
|
|
||||||
|
|
||||||
const artists = song?.artists.map((artist) => artist.name).join(', ');
|
const artists = song?.artists.map((artist) => artist.name).join(', ');
|
||||||
|
|
||||||
const activity: SetActivity = {
|
const activity: SetActivity = {
|
||||||
details: song?.name.padEnd(2, ' ') || 'Idle',
|
details: song?.name.padEnd(2, ' ') || 'Idle',
|
||||||
instance: false,
|
instance: false,
|
||||||
largeImageKey: undefined,
|
largeImageKey: undefined,
|
||||||
largeImageText: song?.album || 'Unknown album',
|
largeImageText: song?.album || 'Unknown album',
|
||||||
smallImageKey: undefined,
|
smallImageKey: undefined,
|
||||||
smallImageText: currentStatus,
|
smallImageText: current[2] as string,
|
||||||
state: (artists && `By ${artists}`) || 'Unknown artist',
|
state: (artists && `By ${artists}`) || 'Unknown artist',
|
||||||
// I would love to use the actual type as opposed to hardcoding to 2,
|
// I would love to use the actual type as opposed to hardcoding to 2,
|
||||||
// but manually installing the discord-types package appears to break things
|
// but manually installing the discord-types package appears to break things
|
||||||
type: discordSettings.showAsListening ? 2 : 0,
|
type: discordSettings.showAsListening ? 2 : 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (currentStatus === PlayerStatus.PLAYING) {
|
if ((current[2] as PlayerStatus) === PlayerStatus.PLAYING) {
|
||||||
if (start && end) {
|
if (start && end) {
|
||||||
activity.startTimestamp = start;
|
activity.startTimestamp = start;
|
||||||
activity.endTimestamp = end;
|
activity.endTimestamp = end;
|
||||||
}
|
|
||||||
|
|
||||||
activity.smallImageKey = 'playing';
|
|
||||||
} else {
|
|
||||||
activity.smallImageKey = 'paused';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (discordSettings.showServerImage && song) {
|
|
||||||
if (song.serverType === ServerType.JELLYFIN && song.imageUrl) {
|
|
||||||
activity.largeImageKey = song.imageUrl;
|
|
||||||
} else if (song.serverType === ServerType.NAVIDROME) {
|
|
||||||
const server = getServerById(song.serverId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const info = await controller.getAlbumInfo({
|
|
||||||
apiClientProps: { server },
|
|
||||||
query: { id: song.albumId },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (info.imageUrl) {
|
|
||||||
activity.largeImageKey = info.imageUrl;
|
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
/* empty */
|
activity.smallImageKey = 'playing';
|
||||||
|
} else {
|
||||||
|
activity.smallImageKey = 'paused';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (discordSettings.showServerImage && song) {
|
||||||
|
if (song.serverType === ServerType.JELLYFIN && song.imageUrl) {
|
||||||
|
activity.largeImageKey = song.imageUrl;
|
||||||
|
} else if (song.serverType === ServerType.NAVIDROME) {
|
||||||
|
const server = getServerById(song.serverId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const info = await controller.getAlbumInfo({
|
||||||
|
apiClientProps: { server },
|
||||||
|
query: { id: song.albumId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (info.imageUrl) {
|
||||||
|
activity.largeImageKey = info.imageUrl;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
/* empty */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
activity.largeImageKey === undefined &&
|
||||||
|
generalSettings.lastfmApiKey &&
|
||||||
|
song?.album &&
|
||||||
|
song?.albumArtists.length
|
||||||
|
) {
|
||||||
|
const albumInfo = await fetch(
|
||||||
|
`https://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key=${generalSettings.lastfmApiKey}&artist=${encodeURIComponent(song.albumArtists[0].name)}&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';
|
||||||
|
}
|
||||||
|
|
||||||
|
discordRpc?.setActivity(activity);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
[
|
||||||
if (
|
discordSettings.showAsListening,
|
||||||
activity.largeImageKey === undefined &&
|
discordSettings.showServerImage,
|
||||||
generalSettings.lastfmApiKey &&
|
generalSettings.lastfmApiKey,
|
||||||
song?.album &&
|
lastUniqueId,
|
||||||
song?.albumArtists.length
|
],
|
||||||
) {
|
);
|
||||||
const albumInfo = await fetch(
|
|
||||||
`https://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key=${generalSettings.lastfmApiKey}&artist=${encodeURIComponent(song.albumArtists[0].name)}&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';
|
|
||||||
}
|
|
||||||
|
|
||||||
discordRpc?.setActivity(activity);
|
|
||||||
}, [
|
|
||||||
currentSong,
|
|
||||||
currentStatus,
|
|
||||||
discordSettings.enableIdle,
|
|
||||||
discordSettings.showAsListening,
|
|
||||||
discordSettings.showServerImage,
|
|
||||||
generalSettings.lastfmApiKey,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initializeDiscordRpc = async () => {
|
if (!discordSettings.enabled) return discordRpc?.quit();
|
||||||
discordRpc?.initialize(discordSettings.clientId);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (discordSettings.enabled) {
|
|
||||||
initializeDiscordRpc();
|
|
||||||
} else {
|
|
||||||
discordRpc?.quit();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
discordRpc?.initialize(discordSettings.clientId);
|
||||||
return () => {
|
return () => {
|
||||||
discordRpc?.quit();
|
discordRpc?.quit();
|
||||||
};
|
};
|
||||||
}, [discordSettings.clientId, discordSettings.enabled]);
|
}, [discordSettings.clientId, discordSettings.enabled]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (discordSettings.enabled) {
|
if (!discordSettings.enabled) return;
|
||||||
let intervalSeconds = discordSettings.updateInterval;
|
const unsubSongChange = usePlayerStore.subscribe(
|
||||||
if (intervalSeconds < 15) {
|
(state) => [state.current.song, state.current.time, state.current.status],
|
||||||
intervalSeconds = 15;
|
setActivity,
|
||||||
}
|
);
|
||||||
|
return () => {
|
||||||
intervalRef.current = window.setInterval(setActivity, intervalSeconds * 1000);
|
unsubSongChange();
|
||||||
return () => clearInterval(intervalRef.current);
|
};
|
||||||
}
|
}, [discordSettings.enabled, setActivity]);
|
||||||
|
|
||||||
return () => {};
|
|
||||||
}, [discordSettings.enabled, discordSettings.updateInterval, setActivity]);
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// console.log(
|
|
||||||
// 'currentStatus, discordSettings.enableIdle',
|
|
||||||
// currentStatus,
|
|
||||||
// discordSettings.enableIdle,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// if (discordSettings.enableIdle === false && currentStatus === PlayerStatus.PAUSED) {
|
|
||||||
// console.log('removing activity');
|
|
||||||
// clearActivity();
|
|
||||||
// clearInterval(intervalRef.current);
|
|
||||||
// }
|
|
||||||
// }, [
|
|
||||||
// clearActivity,
|
|
||||||
// currentStatus,
|
|
||||||
// discordSettings.enableIdle,
|
|
||||||
// discordSettings.enabled,
|
|
||||||
// setActivity,
|
|
||||||
// ]);
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import isElectron from 'is-electron';
|
import isElectron from 'is-electron';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { NumberInput, Switch, TextInput } from '/@/renderer/components';
|
import { Switch, TextInput } from '/@/renderer/components';
|
||||||
import {
|
import {
|
||||||
SettingOption,
|
SettingOption,
|
||||||
SettingsSection,
|
SettingsSection,
|
||||||
|
|
@ -73,58 +73,6 @@ export const DiscordSettings = () => {
|
||||||
postProcess: 'sentenceCase',
|
postProcess: 'sentenceCase',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
control: (
|
|
||||||
<NumberInput
|
|
||||||
onChange={(e) => {
|
|
||||||
let value = e ? Number(e) : 0;
|
|
||||||
if (value < 15) {
|
|
||||||
value = 15;
|
|
||||||
}
|
|
||||||
|
|
||||||
setSettings({
|
|
||||||
discord: {
|
|
||||||
...settings,
|
|
||||||
updateInterval: value,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
value={settings.updateInterval}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
description: t('setting.discordUpdateInterval', {
|
|
||||||
context: 'description',
|
|
||||||
postProcess: 'sentenceCase',
|
|
||||||
}),
|
|
||||||
isHidden: !isElectron(),
|
|
||||||
title: t('setting.discordUpdateInterval', {
|
|
||||||
discord: 'Discord',
|
|
||||||
postProcess: 'sentenceCase',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
control: (
|
|
||||||
<Switch
|
|
||||||
checked={settings.enableIdle}
|
|
||||||
onChange={(e) => {
|
|
||||||
setSettings({
|
|
||||||
discord: {
|
|
||||||
...settings,
|
|
||||||
enableIdle: e.currentTarget.checked,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
description: t('setting.discordIdleStatus', {
|
|
||||||
context: 'description',
|
|
||||||
postProcess: 'sentenceCase',
|
|
||||||
}),
|
|
||||||
isHidden: !isElectron(),
|
|
||||||
title: t('setting.discordIdleStatus', {
|
|
||||||
postProcess: 'sentenceCase',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
control: (
|
control: (
|
||||||
<Switch
|
<Switch
|
||||||
|
|
|
||||||
|
|
@ -201,10 +201,8 @@ export interface SettingsState {
|
||||||
discord: {
|
discord: {
|
||||||
clientId: string;
|
clientId: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
enableIdle: boolean;
|
|
||||||
showAsListening: boolean;
|
showAsListening: boolean;
|
||||||
showServerImage: boolean;
|
showServerImage: boolean;
|
||||||
updateInterval: number;
|
|
||||||
};
|
};
|
||||||
font: {
|
font: {
|
||||||
builtIn: string;
|
builtIn: string;
|
||||||
|
|
@ -353,10 +351,8 @@ const initialState: SettingsState = {
|
||||||
discord: {
|
discord: {
|
||||||
clientId: '1165957668758900787',
|
clientId: '1165957668758900787',
|
||||||
enabled: false,
|
enabled: false,
|
||||||
enableIdle: false,
|
|
||||||
showAsListening: false,
|
showAsListening: false,
|
||||||
showServerImage: false,
|
showServerImage: false,
|
||||||
updateInterval: 15,
|
|
||||||
},
|
},
|
||||||
font: {
|
font: {
|
||||||
builtIn: 'Inter',
|
builtIn: 'Inter',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue