feat(discord): show track title in Discord member list (configurable name template) (#885)

* feat(discord): override activity name in member list with track title (configurable template)

The Discord member list and the collapsed Rich Presence card display the
activity's `name` field next to the music icon. Without an override
Discord falls back to the registered application name ("Psysonic"), so
the line reads "Psysonic" while every track plays instead of the actual
track title that comparable players show.

Add a fourth user-configurable template `discordTemplateName` (Settings ->
Integrations -> Discord Rich Presence). The Rust side passes it through
`Activity::new().name(...)` when the rendered template is non-empty; an
empty template falls back to Discord's default application name. Default
template is "{title}".

Tauri boundary: additive optional `nameTemplate` field on the existing
`discord_update_presence` command. Existing call sites that omit it keep
working -- the Rust handler applies the same "{title}" fallback.

* i18n(settings): discord name template label in all locales

Adds the discordTemplateName label across en, de, fr, es, nl, nb, ro,
zh, ru -- shown next to the new "User list line (name)" template input
under Discord Rich Presence.

* docs(release): CHANGELOG and credits for discord name template (PR #885)
This commit is contained in:
Frank Stellmacher
2026-05-28 20:32:41 +02:00
committed by GitHub
parent 403979b35d
commit 455aec4def
18 changed files with 70 additions and 6 deletions
@@ -202,6 +202,16 @@ export function IntegrationsTab() {
<div style={{ paddingTop: 8 }}>
<div style={{ fontWeight: 500, fontSize: 13, marginBottom: 8 }}>{t('settings.discordTemplates')}</div>
<div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 12 }}>{t('settings.discordTemplatesDesc')}</div>
<div className="form-group" style={{ marginBottom: '0.75rem' }}>
<label style={{ fontSize: 12 }}>{t('settings.discordTemplateName')}</label>
<input
className="input"
type="text"
value={auth.discordTemplateName}
onChange={e => auth.setDiscordTemplateName(e.target.value)}
placeholder="{title}"
/>
</div>
<div className="form-group" style={{ marginBottom: '0.75rem' }}>
<label style={{ fontSize: 12 }}>{t('settings.discordTemplateDetails')}</label>
<input
+1
View File
@@ -337,6 +337,7 @@ const CONTRIBUTOR_ENTRIES = [
'Local library index (preview): SQLite per-server track store, background initial and delta sync, live and Advanced Search against the local index, integrity verify and auto-reconcile on count drop (PR #846)',
'Server index-key rebuild: safe dual-DB migration flow, per-server analysis strategy controls, and playback/index scope hardening (PR #864)',
'Settings: opt-in Linux input-focus repaint — workaround for the WebKitGTK 2.50.x text-field freeze (PR #884)',
'Discord Rich Presence: configurable activity-name template — member list shows the playing track instead of "Psysonic" (PR #885)',
],
},
] as const;
+1
View File
@@ -225,6 +225,7 @@ export const settings = {
discordTemplateDetails: 'Primäre Zeile (details)',
discordTemplateState: 'Sekundäre Zeile (state)',
discordTemplateLargeText: 'Album-Tooltip (largeText)',
discordTemplateName: 'Userliste-Zeile (name)',
nowPlayingEnabled: 'Im Livefenster anzeigen',
nowPlayingEnabledDesc: 'Überträgt den aktuell gespielten Titel an die Livehörer-Ansicht des Servers. Deaktivieren, um keine Wiedergabedaten zu senden.',
enableBandsintown: 'Bandsintown-Tourdaten',
+1
View File
@@ -248,6 +248,7 @@ export const settings = {
discordTemplateDetails: 'Primary line (details)',
discordTemplateState: 'Secondary line (state)',
discordTemplateLargeText: 'Album tooltip (largeText)',
discordTemplateName: 'User list line (name)',
nowPlayingEnabled: 'Show in Now Playing',
nowPlayingEnabledDesc: 'Broadcast your currently playing track to the server\'s live listener view. Disable to stop sending playback data.',
enableBandsintown: 'Bandsintown tour dates',
+1
View File
@@ -224,6 +224,7 @@ export const settings = {
discordTemplateDetails: 'Línea principal (details)',
discordTemplateState: 'Línea secundaria (state)',
discordTemplateLargeText: 'Tooltip del álbum (largeText)',
discordTemplateName: 'Línea de la lista (name)',
nowPlayingEnabled: 'Mostrar en Reproduciendo Ahora',
nowPlayingEnabledDesc: 'Transmite tu pista actual a la vista de oyentes en vivo del servidor. Desactiva para dejar de enviar datos de reproducción.',
enableBandsintown: 'Fechas de gira de Bandsintown',
+1
View File
@@ -222,6 +222,7 @@ export const settings = {
discordTemplateDetails: 'Ligne principale (details)',
discordTemplateState: 'Ligne secondaire (state)',
discordTemplateLargeText: 'Info-bulle album (largeText)',
discordTemplateName: 'Ligne de la liste (name)',
nowPlayingEnabled: 'Afficher dans la fenêtre live',
nowPlayingEnabledDesc: 'Diffuse le titre en cours de lecture vers la vue des auditeurs en direct du serveur. Désactiver pour ne pas envoyer de données de lecture.',
enableBandsintown: 'Dates de tournée Bandsintown',
+1
View File
@@ -221,6 +221,7 @@ export const settings = {
discordTemplateDetails: 'Primær linje (details)',
discordTemplateState: 'Sekundær linje (state)',
discordTemplateLargeText: 'Album-verktøytips (largeText)',
discordTemplateName: 'Brukerliste-linje (name)',
nowPlayingEnabled: 'Vis i "Nå spiller"',
nowPlayingEnabledDesc: 'Send sporet som spilles av til tjenerens live-lyttervisning. Deaktiver for å stoppe sending av avspillingsdata.',
enableBandsintown: 'Bandsintown-turnédatoer',
+1
View File
@@ -222,6 +222,7 @@ export const settings = {
discordTemplateDetails: 'Primaire regel (details)',
discordTemplateState: 'Secundaire regel (state)',
discordTemplateLargeText: 'Album-tooltip (largeText)',
discordTemplateName: 'Gebruikerslijst-regel (name)',
nowPlayingEnabled: 'Weergeven in live-venster',
nowPlayingEnabledDesc: 'Stuurt het huidige nummer naar de live-luisteraarsweergave van de server. Uitschakelen om geen afspeelgegevens te verzenden.',
enableBandsintown: 'Bandsintown-tourdata',
+1
View File
@@ -227,6 +227,7 @@ export const settings = {
discordTemplateDetails: 'Linia principală (detalii)',
discordTemplateState: 'Linia secundară (status)',
discordTemplateLargeText: 'Tooltip album (largeText)',
discordTemplateName: 'Linia din listă (name)',
nowPlayingEnabled: 'Se afișează în Now Playing',
nowPlayingEnabledDesc: 'Difuzează piesa redată curent către vizualizatorul de ascultare live a serverului. Dezactivează pentru a opri trimiterea datelor de redare.',
enableBandsintown: 'Date de turneu Bandsintown',
+1
View File
@@ -252,6 +252,7 @@ export const settings = {
discordTemplateDetails: 'Основная строка (details)',
discordTemplateState: 'Вторичная строка (state)',
discordTemplateLargeText: 'Подсказка альбома (largeText)',
discordTemplateName: 'Строка в списке пользователей (name)',
nowPlayingEnabled: 'Показывать в «Сейчас играет»',
nowPlayingEnabledDesc:
'Отправлять на сервер, что вы сейчас слушаете. Отключите, чтобы не делиться этим.',
+1
View File
@@ -221,6 +221,7 @@ export const settings = {
discordTemplateDetails: '主行 (details)',
discordTemplateState: '副行 (state)',
discordTemplateLargeText: '专辑提示 (largeText)',
discordTemplateName: '用户列表行 (name)',
nowPlayingEnabled: '在实时窗口中显示',
nowPlayingEnabledDesc: '将当前播放的曲目广播到服务器的实时听众视图。禁用以停止发送播放数据。',
enableBandsintown: 'Bandsintown 巡演日期',
@@ -18,6 +18,7 @@ export function setupDiscordPresence(): () => void {
let discordPrevTemplateDetails: string | null = null;
let discordPrevTemplateState: string | null = null;
let discordPrevTemplateLargeText: string | null = null;
let discordPrevTemplateName: string | null = null;
let discordPrevCoverSource: string | null = null;
const discordServerCoverCache = new Map<string, string | null>();
@@ -30,6 +31,7 @@ export function setupDiscordPresence(): () => void {
discordTemplateDetails,
discordTemplateState,
discordTemplateLargeText,
discordTemplateName,
} = useAuthStore.getState();
if (!discordRichPresence || !currentTrack) {
@@ -41,6 +43,7 @@ export function setupDiscordPresence(): () => void {
discordPrevTemplateDetails = null;
discordPrevTemplateState = null;
discordPrevTemplateLargeText = null;
discordPrevTemplateName = null;
invoke('discord_clear_presence').catch(() => {});
}
return;
@@ -52,7 +55,8 @@ export function setupDiscordPresence(): () => void {
const detailsTemplateChanged = discordTemplateDetails !== discordPrevTemplateDetails;
const stateTemplateChanged = discordTemplateState !== discordPrevTemplateState;
const largeTextTemplateChanged = discordTemplateLargeText !== discordPrevTemplateLargeText;
if (!trackChanged && !playingChanged && !coverSourceChanged && !detailsTemplateChanged && !stateTemplateChanged && !largeTextTemplateChanged) return;
const nameTemplateChanged = discordTemplateName !== discordPrevTemplateName;
if (!trackChanged && !playingChanged && !coverSourceChanged && !detailsTemplateChanged && !stateTemplateChanged && !largeTextTemplateChanged && !nameTemplateChanged) return;
discordPrevTrackId = currentTrack.id;
discordPrevIsPlaying = isPlaying;
@@ -61,6 +65,7 @@ export function setupDiscordPresence(): () => void {
discordPrevTemplateDetails = discordTemplateDetails;
discordPrevTemplateState = discordTemplateState;
discordPrevTemplateLargeText = discordTemplateLargeText;
discordPrevTemplateName = discordTemplateName;
const sendPresence = (coverArtUrl: string | null) => {
invoke('discord_update_presence', {
@@ -74,6 +79,7 @@ export function setupDiscordPresence(): () => void {
detailsTemplate: discordTemplateDetails,
stateTemplate: discordTemplateState,
largeTextTemplate: discordTemplateLargeText,
nameTemplate: discordTemplateName,
}).catch(() => {});
};
+2
View File
@@ -12,6 +12,7 @@ export function createDiscordSettingsActions(set: SetState): Pick<
| 'setDiscordTemplateDetails'
| 'setDiscordTemplateState'
| 'setDiscordTemplateLargeText'
| 'setDiscordTemplateName'
> {
return {
setDiscordRichPresence: (v) => set({ discordRichPresence: v }),
@@ -20,5 +21,6 @@ export function createDiscordSettingsActions(set: SetState): Pick<
setDiscordTemplateDetails: (v) => set({ discordTemplateDetails: v }),
setDiscordTemplateState: (v) => set({ discordTemplateState: v }),
setDiscordTemplateLargeText: (v) => set({ discordTemplateLargeText: v }),
setDiscordTemplateName: (v) => set({ discordTemplateName: v }),
};
}
+1
View File
@@ -98,6 +98,7 @@ describe('trivial pass-through setters', () => {
['setDiscordTemplateDetails', 'discordTemplateDetails', '{artist} — {title}'],
['setDiscordTemplateState', 'discordTemplateState', '{album}'],
['setDiscordTemplateLargeText', 'discordTemplateLargeText', 'Hi'],
['setDiscordTemplateName', 'discordTemplateName', '{title} — {artist}'],
])('%s stores a string value', (setter, key, value) => {
(useAuthStore.getState() as unknown as Record<string, (v: unknown) => void>)[setter](value);
expect((useAuthStore.getState() as unknown as Record<string, unknown>)[key]).toBe(value);
+1
View File
@@ -75,6 +75,7 @@ export const useAuthStore = create<AuthState>()(
discordTemplateDetails: '{artist}',
discordTemplateState: '{title}',
discordTemplateLargeText: '{album}',
discordTemplateName: '{title}',
useCustomTitlebar: false,
preloadMiniPlayer: false,
linuxWebkitKineticScroll: true,
+5
View File
@@ -128,6 +128,10 @@ export interface AuthState {
discordTemplateDetails: string;
discordTemplateState: string;
discordTemplateLargeText: string;
/** Template for Discord activity name (overrides the registered application
* name in the user list / collapsed presence). Default "{title}".
* Empty string falls back to "Psysonic". */
discordTemplateName: string;
useCustomTitlebar: boolean;
/** Pre-build the mini-player webview at app start on Linux/macOS so content is available instantly
* on first open. Ignored on Windows — that platform always pre-creates as a hang workaround. */
@@ -311,6 +315,7 @@ export interface AuthState {
setDiscordTemplateDetails: (v: string) => void;
setDiscordTemplateState: (v: string) => void;
setDiscordTemplateLargeText: (v: string) => void;
setDiscordTemplateName: (v: string) => void;
setUseCustomTitlebar: (v: boolean) => void;
setPreloadMiniPlayer: (v: boolean) => void;
setLinuxWebkitKineticScroll: (v: boolean) => void;