diff --git a/public/index.html b/public/index.html index f19202d..2614ce4 100644 --- a/public/index.html +++ b/public/index.html @@ -243,7 +243,7 @@ - + diff --git a/public/search.js b/public/search.js index 7a6590e..d99af6e 100644 --- a/public/search.js +++ b/public/search.js @@ -112,6 +112,28 @@ function formatDuration(sec) { return `${m}:${s < 10 ? '0' : ''}${s}`; } +function getCleanSearchTerm(artist, title) { + const raw = `${artist || ''} ${title || ''}`; + return raw.replace(/[/\\?#%&"'\(\)\[\]]/g, ' ').replace(/\s+/g, ' ').trim(); +} + +function getDeezerUrl(track) { + if (track && track.deezer_link) return track.deezer_link; + if (track && track.deezer_id) return `https://www.deezer.com/track/${track.deezer_id}`; + const term = getCleanSearchTerm(track ? track.artist : '', track ? track.title : ''); + return `https://www.deezer.com/search/${encodeURIComponent(term)}/track`; +} + +function getSpotifyUrl(track) { + const term = getCleanSearchTerm(track ? track.artist : '', track ? track.title : ''); + return `https://open.spotify.com/search/${encodeURIComponent(term)}`; +} + +function getYoutubeUrl(track) { + const term = getCleanSearchTerm(track ? track.artist : '', track ? track.title : ''); + return `https://www.youtube.com/results?search_query=${encodeURIComponent(term)}`; +} + async function selectTrack(track) { // Update Hero UI document.getElementById('track-title').textContent = track.title; @@ -137,13 +159,12 @@ async function selectTrack(track) { const trackActions = document.getElementById('track-actions'); if (trackActions) { trackActions.style.display = 'flex'; - const query = encodeURIComponent(`${track.artist} ${track.title}`); const spotEl = document.getElementById('action-spotify'); const deezEl = document.getElementById('action-deezer'); const ytEl = document.getElementById('action-youtube'); - if (spotEl) spotEl.href = `https://open.spotify.com/search/${query}`; - if (deezEl) deezEl.href = `https://www.deezer.com/search/${query}`; - if (ytEl) ytEl.href = `https://www.youtube.com/results?search_query=${query}`; + if (spotEl) spotEl.href = getSpotifyUrl(track); + if (deezEl) deezEl.href = getDeezerUrl(track); + if (ytEl) ytEl.href = getYoutubeUrl(track); } // Fetch or apply metadata @@ -155,6 +176,8 @@ async function selectTrack(track) { let energy = track.energy || null; let danceability = track.danceability || null; let happiness = track.happiness || null; + let deezerLink = track.deezer_link || (track.deezer_id ? `https://www.deezer.com/track/${track.deezer_id}` : null); + let deezerId = track.deezer_id || null; if (!key || !bpm || !energy) { // Query metadata route to get key, bpm, and persist into PostgreSQL @@ -171,6 +194,14 @@ async function selectTrack(track) { energy = d.energy; danceability = d.danceability; happiness = d.happiness; + if (d.deezer_link || d.deezer_id) { + deezerLink = d.deezer_link || deezerLink; + deezerId = d.deezer_id || deezerId; + track.deezer_link = deezerLink; + track.deezer_id = deezerId; + const deezEl = document.getElementById('action-deezer'); + if (deezEl) deezEl.href = getDeezerUrl(track); + } document.getElementById('stat-match').textContent = d.is_corrected ? '100%' : (d.source === 'postgresql' ? '100%' : 'Online'); document.getElementById('stat-source-sub').textContent = d.source === 'postgresql' ? (d.is_corrected ? 'PostgreSQL (Fix)' : 'PostgreSQL') : 'PostgreSQL (Neu)'; } @@ -350,13 +381,13 @@ function applySuggestionsFilter() {
${s.compatLabel || 'KOMPATIBEL'}
diff --git a/server/routes/metadata.js b/server/routes/metadata.js index 0bd9a50..376ed8e 100644 --- a/server/routes/metadata.js +++ b/server/routes/metadata.js @@ -122,6 +122,8 @@ router.get('/', async (req, res) => { is_corrected: !!local.is_corrected, cover_url: local.cover_url || null, preview_url: local.preview_url || null, + deezer_id: local.deezer_id || null, + deezer_link: local.deezer_link || (local.deezer_id ? `https://www.deezer.com/track/${local.deezer_id}` : null), duration_sec: local.duration_sec || 210, energy: local.energy || est.energy, danceability: local.danceability || est.danceability, @@ -193,6 +195,8 @@ router.get('/', async (req, res) => { // 4. Query Deezer for cover, duration, preview, and genre fallback let durationSec = 210; let previewUrl = null; + let deezerLink = null; + let deezerId = null; try { const deezerRes = await axios.get(`https://api.deezer.com/search?q=${encodeURIComponent(artist + ' ' + track)}&limit=1`, { timeout: 3000 @@ -208,6 +212,13 @@ router.get('/', async (req, res) => { if (dTrack.preview) { previewUrl = dTrack.preview; } + if (dTrack.link) { + deezerLink = dTrack.link; + } + if (dTrack.id) { + deezerId = dTrack.id; + if (!deezerLink) deezerLink = `https://www.deezer.com/track/${dTrack.id}`; + } } } catch (e) { // ignore @@ -250,6 +261,8 @@ router.get('/', async (req, res) => { cover_url: coverUrl, preview_url: previewUrl, duration_sec: durationSec, + deezer_id: deezerId, + deezer_link: deezerLink, energy: audioAttrs.energy, danceability: audioAttrs.danceability, happiness: audioAttrs.happiness, diff --git a/server/routes/search.js b/server/routes/search.js index a5c3fef..58c2451 100644 --- a/server/routes/search.js +++ b/server/routes/search.js @@ -35,7 +35,8 @@ router.get('/', async (req, res) => { cover_url: item.album && item.album.cover_medium ? item.album.cover_medium : (item.album ? item.album.cover : null), preview_url: item.preview || null, duration_sec: item.duration || 210, - deezer_id: item.id + deezer_id: item.id, + deezer_link: item.link || (item.id ? `https://www.deezer.com/track/${item.id}` : null) })); } } catch (e) { @@ -63,6 +64,8 @@ router.get('/', async (req, res) => { energy: lt.energy, danceability: lt.danceability, happiness: lt.happiness, + deezer_id: lt.deezer_id || null, + deezer_link: lt.deezer_link || (lt.deezer_id ? `https://www.deezer.com/track/${lt.deezer_id}` : null), is_local: true, is_corrected: lt.is_corrected }); @@ -81,6 +84,8 @@ router.get('/', async (req, res) => { cover_url: ot.cover_url, preview_url: ot.preview_url, duration_sec: ot.duration_sec, + deezer_id: ot.deezer_id, + deezer_link: ot.deezer_link, camelot_key: existingLocal ? existingLocal.camelot_key : null, bpm: existingLocal ? existingLocal.bpm : null, music_key: existingLocal ? existingLocal.music_key : null, diff --git a/server/routes/suggestions.js b/server/routes/suggestions.js index 8c6b49a..945d903 100644 --- a/server/routes/suggestions.js +++ b/server/routes/suggestions.js @@ -80,6 +80,7 @@ router.get('/', async (req, res) => { compatLabel: compatLabel, cover_url: t.cover_url || null, preview_url: t.preview_url || null, + deezer_link: t.deezer_link || (t.deezer_id ? `https://www.deezer.com/track/${t.deezer_id}` : null), duration_sec: t.duration_sec || 215, energy: t.energy || defaultEnergy, danceability: t.danceability || defaultDance,