mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 14:55:43 +00:00
feat(discord): add server cover art source (#462)
* feat(discord): add server cover art source
The old Apple Music toggle is replaced with a radio selector which let's you choose
between Apple Music, Server and no image.
It's important to note that the server needs to be publicly accessible.
Translations have been added for all locales
* feat(discord): toggle UI for cover source and tightened defaults
- Replace cover-source radio buttons with three indented sub-toggles
(none / server / apple) under Discord Rich Presence; mutex via
setDiscordCoverSource — turning one on flips the others off.
- Default discordCoverSource is now 'server' for fresh installs
(opt-in friendly: own server, no third-party data leak). Existing
users keep their state via the legacy bool migration.
- Tighten template defaults: details {artist}, state {title}, largeText
unchanged. Existing users keep their persisted values.
* docs(contributors): credit Sayykii + changelog entry for #462
---------
Co-authored-by: Psychotoxical <171614930+Psychotoxical@users.noreply.github.com>
This commit is contained in:
@@ -16,6 +16,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [1.46.0] - 2026-05-05
|
||||
|
||||
## Added
|
||||
|
||||
### Discord — album cover art from your own server
|
||||
|
||||
**By [@Sayykii](https://github.com/Sayykii), PR [#462](https://github.com/Psychotoxical/psysonic/pull/462)**
|
||||
|
||||
* Discord Rich Presence can now show **album artwork from your own server** via the Subsonic `getAlbumInfo2` endpoint (requires the server to be publicly reachable).
|
||||
* New cover-source picker under Discord Rich Presence settings: **None** (app icon only), **Server**, or **Apple Music**. Mutually exclusive.
|
||||
* Fresh installs default to **Server** for opt-in-friendly cover art with no third-party data leak. Existing users keep their previous Apple-covers preference via migration.
|
||||
|
||||
## Changed
|
||||
|
||||
### Dependencies — npm / Cargo refresh and rodio 0.22
|
||||
|
||||
@@ -1053,6 +1053,23 @@ export function buildDownloadUrl(id: string): string {
|
||||
return `${baseUrl}/rest/download.view?${p.toString()}`;
|
||||
}
|
||||
|
||||
// ─── Album Info (public image URLs from Last.fm/MusicBrainz) ──
|
||||
export interface AlbumInfo {
|
||||
largeImageUrl?: string;
|
||||
mediumImageUrl?: string;
|
||||
smallImageUrl?: string;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export async function getAlbumInfo2(albumId: string): Promise<AlbumInfo | null> {
|
||||
try {
|
||||
const data = await api<{ albumInfo: AlbumInfo }>('getAlbumInfo2.view', { id: albumId });
|
||||
return data.albumInfo ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Playlists ────────────────────────────────────────────────
|
||||
export async function getPlaylists(includeOrbit = false): Promise<SubsonicPlaylist[]> {
|
||||
const data = await api<{ playlists: { playlist: SubsonicPlaylist[] } }>('getPlaylists.view', { _t: Date.now() });
|
||||
|
||||
+5
-2
@@ -709,8 +709,11 @@ export const deTranslation = {
|
||||
useCustomTitlebarDesc: 'Ersetzt die System-Titelleiste durch eine eingebaute, die zum App-Theme passt. Deaktivieren, um die native GNOME/GTK-Titelleiste zu verwenden.',
|
||||
linuxWebkitSmoothScroll: 'Sanftes Mausrad (Linux)',
|
||||
linuxWebkitSmoothScrollDesc: 'An: mit Nachlauf. Aus: zeilenweise wie in GTK-Apps.',
|
||||
discordAppleCovers: 'Cover über Apple Music für Discord laden',
|
||||
discordAppleCoversDesc: 'Sendet Künstler- und Albumname an die Apple-Such-API, um Cover für dein Discord-Profil zu finden. Standardmäßig aus Datenschutzgründen deaktiviert.',
|
||||
discordCoverSource: 'Cover-Quelle',
|
||||
discordCoverSourceDesc: 'Woher das Album-Cover für dein Discord-Profil geladen wird.',
|
||||
discordCoverNone: 'Keine (nur App-Symbol)',
|
||||
discordCoverServer: 'Server (über Album-Info)',
|
||||
discordCoverApple: 'Apple Music',
|
||||
discordOptions: 'Erweiterte Discord-Optionen',
|
||||
discordTemplates: 'Benutzerdefinierte Textvorlagen',
|
||||
discordTemplatesDesc: 'Passen Sie an, welche Informationen in Ihrem Discord-Profil angezeigt werden. Variablen: {title}, {artist}, {album}',
|
||||
|
||||
+5
-2
@@ -715,8 +715,11 @@ export const enTranslation = {
|
||||
useCustomTitlebarDesc: 'Replace the system title bar with a built-in one that matches the app theme. Disable to use the native GNOME/GTK title bar.',
|
||||
linuxWebkitSmoothScroll: 'Smooth wheel (Linux)',
|
||||
linuxWebkitSmoothScrollDesc: 'On: inertial scroll. Off: line-by-line, GTK-style.',
|
||||
discordAppleCovers: 'Fetch covers from Apple Music for Discord',
|
||||
discordAppleCoversDesc: 'Sends the artist and album name to Apple\'s search API to find cover art for your Discord profile. Disabled by default for privacy.',
|
||||
discordCoverSource: 'Cover art source',
|
||||
discordCoverSourceDesc: 'Where to fetch album artwork shown on your Discord profile.',
|
||||
discordCoverNone: 'None (app icon only)',
|
||||
discordCoverServer: 'Server (via album info)',
|
||||
discordCoverApple: 'Apple Music',
|
||||
discordOptions: 'Advanced Discord options',
|
||||
discordTemplates: 'Custom text templates',
|
||||
discordTemplatesDesc: 'Customize what information is shown on your Discord profile. Variables: {title}, {artist}, {album}',
|
||||
|
||||
+5
-2
@@ -702,8 +702,11 @@ export const esTranslation = {
|
||||
useCustomTitlebarDesc: 'Reemplaza la barra de título del sistema con una integrada que coincide con el tema de la app. Desactiva para usar la barra nativa de GNOME/GTK.',
|
||||
linuxWebkitSmoothScroll: 'Rueda suave (Linux)',
|
||||
linuxWebkitSmoothScrollDesc: 'Activado: inercia. Desactivado: pasos por línea (estilo GTK).',
|
||||
discordAppleCovers: 'Obtener portadas de Apple Music para Discord',
|
||||
discordAppleCoversDesc: 'Envía el artista y nombre del álbum a la API de búsqueda de Apple para encontrar portadas para tu perfil de Discord. Desactivado por defecto por privacidad.',
|
||||
discordCoverSource: 'Fuente de portada',
|
||||
discordCoverSourceDesc: 'De dónde obtener la portada del álbum para tu perfil de Discord.',
|
||||
discordCoverNone: 'Ninguna (solo icono de la app)',
|
||||
discordCoverServer: 'Servidor (vía info del álbum)',
|
||||
discordCoverApple: 'Apple Music',
|
||||
discordOptions: 'Opciones avanzadas de Discord',
|
||||
discordTemplates: 'Plantillas de texto personalizadas',
|
||||
discordTemplatesDesc: 'Personaliza qué información se muestra en tu perfil de Discord. Variables: {title}, {artist}, {album}',
|
||||
|
||||
+5
-2
@@ -697,8 +697,11 @@ export const frTranslation = {
|
||||
linuxWebkitSmoothScrollDesc: 'Activé : inertie. Désactivé : pas à la ligne, style GTK.',
|
||||
discordRichPresence: 'Discord Rich Presence',
|
||||
discordRichPresenceDesc: 'Affiche le titre en cours de lecture sur votre profil Discord. Discord doit être ouvert.',
|
||||
discordAppleCovers: 'Récupérer les pochettes via Apple Music pour Discord',
|
||||
discordAppleCoversDesc: 'Envoie le nom de l\'artiste et de l\'album à l\'API Apple pour trouver une pochette pour votre profil Discord. Désactivé par défaut pour des raisons de confidentialité.',
|
||||
discordCoverSource: 'Source de pochette',
|
||||
discordCoverSourceDesc: 'D\'où récupérer la pochette pour votre profil Discord.',
|
||||
discordCoverNone: 'Aucune (icône de l\'app uniquement)',
|
||||
discordCoverServer: 'Serveur (via infos album)',
|
||||
discordCoverApple: 'Apple Music',
|
||||
discordOptions: 'Options Discord avancées',
|
||||
discordTemplates: 'Modèles de texte personnalisés',
|
||||
discordTemplatesDesc: 'Personnalisez les informations affichées sur votre profil Discord. Variables : {title}, {artist}, {album}',
|
||||
|
||||
+5
-2
@@ -696,8 +696,11 @@ export const nbTranslation = {
|
||||
linuxWebkitSmoothScrollDesc: 'På: treg rull med etterslep. Av: trinnvis som i GTK.',
|
||||
discordRichPresence: 'Discord Rich Presence',
|
||||
discordRichPresenceDesc: 'Vis sporet som spilles i din Discord-profil. Krever at Discord kjører.',
|
||||
discordAppleCovers: 'Hent covere fra Apple Music til Discord',
|
||||
discordAppleCoversDesc: 'Sender artist- og albumnavn til Apples søke-API for å finne cover til Discord-profilen din. Deaktivert som standard av personvernhensyn.',
|
||||
discordCoverSource: 'Coverkilde',
|
||||
discordCoverSourceDesc: 'Hvor albumcoveret for Discord-profilen din hentes fra.',
|
||||
discordCoverNone: 'Ingen (kun app-ikon)',
|
||||
discordCoverServer: 'Server (via albuminfo)',
|
||||
discordCoverApple: 'Apple Music',
|
||||
discordOptions: 'Avanserte Discord-alternativer',
|
||||
discordTemplates: 'Egendefinerte tekstmaler',
|
||||
discordTemplatesDesc: 'Tilpass hvilken informasjon som vises på Discord-profilen din. Variabler: {title}, {artist}, {album}',
|
||||
|
||||
+5
-2
@@ -696,8 +696,11 @@ export const nlTranslation = {
|
||||
linuxWebkitSmoothScrollDesc: 'Aan: traag naloop. Uit: regel voor regel, GTK-stijl.',
|
||||
discordRichPresence: 'Discord Rich Presence',
|
||||
discordRichPresenceDesc: 'Toont het huidige nummer op je Discord-profiel. Discord moet daarvoor geopend zijn.',
|
||||
discordAppleCovers: 'Hoezen ophalen via Apple Music voor Discord',
|
||||
discordAppleCoversDesc: 'Stuurt artiest- en albumnaam naar de Apple-zoek-API om een hoes te vinden voor je Discord-profiel. Standaard uitgeschakeld vanwege privacy.',
|
||||
discordCoverSource: 'Hoesbron',
|
||||
discordCoverSourceDesc: 'Waar de albumhoes voor je Discord-profiel vandaan komt.',
|
||||
discordCoverNone: 'Geen (alleen app-icoon)',
|
||||
discordCoverServer: 'Server (via albuminfo)',
|
||||
discordCoverApple: 'Apple Music',
|
||||
discordOptions: 'Geavanceerde Discord-opties',
|
||||
discordTemplates: 'Aangepaste tekstsjablonen',
|
||||
discordTemplatesDesc: 'Pas aan welke informatie wordt weergegeven op je Discord-profiel. Variabelen: {title}, {artist}, {album}',
|
||||
|
||||
+5
-2
@@ -739,8 +739,11 @@ export const ruTranslation = {
|
||||
discordRichPresence: 'Статус в Discord',
|
||||
discordRichPresenceDesc:
|
||||
'Показывать текущий трек в профиле и статусе Discord. Нужен запущенный клиент Discord.',
|
||||
discordAppleCovers: 'Загружать обложки через Apple Music для Discord',
|
||||
discordAppleCoversDesc: 'Отправляет имя исполнителя и альбома в Apple Music API для поиска обложки для профиля Discord. По умолчанию отключено из соображений конфиденциальности.',
|
||||
discordCoverSource: 'Источник обложки',
|
||||
discordCoverSourceDesc: 'Откуда загружать обложку альбома для профиля Discord.',
|
||||
discordCoverNone: 'Нет (только иконка приложения)',
|
||||
discordCoverServer: 'Сервер (через информацию об альбоме)',
|
||||
discordCoverApple: 'Apple Music',
|
||||
discordOptions: 'Расширенные параметры Discord',
|
||||
discordTemplates: 'Пользовательские шаблоны текста',
|
||||
discordTemplatesDesc: 'Настройте, какая информация отображается в профиле Discord. Переменные: {title}, {artist}, {album}',
|
||||
|
||||
+5
-2
@@ -691,8 +691,11 @@ export const zhTranslation = {
|
||||
linuxWebkitSmoothScrollDesc: '开:惯性滚动。关:逐行,类似 GTK。',
|
||||
discordRichPresence: 'Discord Rich Presence',
|
||||
discordRichPresenceDesc: '在 Discord 个人资料上显示当前播放的曲目。需要 Discord 处于运行状态。',
|
||||
discordAppleCovers: '通过 Apple Music 为 Discord 获取封面',
|
||||
discordAppleCoversDesc: '将艺术家和专辑名称发送至 Apple 搜索 API,以为 Discord 个人资料查找封面图片。出于隐私考虑,默认禁用。',
|
||||
discordCoverSource: '封面来源',
|
||||
discordCoverSourceDesc: '从何处获取 Discord 个人资料上显示的专辑封面。',
|
||||
discordCoverNone: '无(仅显示应用图标)',
|
||||
discordCoverServer: '服务器(通过专辑信息)',
|
||||
discordCoverApple: 'Apple Music',
|
||||
discordOptions: 'Discord 高级选项',
|
||||
discordTemplates: '自定义文本模板',
|
||||
discordTemplatesDesc: '自定义在 Discord 个人资料上显示的信息。变量:{title}, {artist}, {album}',
|
||||
|
||||
+39
-10
@@ -269,6 +269,13 @@ const CONTRIBUTORS = [
|
||||
'WebView2 idle hooks when Tauri windows are hidden — Windows GPU and compositor mitigation (PR #273)',
|
||||
],
|
||||
},
|
||||
{
|
||||
github: 'Sayykii',
|
||||
since: '1.46.0',
|
||||
contributions: [
|
||||
'Discord Rich Presence: cover art from your own server (Subsonic getAlbumInfo2) with three-way picker — none / server / Apple Music (PR #462)',
|
||||
],
|
||||
},
|
||||
{
|
||||
github: 'Psychotoxical',
|
||||
since: '1.0.0',
|
||||
@@ -2803,14 +2810,36 @@ export default function Settings() {
|
||||
</div>
|
||||
{auth.discordRichPresence && (
|
||||
<>
|
||||
<div className="settings-section-divider" />
|
||||
<div className="settings-toggle-row">
|
||||
<div>
|
||||
<div style={{ fontWeight: 500 }}>{t('settings.discordAppleCovers')}</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--text-muted)' }}>{t('settings.discordAppleCoversDesc')}</div>
|
||||
</div>
|
||||
<label className="toggle-switch" aria-label={t('settings.discordAppleCovers')}>
|
||||
<input type="checkbox" checked={auth.enableAppleMusicCoversDiscord} onChange={e => auth.setEnableAppleMusicCoversDiscord(e.target.checked)} />
|
||||
<div className="settings-toggle-row" style={{ padding: '4px var(--space-3) 4px var(--space-6)', fontSize: 13 }}>
|
||||
<div style={{ fontWeight: 500 }}>{t('settings.discordCoverNone')}</div>
|
||||
<label className="toggle-switch" aria-label={t('settings.discordCoverNone')}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={auth.discordCoverSource === 'none'}
|
||||
onChange={e => auth.setDiscordCoverSource(e.target.checked ? 'none' : 'server')}
|
||||
/>
|
||||
<span className="toggle-track" />
|
||||
</label>
|
||||
</div>
|
||||
<div className="settings-toggle-row" style={{ padding: '4px var(--space-3) 4px var(--space-6)', fontSize: 13 }}>
|
||||
<div style={{ fontWeight: 500 }}>{t('settings.discordCoverServer')}</div>
|
||||
<label className="toggle-switch" aria-label={t('settings.discordCoverServer')}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={auth.discordCoverSource === 'server'}
|
||||
onChange={e => auth.setDiscordCoverSource(e.target.checked ? 'server' : 'none')}
|
||||
/>
|
||||
<span className="toggle-track" />
|
||||
</label>
|
||||
</div>
|
||||
<div className="settings-toggle-row" style={{ padding: '4px var(--space-3) 4px var(--space-6)', fontSize: 13 }}>
|
||||
<div style={{ fontWeight: 500 }}>{t('settings.discordCoverApple')}</div>
|
||||
<label className="toggle-switch" aria-label={t('settings.discordCoverApple')}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={auth.discordCoverSource === 'apple'}
|
||||
onChange={e => auth.setDiscordCoverSource(e.target.checked ? 'apple' : 'none')}
|
||||
/>
|
||||
<span className="toggle-track" />
|
||||
</label>
|
||||
</div>
|
||||
@@ -2825,7 +2854,7 @@ export default function Settings() {
|
||||
type="text"
|
||||
value={auth.discordTemplateDetails}
|
||||
onChange={e => auth.setDiscordTemplateDetails(e.target.value)}
|
||||
placeholder="{artist} - {title}"
|
||||
placeholder="{artist}"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group" style={{ marginBottom: '0.75rem' }}>
|
||||
@@ -2835,7 +2864,7 @@ export default function Settings() {
|
||||
type="text"
|
||||
value={auth.discordTemplateState}
|
||||
onChange={e => auth.setDiscordTemplateState(e.target.value)}
|
||||
placeholder="{album}"
|
||||
placeholder="{title}"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
|
||||
+15
-6
@@ -26,6 +26,7 @@ export type SeekbarStyle = 'truewave' | 'pseudowave' | 'linedot' | 'bar' | 'thic
|
||||
export type LoggingMode = 'off' | 'normal' | 'debug';
|
||||
export type NormalizationEngine = 'off' | 'replaygain' | 'loudness';
|
||||
export type AnimationMode = 'full' | 'reduced' | 'static';
|
||||
export type DiscordCoverSource = 'none' | 'apple' | 'server';
|
||||
|
||||
/** Integrated-loudness target presets (Settings + analysis). */
|
||||
export type LoudnessLufsPreset = -16 | -14 | -12 | -10;
|
||||
@@ -136,7 +137,7 @@ interface AuthState {
|
||||
* touch Orbit can hide it so the header stays uncluttered. */
|
||||
showOrbitTrigger: boolean;
|
||||
discordRichPresence: boolean;
|
||||
enableAppleMusicCoversDiscord: boolean;
|
||||
discordCoverSource: DiscordCoverSource;
|
||||
/** Opt-in: fetch upcoming tour dates from Bandsintown for the Now-Playing info panel. */
|
||||
enableBandsintown: boolean;
|
||||
discordTemplateDetails: string;
|
||||
@@ -311,7 +312,7 @@ interface AuthState {
|
||||
setMinimizeToTray: (v: boolean) => void;
|
||||
setShowOrbitTrigger: (v: boolean) => void;
|
||||
setDiscordRichPresence: (v: boolean) => void;
|
||||
setEnableAppleMusicCoversDiscord: (v: boolean) => void;
|
||||
setDiscordCoverSource: (v: DiscordCoverSource) => void;
|
||||
setEnableBandsintown: (v: boolean) => void;
|
||||
setDiscordTemplateDetails: (v: string) => void;
|
||||
setDiscordTemplateState: (v: string) => void;
|
||||
@@ -448,10 +449,10 @@ export const useAuthStore = create<AuthState>()(
|
||||
minimizeToTray: false,
|
||||
showOrbitTrigger: true,
|
||||
discordRichPresence: false,
|
||||
enableAppleMusicCoversDiscord: false,
|
||||
discordCoverSource: 'server',
|
||||
enableBandsintown: false,
|
||||
discordTemplateDetails: '{artist} - {title}',
|
||||
discordTemplateState: '{album}',
|
||||
discordTemplateDetails: '{artist}',
|
||||
discordTemplateState: '{title}',
|
||||
discordTemplateLargeText: '{album}',
|
||||
useCustomTitlebar: false,
|
||||
preloadMiniPlayer: false,
|
||||
@@ -611,7 +612,7 @@ export const useAuthStore = create<AuthState>()(
|
||||
setMinimizeToTray: (v) => set({ minimizeToTray: v }),
|
||||
setShowOrbitTrigger: (v) => set({ showOrbitTrigger: v }),
|
||||
setDiscordRichPresence: (v) => set({ discordRichPresence: v }),
|
||||
setEnableAppleMusicCoversDiscord: (v) => set({ enableAppleMusicCoversDiscord: v }),
|
||||
setDiscordCoverSource: (v) => set({ discordCoverSource: v }),
|
||||
setEnableBandsintown: (v) => set({ enableBandsintown: v }),
|
||||
setDiscordTemplateDetails: (v) => set({ discordTemplateDetails: v }),
|
||||
setDiscordTemplateState: (v) => set({ discordTemplateState: v }),
|
||||
@@ -864,6 +865,13 @@ export const useAuthStore = create<AuthState>()(
|
||||
)
|
||||
: DEFAULT_LOUDNESS_PRE_ANALYSIS_ATTENUATION_DB);
|
||||
|
||||
// Migrate enableAppleMusicCoversDiscord boolean → discordCoverSource enum.
|
||||
let discordCoverSourceMigrated: { discordCoverSource?: DiscordCoverSource } = {};
|
||||
const legacyAppleCovers = (state as { enableAppleMusicCoversDiscord?: unknown }).enableAppleMusicCoversDiscord;
|
||||
if (legacyAppleCovers === true && (!state.discordCoverSource || state.discordCoverSource === 'none')) {
|
||||
discordCoverSourceMigrated = { discordCoverSource: 'apple' };
|
||||
}
|
||||
|
||||
useAuthStore.setState({
|
||||
mixMinRatingSong: clampMixFilterMinStars(state.mixMinRatingSong as number),
|
||||
mixMinRatingAlbum: clampMixFilterMinStars(state.mixMinRatingAlbum as number),
|
||||
@@ -880,6 +888,7 @@ export const useAuthStore = create<AuthState>()(
|
||||
...wheelSmoothOneTime,
|
||||
...seekbarStyleMigrated,
|
||||
...animationModeMigrated,
|
||||
...discordCoverSourceMigrated,
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
+38
-19
@@ -3,7 +3,7 @@ import { persist, createJSONStorage } from 'zustand/middleware';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import { showToast } from '../utils/toast';
|
||||
import { buildCoverArtUrl, buildStreamUrl, getPlayQueue, savePlayQueue, reportNowPlaying, scrobbleSong, SubsonicSong, getSong, getRandomSongs, getSimilarSongs2, getTopSongs, InternetRadioStation, setRating } from '../api/subsonic';
|
||||
import { buildCoverArtUrl, buildStreamUrl, getPlayQueue, savePlayQueue, reportNowPlaying, scrobbleSong, SubsonicSong, getSong, getRandomSongs, getSimilarSongs2, getTopSongs, InternetRadioStation, setRating, getAlbumInfo2 } from '../api/subsonic';
|
||||
import { resolvePlaybackUrl, streamUrlTrackId, getPlaybackSourceKind, type PlaybackSourceKind } from '../utils/resolvePlaybackUrl';
|
||||
import { redactSubsonicUrlForLog } from '../utils/redactSubsonicUrl';
|
||||
import { setDeferHotCachePrefetch } from '../utils/hotCacheGate';
|
||||
@@ -1968,13 +1968,15 @@ export function initAudioListeners(): () => void {
|
||||
let discordPrevTemplateDetails: string | null = null;
|
||||
let discordPrevTemplateState: string | null = null;
|
||||
let discordPrevTemplateLargeText: string | null = null;
|
||||
let discordPrevCoverSource: string | null = null;
|
||||
const discordServerCoverCache = new Map<string, string | null>();
|
||||
|
||||
function syncDiscord() {
|
||||
const { currentTrack, isPlaying } = usePlayerStore.getState();
|
||||
const currentTime = getPlaybackProgressSnapshot().currentTime;
|
||||
const {
|
||||
discordRichPresence,
|
||||
enableAppleMusicCoversDiscord,
|
||||
discordCoverSource,
|
||||
discordTemplateDetails,
|
||||
discordTemplateState,
|
||||
discordTemplateLargeText,
|
||||
@@ -1985,6 +1987,7 @@ export function initAudioListeners(): () => void {
|
||||
discordPrevTrackId = null;
|
||||
discordPrevIsPlaying = null;
|
||||
discordPrevFetchCovers = null;
|
||||
discordPrevCoverSource = null;
|
||||
discordPrevTemplateDetails = null;
|
||||
discordPrevTemplateState = null;
|
||||
discordPrevTemplateLargeText = null;
|
||||
@@ -1995,33 +1998,49 @@ export function initAudioListeners(): () => void {
|
||||
|
||||
const trackChanged = currentTrack.id !== discordPrevTrackId;
|
||||
const playingChanged = isPlaying !== discordPrevIsPlaying;
|
||||
const coversSettingChanged = enableAppleMusicCoversDiscord !== discordPrevFetchCovers;
|
||||
const coverSourceChanged = discordCoverSource !== discordPrevCoverSource;
|
||||
const detailsTemplateChanged = discordTemplateDetails !== discordPrevTemplateDetails;
|
||||
const stateTemplateChanged = discordTemplateState !== discordPrevTemplateState;
|
||||
const largeTextTemplateChanged = discordTemplateLargeText !== discordPrevTemplateLargeText;
|
||||
if (!trackChanged && !playingChanged && !coversSettingChanged && !detailsTemplateChanged && !stateTemplateChanged && !largeTextTemplateChanged) return;
|
||||
if (!trackChanged && !playingChanged && !coverSourceChanged && !detailsTemplateChanged && !stateTemplateChanged && !largeTextTemplateChanged) return;
|
||||
|
||||
discordPrevTrackId = currentTrack.id;
|
||||
discordPrevIsPlaying = isPlaying;
|
||||
discordPrevFetchCovers = enableAppleMusicCoversDiscord;
|
||||
discordPrevFetchCovers = discordCoverSource === 'apple';
|
||||
discordPrevCoverSource = discordCoverSource;
|
||||
discordPrevTemplateDetails = discordTemplateDetails;
|
||||
discordPrevTemplateState = discordTemplateState;
|
||||
discordPrevTemplateLargeText = discordTemplateLargeText;
|
||||
|
||||
invoke('discord_update_presence', {
|
||||
title: currentTrack.title,
|
||||
artist: currentTrack.artist ?? 'Unknown Artist',
|
||||
album: currentTrack.album ?? null,
|
||||
isPlaying,
|
||||
elapsedSecs: isPlaying ? currentTime : null,
|
||||
// coverArtUrl is intentionally not passed — Subsonic URLs require auth.
|
||||
// iTunes cover fetching is only done when explicitly opted in.
|
||||
coverArtUrl: null,
|
||||
fetchItunesCovers: enableAppleMusicCoversDiscord,
|
||||
detailsTemplate: discordTemplateDetails,
|
||||
stateTemplate: discordTemplateState,
|
||||
largeTextTemplate: discordTemplateLargeText,
|
||||
}).catch(() => {});
|
||||
const sendPresence = (coverArtUrl: string | null) => {
|
||||
invoke('discord_update_presence', {
|
||||
title: currentTrack.title,
|
||||
artist: currentTrack.artist ?? 'Unknown Artist',
|
||||
album: currentTrack.album ?? null,
|
||||
isPlaying,
|
||||
elapsedSecs: isPlaying ? currentTime : null,
|
||||
coverArtUrl,
|
||||
fetchItunesCovers: discordCoverSource === 'apple',
|
||||
detailsTemplate: discordTemplateDetails,
|
||||
stateTemplate: discordTemplateState,
|
||||
largeTextTemplate: discordTemplateLargeText,
|
||||
}).catch(() => {});
|
||||
};
|
||||
|
||||
if (discordCoverSource === 'server' && currentTrack.albumId) {
|
||||
const cached = discordServerCoverCache.get(currentTrack.albumId);
|
||||
if (cached !== undefined) {
|
||||
sendPresence(cached);
|
||||
} else {
|
||||
getAlbumInfo2(currentTrack.albumId).then(info => {
|
||||
const url = info?.largeImageUrl || info?.mediumImageUrl || info?.smallImageUrl || null;
|
||||
discordServerCoverCache.set(currentTrack.albumId, url);
|
||||
sendPresence(url);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
sendPresence(null);
|
||||
}
|
||||
}
|
||||
|
||||
const unsubDiscordPlayer = usePlayerStore.subscribe(syncDiscord);
|
||||
|
||||
Reference in New Issue
Block a user