mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
release
This commit is contained in:
+34
-15
@@ -216,6 +216,19 @@ fn try_connect() -> Option<DiscordIpcClient> {
|
||||
Some(client)
|
||||
}
|
||||
|
||||
/// Apply a template string, replacing placeholders with actual values.
|
||||
/// Supported placeholders: {title}, {artist}, {album}, {paused}
|
||||
fn apply_template(template: &str, title: &str, artist: &str, album: Option<&str>, is_playing: bool) -> String {
|
||||
let paused_text = if is_playing { "" } else { "Paused — " };
|
||||
let album_text = album.unwrap_or("");
|
||||
|
||||
template
|
||||
.replace("{title}", title)
|
||||
.replace("{artist}", artist)
|
||||
.replace("{album}", album_text)
|
||||
.replace("{paused}", paused_text)
|
||||
}
|
||||
|
||||
/// Update the Discord Rich Presence activity.
|
||||
///
|
||||
/// - `is_playing`: true = playing (timer shown), false = paused (no timer, state shows "Paused").
|
||||
@@ -225,6 +238,12 @@ fn try_connect() -> Option<DiscordIpcClient> {
|
||||
/// - `fetch_itunes_covers`: if true, fetch artwork from the iTunes Search API when no
|
||||
/// `cover_art_url` is provided. If false (default), fall back to the Psysonic app icon
|
||||
/// without making any external request — required for privacy opt-in.
|
||||
/// - `details_template`: template string for the "details" field. Default: "{artist} - {title}".
|
||||
/// Supported placeholders: {title}, {artist}, {album}, {paused}
|
||||
/// - `state_template`: template string for the "state" field. Default: "{album}".
|
||||
/// Supported placeholders: {title}, {artist}, {album}, {paused}
|
||||
/// - `large_text_template`: template string for the large image tooltip. Default: "{album}".
|
||||
/// Supported placeholders: {title}, {artist}, {album}, {paused}
|
||||
#[tauri::command]
|
||||
pub async fn discord_update_presence(
|
||||
state: tauri::State<'_, DiscordState>,
|
||||
@@ -235,6 +254,9 @@ pub async fn discord_update_presence(
|
||||
elapsed_secs: Option<f64>,
|
||||
cover_art_url: Option<String>,
|
||||
fetch_itunes_covers: bool,
|
||||
details_template: Option<String>,
|
||||
state_template: Option<String>,
|
||||
large_text_template: Option<String>,
|
||||
) -> Result<(), String> {
|
||||
// Resolve artwork on a dedicated blocking thread — reqwest::blocking must not
|
||||
// run on the Tokio async executor directly.
|
||||
@@ -273,28 +295,25 @@ pub async fn discord_update_presence(
|
||||
|
||||
let client = guard.as_mut().unwrap();
|
||||
|
||||
// Discord RPC only exposes two visible text rows (details + state).
|
||||
// The application name "Psysonic" is shown automatically by Discord as the
|
||||
// header line. Album goes into large_text — visible as a hover tooltip on
|
||||
// the cover art icon.
|
||||
let large_text = album.as_deref().unwrap_or("Psysonic");
|
||||
// Apply templates for the three configurable text fields.
|
||||
let details_str = details_template.as_deref().unwrap_or("{artist} - {title}");
|
||||
let details_text = apply_template(details_str, &title, &artist, album.as_deref(), is_playing);
|
||||
|
||||
let state_str = state_template.as_deref().unwrap_or("{album}");
|
||||
let state_text = apply_template(state_str, &title, &artist, album.as_deref(), is_playing);
|
||||
|
||||
let large_text_str = large_text_template.as_deref().unwrap_or("{album}");
|
||||
let large_text = apply_template(large_text_str, &title, &artist, album.as_deref(), is_playing);
|
||||
|
||||
let assets = if let Some(ref url) = artwork_url {
|
||||
Assets::new()
|
||||
.large_image(url.as_str())
|
||||
.large_text(large_text)
|
||||
.large_text(&large_text)
|
||||
} else {
|
||||
// Fallback to default Psysonic icon
|
||||
Assets::new()
|
||||
.large_image("psysonic")
|
||||
.large_text(large_text)
|
||||
};
|
||||
|
||||
// When paused, show "Paused" as the state text (replaces artist name).
|
||||
let state_text: String = if is_playing {
|
||||
artist.clone()
|
||||
} else {
|
||||
"Paused".to_string()
|
||||
.large_text(&large_text)
|
||||
};
|
||||
|
||||
// ActivityType::Listening causes the Discord client to auto-start a running
|
||||
@@ -308,7 +327,7 @@ pub async fn discord_update_presence(
|
||||
|
||||
let mut activity = Activity::new()
|
||||
.activity_type(activity_type)
|
||||
.details(&title)
|
||||
.details(&details_text)
|
||||
.state(&state_text)
|
||||
.assets(assets);
|
||||
|
||||
|
||||
+1136
-36
File diff suppressed because it is too large
Load Diff
+1139
-36
File diff suppressed because it is too large
Load Diff
+1143
-36
File diff suppressed because it is too large
Load Diff
+1138
-36
File diff suppressed because it is too large
Load Diff
+1137
-36
File diff suppressed because it is too large
Load Diff
+1137
-36
File diff suppressed because it is too large
Load Diff
+1196
-36
File diff suppressed because it is too large
Load Diff
+1131
-36
File diff suppressed because it is too large
Load Diff
+3217
-372
File diff suppressed because it is too large
Load Diff
+680
-63
File diff suppressed because it is too large
Load Diff
@@ -689,16 +689,28 @@ export function initAudioListeners(): () => void {
|
||||
let discordPrevTrackId: string | null = null;
|
||||
let discordPrevIsPlaying: boolean | null = null;
|
||||
let discordPrevFetchCovers: boolean | null = null;
|
||||
let discordPrevTemplateDetails: string | null = null;
|
||||
let discordPrevTemplateState: string | null = null;
|
||||
let discordPrevTemplateLargeText: string | null = null;
|
||||
|
||||
function syncDiscord() {
|
||||
const { currentTrack, isPlaying, currentTime } = usePlayerStore.getState();
|
||||
const { discordRichPresence, enableAppleMusicCoversDiscord } = useAuthStore.getState();
|
||||
const {
|
||||
discordRichPresence,
|
||||
enableAppleMusicCoversDiscord,
|
||||
discordTemplateDetails,
|
||||
discordTemplateState,
|
||||
discordTemplateLargeText,
|
||||
} = useAuthStore.getState();
|
||||
|
||||
if (!discordRichPresence || !currentTrack) {
|
||||
if (discordPrevTrackId !== null) {
|
||||
discordPrevTrackId = null;
|
||||
discordPrevIsPlaying = null;
|
||||
discordPrevFetchCovers = null;
|
||||
discordPrevTemplateDetails = null;
|
||||
discordPrevTemplateState = null;
|
||||
discordPrevTemplateLargeText = null;
|
||||
invoke('discord_clear_presence').catch(() => {});
|
||||
}
|
||||
return;
|
||||
@@ -707,11 +719,17 @@ export function initAudioListeners(): () => void {
|
||||
const trackChanged = currentTrack.id !== discordPrevTrackId;
|
||||
const playingChanged = isPlaying !== discordPrevIsPlaying;
|
||||
const coversSettingChanged = enableAppleMusicCoversDiscord !== discordPrevFetchCovers;
|
||||
if (!trackChanged && !playingChanged && !coversSettingChanged) return;
|
||||
const detailsTemplateChanged = discordTemplateDetails !== discordPrevTemplateDetails;
|
||||
const stateTemplateChanged = discordTemplateState !== discordPrevTemplateState;
|
||||
const largeTextTemplateChanged = discordTemplateLargeText !== discordPrevTemplateLargeText;
|
||||
if (!trackChanged && !playingChanged && !coversSettingChanged && !detailsTemplateChanged && !stateTemplateChanged && !largeTextTemplateChanged) return;
|
||||
|
||||
discordPrevTrackId = currentTrack.id;
|
||||
discordPrevIsPlaying = isPlaying;
|
||||
discordPrevFetchCovers = enableAppleMusicCoversDiscord;
|
||||
discordPrevTemplateDetails = discordTemplateDetails;
|
||||
discordPrevTemplateState = discordTemplateState;
|
||||
discordPrevTemplateLargeText = discordTemplateLargeText;
|
||||
|
||||
invoke('discord_update_presence', {
|
||||
title: currentTrack.title,
|
||||
@@ -725,6 +743,9 @@ export function initAudioListeners(): () => void {
|
||||
// iTunes cover fetching is only done when explicitly opted in.
|
||||
coverArtUrl: null,
|
||||
fetchItunesCovers: enableAppleMusicCoversDiscord,
|
||||
detailsTemplate: discordTemplateDetails,
|
||||
stateTemplate: discordTemplateState,
|
||||
largeTextTemplate: discordTemplateLargeText,
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user