mirror of
https://github.com/antebudimir/feishin.git
synced 2025-12-31 10:03:33 +00:00
Feature: Add song and artist links to discord RPC (#1160)
* Add song and artist links to discord RPC * use first artist name for artist link, full artist name for song link * use first album artist for song link * add discord rpc links setting * simplify discord link settings * fix setting description * add musicbrainz links * fix callback missing dependency * use encodeURIComponent for lastfm links Co-authored-by: Kendall Garner <17521368+kgarner7@users.noreply.github.com> * split musicbrainz ids * combine link settings --------- Co-authored-by: Kendall Garner <17521368+kgarner7@users.noreply.github.com>
This commit is contained in:
parent
f1a75d8e81
commit
1b278cb33a
10 changed files with 108 additions and 6 deletions
|
|
@ -544,6 +544,10 @@
|
||||||
"discordDisplayType_description": "changes what you are listening to in your status",
|
"discordDisplayType_description": "changes what you are listening to in your status",
|
||||||
"discordDisplayType_songname": "song name",
|
"discordDisplayType_songname": "song name",
|
||||||
"discordDisplayType_artistname": "artist name(s)",
|
"discordDisplayType_artistname": "artist name(s)",
|
||||||
|
"discordLinkType": "{{discord}} presence links",
|
||||||
|
"discordLinkType_description": "adds external links to {{lastfm}} or {{musicbrainz}} to the song and artist fields in {{discord}} rich presence. {{musicbrainz}} is the most accurate but requires tags and doesn't provide artist links while {{lastfm}} should always provide a link. makes no extra network requests",
|
||||||
|
"discordLinkType_none": "$t(common.none)",
|
||||||
|
"discordLinkType_mbz_lastfm": "{{musicbrainz}} with {{lastfm}} fallback",
|
||||||
"doubleClickBehavior": "queue all searched tracks when double clicking",
|
"doubleClickBehavior": "queue all searched tracks when double clicking",
|
||||||
"doubleClickBehavior_description": "if true, all matching tracks in a track search will be queued. otherwise, only the clicked one will be queued",
|
"doubleClickBehavior_description": "if true, all matching tracks in a track search will be queued. otherwise, only the clicked one will be queued",
|
||||||
"enableRemote": "enable remote control server",
|
"enableRemote": "enable remote control server",
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { useCallback, useEffect, useState } from 'react';
|
||||||
import { controller } from '/@/renderer/api/controller';
|
import { controller } from '/@/renderer/api/controller';
|
||||||
import {
|
import {
|
||||||
DiscordDisplayType,
|
DiscordDisplayType,
|
||||||
|
DiscordLinkType,
|
||||||
getServerById,
|
getServerById,
|
||||||
useAppStore,
|
useAppStore,
|
||||||
useDiscordSettings,
|
useDiscordSettings,
|
||||||
|
|
@ -77,6 +78,34 @@ export const useDiscordRpc = () => {
|
||||||
type: discordSettings.showAsListening ? 2 : 0,
|
type: discordSettings.showAsListening ? 2 : 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
(discordSettings.linkType == DiscordLinkType.LAST_FM ||
|
||||||
|
discordSettings.linkType == DiscordLinkType.MBZ_LAST_FM) &&
|
||||||
|
song?.artistName
|
||||||
|
) {
|
||||||
|
activity.stateUrl =
|
||||||
|
'https://www.last.fm/music/' + encodeURIComponent(song.artists[0].name);
|
||||||
|
activity.detailsUrl =
|
||||||
|
'https://www.last.fm/music/' +
|
||||||
|
encodeURIComponent(song.albumArtists[0].name) +
|
||||||
|
'/' +
|
||||||
|
encodeURIComponent(song.album || '_') +
|
||||||
|
'/' +
|
||||||
|
encodeURIComponent(song.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
discordSettings.linkType == DiscordLinkType.MBZ ||
|
||||||
|
discordSettings.linkType == DiscordLinkType.MBZ_LAST_FM
|
||||||
|
) {
|
||||||
|
if (song?.mbzTrackId) {
|
||||||
|
activity.detailsUrl = 'https://musicbrainz.org/track/' + song.mbzTrackId;
|
||||||
|
} else if (song?.mbzRecordingId) {
|
||||||
|
activity.detailsUrl =
|
||||||
|
'https://musicbrainz.org/recording/' + song.mbzRecordingId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ((current[2] as PlayerStatus) === PlayerStatus.PLAYING) {
|
if ((current[2] as PlayerStatus) === PlayerStatus.PLAYING) {
|
||||||
if (start && end) {
|
if (start && end) {
|
||||||
activity.startTimestamp = start;
|
activity.startTimestamp = start;
|
||||||
|
|
@ -145,6 +174,7 @@ export const useDiscordRpc = () => {
|
||||||
generalSettings.lastfmApiKey,
|
generalSettings.lastfmApiKey,
|
||||||
discordSettings.clientId,
|
discordSettings.clientId,
|
||||||
discordSettings.displayType,
|
discordSettings.displayType,
|
||||||
|
discordSettings.linkType,
|
||||||
lastUniqueId,
|
lastUniqueId,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
} from '/@/renderer/features/settings/components/settings-section';
|
} from '/@/renderer/features/settings/components/settings-section';
|
||||||
import {
|
import {
|
||||||
DiscordDisplayType,
|
DiscordDisplayType,
|
||||||
|
DiscordLinkType,
|
||||||
useDiscordSettings,
|
useDiscordSettings,
|
||||||
useGeneralSettings,
|
useGeneralSettings,
|
||||||
useSettingsStoreActions,
|
useSettingsStoreActions,
|
||||||
|
|
@ -162,6 +163,54 @@ export const DiscordSettings = () => {
|
||||||
}),
|
}),
|
||||||
isHidden: !isElectron(),
|
isHidden: !isElectron(),
|
||||||
title: t('setting.discordDisplayType', {
|
title: t('setting.discordDisplayType', {
|
||||||
|
discord: 'Discord',
|
||||||
|
musicbrainz: 'musicbrainz',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Select
|
||||||
|
aria-label={t('setting.discordLinkType')}
|
||||||
|
clearable={false}
|
||||||
|
data={[
|
||||||
|
{
|
||||||
|
label: t('setting.discordLinkType_none', {
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
value: DiscordLinkType.NONE,
|
||||||
|
},
|
||||||
|
{ label: 'last.fm', value: DiscordLinkType.LAST_FM },
|
||||||
|
{ label: 'musicbrainz', value: DiscordLinkType.MBZ },
|
||||||
|
{
|
||||||
|
label: t('setting.discordLinkType_mbz_lastfm', {
|
||||||
|
lastfm: 'last.fm',
|
||||||
|
musicbrainz: 'musicbrainz',
|
||||||
|
}),
|
||||||
|
value: DiscordLinkType.MBZ_LAST_FM,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
defaultValue={settings.linkType}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (!e) return;
|
||||||
|
setSettings({
|
||||||
|
discord: {
|
||||||
|
...settings,
|
||||||
|
linkType: e as DiscordLinkType,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.discordLinkType', {
|
||||||
|
context: 'description',
|
||||||
|
discord: 'Discord',
|
||||||
|
lastfm: 'last.fm',
|
||||||
|
musicbrainz: 'musicbrainz',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
isHidden: !isElectron(),
|
||||||
|
title: t('setting.discordLinkType', {
|
||||||
discord: 'Discord',
|
discord: 'Discord',
|
||||||
postProcess: 'sentenceCase',
|
postProcess: 'sentenceCase',
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,13 @@ export enum DiscordDisplayType {
|
||||||
SONG_NAME = 'song',
|
SONG_NAME = 'song',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum DiscordLinkType {
|
||||||
|
LAST_FM = 'last_fm',
|
||||||
|
MBZ = 'musicbrainz',
|
||||||
|
MBZ_LAST_FM = 'musicbrainz_last_fm',
|
||||||
|
NONE = 'none',
|
||||||
|
}
|
||||||
|
|
||||||
export enum GenreTarget {
|
export enum GenreTarget {
|
||||||
ALBUM = 'album',
|
ALBUM = 'album',
|
||||||
TRACK = 'track',
|
TRACK = 'track',
|
||||||
|
|
@ -207,6 +214,7 @@ export interface SettingsState {
|
||||||
clientId: string;
|
clientId: string;
|
||||||
displayType: DiscordDisplayType;
|
displayType: DiscordDisplayType;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
linkType: DiscordLinkType;
|
||||||
showAsListening: boolean;
|
showAsListening: boolean;
|
||||||
showPaused: boolean;
|
showPaused: boolean;
|
||||||
showServerImage: boolean;
|
showServerImage: boolean;
|
||||||
|
|
@ -364,6 +372,7 @@ const initialState: SettingsState = {
|
||||||
clientId: '1165957668758900787',
|
clientId: '1165957668758900787',
|
||||||
displayType: DiscordDisplayType.FEISHIN,
|
displayType: DiscordDisplayType.FEISHIN,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
linkType: DiscordLinkType.NONE,
|
||||||
showAsListening: false,
|
showAsListening: false,
|
||||||
showPaused: true,
|
showPaused: true,
|
||||||
showServerImage: false,
|
showServerImage: false,
|
||||||
|
|
|
||||||
|
|
@ -261,6 +261,8 @@ const normalizeSong = (
|
||||||
itemType: LibraryItem.SONG,
|
itemType: LibraryItem.SONG,
|
||||||
lastPlayedAt: null,
|
lastPlayedAt: null,
|
||||||
lyrics: null,
|
lyrics: null,
|
||||||
|
mbzRecordingId: null,
|
||||||
|
mbzTrackId: item.ProviderIds?.MusicBrainzTrack || null,
|
||||||
name: item.Name,
|
name: item.Name,
|
||||||
participants: getPeople(item),
|
participants: getPeople(item),
|
||||||
path,
|
path,
|
||||||
|
|
|
||||||
|
|
@ -393,6 +393,12 @@ const participant = z.object({
|
||||||
Type: z.string().optional(),
|
Type: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const providerIds = z.object({
|
||||||
|
MusicBrainzAlbum: z.string().optional(),
|
||||||
|
MusicBrainzArtist: z.string().optional(),
|
||||||
|
MusicBrainzTrack: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
const songDetailParameters = baseParameters;
|
const songDetailParameters = baseParameters;
|
||||||
|
|
||||||
const song = z.object({
|
const song = z.object({
|
||||||
|
|
@ -425,6 +431,7 @@ const song = z.object({
|
||||||
PlaylistItemId: z.string().optional(),
|
PlaylistItemId: z.string().optional(),
|
||||||
PremiereDate: z.string().optional(),
|
PremiereDate: z.string().optional(),
|
||||||
ProductionYear: z.number(),
|
ProductionYear: z.number(),
|
||||||
|
ProviderIds: providerIds.optional(),
|
||||||
RunTimeTicks: z.number(),
|
RunTimeTicks: z.number(),
|
||||||
ServerId: z.string(),
|
ServerId: z.string(),
|
||||||
SortName: z.string(),
|
SortName: z.string(),
|
||||||
|
|
@ -433,11 +440,6 @@ const song = z.object({
|
||||||
UserData: userData.optional(),
|
UserData: userData.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const providerIds = z.object({
|
|
||||||
MusicBrainzAlbum: z.string().optional(),
|
|
||||||
MusicBrainzArtist: z.string().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const albumArtist = z.object({
|
const albumArtist = z.object({
|
||||||
AlbumCount: z.number().optional(),
|
AlbumCount: z.number().optional(),
|
||||||
BackdropImageTags: z.array(z.string()),
|
BackdropImageTags: z.array(z.string()),
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,8 @@ const normalizeSong = (
|
||||||
itemType: LibraryItem.SONG,
|
itemType: LibraryItem.SONG,
|
||||||
lastPlayedAt: normalizePlayDate(item),
|
lastPlayedAt: normalizePlayDate(item),
|
||||||
lyrics: item.lyrics ? item.lyrics : null,
|
lyrics: item.lyrics ? item.lyrics : null,
|
||||||
|
mbzRecordingId: item.mbzReleaseTrackId || null,
|
||||||
|
mbzTrackId: item.mbzReleaseTrackId || null,
|
||||||
name: item.title,
|
name: item.title,
|
||||||
// Thankfully, Windows is merciful and allows a mix of separators. So, we can use the
|
// Thankfully, Windows is merciful and allows a mix of separators. So, we can use the
|
||||||
// POSIX separator here instead
|
// POSIX separator here instead
|
||||||
|
|
|
||||||
|
|
@ -214,7 +214,7 @@ const song = z.object({
|
||||||
mbzAlbumArtistId: z.string().optional(),
|
mbzAlbumArtistId: z.string().optional(),
|
||||||
mbzAlbumId: z.string().optional(),
|
mbzAlbumId: z.string().optional(),
|
||||||
mbzArtistId: z.string().optional(),
|
mbzArtistId: z.string().optional(),
|
||||||
mbzTrackId: z.string().optional(),
|
mbzReleaseTrackId: z.string().optional(),
|
||||||
mediumImageUrl: z.string().optional(),
|
mediumImageUrl: z.string().optional(),
|
||||||
orderAlbumArtistName: z.string(),
|
orderAlbumArtistName: z.string(),
|
||||||
orderAlbumName: z.string(),
|
orderAlbumName: z.string(),
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,8 @@ const normalizeSong = (
|
||||||
itemType: LibraryItem.SONG,
|
itemType: LibraryItem.SONG,
|
||||||
lastPlayedAt: null,
|
lastPlayedAt: null,
|
||||||
lyrics: null,
|
lyrics: null,
|
||||||
|
mbzRecordingId: item.musicBrainzId || null,
|
||||||
|
mbzTrackId: null,
|
||||||
name: item.title,
|
name: item.title,
|
||||||
participants: getParticipants(item),
|
participants: getParticipants(item),
|
||||||
path: item.path,
|
path: item.path,
|
||||||
|
|
|
||||||
|
|
@ -340,6 +340,8 @@ export type Song = {
|
||||||
itemType: LibraryItem.SONG;
|
itemType: LibraryItem.SONG;
|
||||||
lastPlayedAt: null | string;
|
lastPlayedAt: null | string;
|
||||||
lyrics: null | string;
|
lyrics: null | string;
|
||||||
|
mbzRecordingId: null | string;
|
||||||
|
mbzTrackId: null | string;
|
||||||
name: string;
|
name: string;
|
||||||
participants: null | Record<string, RelatedArtist[]>;
|
participants: null | Record<string, RelatedArtist[]>;
|
||||||
path: null | string;
|
path: null | string;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue