From 657682fc5ef4aba0ffb59c7ae3632746b6a542d7 Mon Sep 17 00:00:00 2001 From: vicrodh <103399283+vicrodh@users.noreply.github.com> Date: Fri, 9 Jan 2026 18:50:30 -0600 Subject: [PATCH] fix(share): use Qobuz track URL for song.link instead of ISRC Odesli API doesn't support direct ISRC lookup. Changed to use Qobuz track URL (https://open.qobuz.com/track/{id}) which Odesli can properly resolve to song.link. --- src-tauri/src/commands/share.rs | 19 +++++-------------- src-tauri/src/share/songlink.rs | 16 +++++++++------- src/routes/+page.svelte | 32 +++++++------------------------- 3 files changed, 21 insertions(+), 46 deletions(-) diff --git a/src-tauri/src/commands/share.rs b/src-tauri/src/commands/share.rs index e42490649..97fab9e30 100644 --- a/src-tauri/src/commands/share.rs +++ b/src-tauri/src/commands/share.rs @@ -2,29 +2,20 @@ use tauri::State; -use crate::share::{ShareError, SongLinkClient, SongLinkResponse}; +use crate::share::{ShareError, SongLinkResponse}; use crate::AppState; -/// Get song.link URL for a track -/// Requires ISRC to be present in the track metadata +/// Get song.link URL for a track using Qobuz track ID #[tauri::command] pub async fn share_track_songlink( - isrc: Option, + track_id: u64, state: State<'_, AppState>, ) -> Result { - let isrc = isrc.ok_or_else(|| { - ShareError::MissingIsrc.to_string() - })?; - - if isrc.is_empty() { - return Err(ShareError::MissingIsrc.to_string()); - } - - log::info!("Command: share_track_songlink ISRC={}", isrc); + log::info!("Command: share_track_songlink track_id={}", track_id); state .songlink - .get_by_isrc(&isrc) + .get_by_track_id(&track_id.to_string()) .await .map_err(|e| e.to_string()) } diff --git a/src-tauri/src/share/songlink.rs b/src-tauri/src/share/songlink.rs index 465c706e2..2c85b9230 100644 --- a/src-tauri/src/share/songlink.rs +++ b/src-tauri/src/share/songlink.rs @@ -48,22 +48,24 @@ impl SongLinkClient { } } - /// Get song.link URL for a track by ISRC - pub async fn get_by_isrc(&self, isrc: &str) -> Result { - let cache_key = format!("isrc:{}", isrc); + /// Get song.link URL for a track by Qobuz track ID + pub async fn get_by_track_id(&self, track_id: &str) -> Result { + let cache_key = format!("track:{}", track_id); // Check cache first if let Some(cached) = self.get_from_cache(&cache_key) { - log::debug!("Cache hit for ISRC: {}", isrc); + log::debug!("Cache hit for track ID: {}", track_id); return Ok(cached); } - log::info!("Fetching song.link for ISRC: {}", isrc); + // Use Qobuz track URL for Odesli lookup + let qobuz_url = format!("https://open.qobuz.com/track/{}", track_id); + log::info!("Fetching song.link for Qobuz track: {}", qobuz_url); let response = self .client .get(ODESLI_API_URL) - .query(&[("isrc", isrc), ("userCountry", "US")]) + .query(&[("url", &qobuz_url), ("userCountry", &"US".to_string())]) .send() .await?; @@ -77,7 +79,7 @@ impl SongLinkClient { } let odesli: OdesliResponse = response.json().await?; - let result = self.convert_response(odesli, isrc.to_string(), ContentType::Track)?; + let result = self.convert_response(odesli, track_id.to_string(), ContentType::Track)?; // Cache the result self.store_in_cache(cache_key, result.clone()); diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index e3231345e..e556ad564 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -472,28 +472,10 @@ } } - async function resolveTrackIsrc(trackId: number, isrc?: string): Promise { - if (isrc && isrc.trim()) { - return isrc; - } - try { - const fullTrack = await invoke('get_track', { trackId }); - return fullTrack.isrc || null; - } catch (err) { - console.error('Failed to fetch track ISRC:', err); - return null; - } - } - - async function shareSonglinkTrack(trackId: number, isrc?: string) { - const resolvedIsrc = await resolveTrackIsrc(trackId, isrc); - if (!resolvedIsrc) { - showToast('ISRC not available for this track', 'error'); - return; - } + async function shareSonglinkTrack(trackId: number) { try { showToast('Fetching Song.link...', 'info'); - const response = await invoke('share_track_songlink', { isrc: resolvedIsrc }); + const response = await invoke('share_track_songlink', { trackId }); await copyToClipboard(response.pageUrl, 'Song.link copied'); } catch (err) { console.error('Failed to get Song.link:', err); @@ -1422,7 +1404,7 @@ onTrackAddFavorite={addTrackToFavorites} onTrackAddToPlaylist={(trackId) => openAddToPlaylist([trackId])} onTrackShareQobuz={shareQobuzTrackLink} - onTrackShareSonglink={(track) => shareSonglinkTrack(track.id, track.isrc)} + onTrackShareSonglink={(track) => shareSonglinkTrack(track.id)} onTrackGoToAlbum={handleAlbumClick} onTrackGoToArtist={handleArtistClick} onArtistClick={handleArtistClick} @@ -1444,7 +1426,7 @@ onTrackPlayLater={handleAlbumTrackPlayLater} onTrackAddFavorite={addTrackToFavorites} onTrackShareQobuz={shareQobuzTrackLink} - onTrackShareSonglink={(track) => shareSonglinkTrack(track.id, track.isrc)} + onTrackShareSonglink={(track) => shareSonglinkTrack(track.id)} onTrackGoToAlbum={handleAlbumClick} onTrackGoToArtist={handleArtistClick} onPlayAll={handlePlayAllAlbum} @@ -1463,7 +1445,7 @@ onTrackAddFavorite={addTrackToFavorites} onTrackAddToPlaylist={(trackId) => openAddToPlaylist([trackId])} onTrackShareQobuz={shareQobuzTrackLink} - onTrackShareSonglink={(track) => shareSonglinkTrack(track.id, track.isrc)} + onTrackShareSonglink={(track) => shareSonglinkTrack(track.id)} onTrackGoToAlbum={handleAlbumClick} onTrackGoToArtist={handleArtistClick} /> @@ -1484,7 +1466,7 @@ onTrackAddFavorite={addTrackToFavorites} onTrackAddToPlaylist={(trackId) => openAddToPlaylist([trackId])} onTrackShareQobuz={shareQobuzTrackLink} - onTrackShareSonglink={(track) => shareSonglinkTrack(track.id, track.isrc)} + onTrackShareSonglink={(track) => shareSonglinkTrack(track.id)} onTrackGoToAlbum={handleAlbumClick} onTrackGoToArtist={handleArtistClick} /> @@ -1498,7 +1480,7 @@ onTrackAddFavorite={addTrackToFavorites} onTrackAddToPlaylist={(trackId) => openAddToPlaylist([trackId])} onTrackShareQobuz={shareQobuzTrackLink} - onTrackShareSonglink={(track) => shareSonglinkTrack(track.id, track.isrc)} + onTrackShareSonglink={(track) => shareSonglinkTrack(track.id)} onTrackGoToAlbum={handleAlbumClick} onTrackGoToArtist={handleArtistClick} />