From 53ef9fa913280075a2835e194df1e4115ed33d95 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Mon, 16 Mar 2026 19:49:54 -0700 Subject: [PATCH] Add Deezer support to watchlist config modal and linked provider section - Query/update watchlist artists by deezer_artist_id in config endpoint - Return deezer_artist_id in config response and recent albums response - Add Deezer provider badge (purple) to linked provider section - Detect Deezer vs iTunes for provider linking using fallback source setting - Show "X fans" instead of "Pop: 0" for Deezer artist search results - Include followers count in match/search artist response - Add deezer_artist_id matching to library enrichment and recent releases queries --- web_server.py | 25 ++++++++++++++----------- webui/static/script.js | 30 ++++++++++++++++++++++-------- webui/static/style.css | 6 ++++++ 3 files changed, 42 insertions(+), 19 deletions(-) diff --git a/web_server.py b/web_server.py index 2ed46941..93650e21 100644 --- a/web_server.py +++ b/web_server.py @@ -12153,7 +12153,8 @@ def search_match(): "name": artist.name, "image_url": getattr(artist, 'image_url', None), "genres": getattr(artist, 'genres', []), - "popularity": getattr(artist, 'popularity', 0) + "popularity": getattr(artist, 'popularity', 0), + "followers": getattr(artist, 'followers', 0) }, "confidence": confidence }) @@ -31095,20 +31096,21 @@ def watchlist_artist_config(artist_id): SELECT include_albums, include_eps, include_singles, include_live, include_remixes, include_acoustic, include_compilations, artist_name, image_url, spotify_artist_id, itunes_artist_id, - last_scan_timestamp, date_added, include_instrumentals + last_scan_timestamp, date_added, include_instrumentals, deezer_artist_id FROM watchlist_artists - WHERE spotify_artist_id = ? OR itunes_artist_id = ? - """, (artist_id, artist_id)) + WHERE spotify_artist_id = ? OR itunes_artist_id = ? OR deezer_artist_id = ? + """, (artist_id, artist_id, artist_id)) result = cursor.fetchone() conn.close() if not result: return jsonify({"success": False, "error": "Artist not found in watchlist"}), 404 - + # Determine if this is an iTunes or Spotify artist is_itunes_artist = artist_id.isdigit() spotify_id = result[9] # spotify_artist_id from query itunes_id = result[10] # itunes_artist_id from query + deezer_id = result[14] # deezer_artist_id from query # Get artist info from Spotify (only for Spotify artists) artist_info = None @@ -31145,9 +31147,9 @@ def watchlist_artist_config(artist_id): cur2.execute(""" SELECT banner_url, summary, style, mood, label, genres FROM artists - WHERE spotify_artist_id = ? OR itunes_artist_id = ? + WHERE spotify_artist_id = ? OR itunes_artist_id = ? OR deezer_artist_id = ? LIMIT 1 - """, (artist_id, artist_id)) + """, (artist_id, artist_id, artist_id)) lib_row = cur2.fetchone() if lib_row: artist_info['banner_url'] = lib_row[0] @@ -31167,10 +31169,10 @@ def watchlist_artist_config(artist_id): SELECT rr.album_name, rr.release_date, rr.album_cover_url, rr.track_count FROM recent_releases rr JOIN watchlist_artists wa ON rr.watchlist_artist_id = wa.id - WHERE wa.spotify_artist_id = ? OR wa.itunes_artist_id = ? + WHERE wa.spotify_artist_id = ? OR wa.itunes_artist_id = ? OR wa.deezer_artist_id = ? ORDER BY rr.release_date DESC LIMIT 6 - """, (artist_id, artist_id)) + """, (artist_id, artist_id, artist_id)) releases = [ { 'album_name': r[0], @@ -31205,6 +31207,7 @@ def watchlist_artist_config(artist_id): "recent_releases": releases, "spotify_artist_id": spotify_id, "itunes_artist_id": itunes_id, + "deezer_artist_id": deezer_id, "watchlist_name": result[7], # Original stored watchlist artist name }) @@ -31235,11 +31238,11 @@ def watchlist_artist_config(artist_id): include_live = ?, include_remixes = ?, include_acoustic = ?, include_compilations = ?, include_instrumentals = ?, updated_at = CURRENT_TIMESTAMP - WHERE spotify_artist_id = ? OR itunes_artist_id = ? + WHERE spotify_artist_id = ? OR itunes_artist_id = ? OR deezer_artist_id = ? """, (int(include_albums), int(include_eps), int(include_singles), int(include_live), int(include_remixes), int(include_acoustic), int(include_compilations), int(include_instrumentals), - artist_id, artist_id)) + artist_id, artist_id, artist_id)) conn.commit() if cursor.rowcount == 0: diff --git a/webui/static/script.js b/webui/static/script.js index 1761338a..a56d7ed9 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -34453,9 +34453,9 @@ function closeWatchlistModal() { /** * Populate the linked provider section in the watchlist config modal. - * Shows which Spotify/iTunes artist is linked and allows changing it. + * Shows which Spotify/iTunes/Deezer artist is linked and allows changing it. */ -function _populateLinkedProviderSection(artistId, artistName, spotifyId, itunesId, artistInfo) { +function _populateLinkedProviderSection(artistId, artistName, spotifyId, itunesId, artistInfo, deezerId) { const section = document.getElementById('watchlist-linked-provider-section'); const content = document.getElementById('watchlist-linked-provider-content'); if (!section || !content) return; @@ -34463,8 +34463,9 @@ function _populateLinkedProviderSection(artistId, artistName, spotifyId, itunesI // Determine which providers are linked const hasSpotify = !!spotifyId; const hasItunes = !!itunesId; + const hasDeezer = !!deezerId; - if (!hasSpotify && !hasItunes) { + if (!hasSpotify && !hasItunes && !hasDeezer) { section.style.display = 'none'; return; } @@ -34496,6 +34497,9 @@ function _populateLinkedProviderSection(artistId, artistName, spotifyId, itunesI if (hasItunes) { html += `iTunes`; } + if (hasDeezer) { + html += `Deezer`; + } html += ``; // Show mismatch warning if linked name differs from watchlist name @@ -34570,12 +34574,14 @@ async function _searchLinkedProviderArtists(currentArtistId, watchlistName) { const img = a.image_url || ''; const genres = (a.genres || []).slice(0, 2).join(', '); const pop = a.popularity || 0; + const followers = a.followers || 0; + const popMeta = pop > 0 ? `Pop: ${pop}` : followers > 0 ? `${followers.toLocaleString()} fans` : ''; html += `
${img ? `` : `
🎵
`}
${escapeHtml(a.name)}
-
${genres ? escapeHtml(genres) + ' · ' : ''}Pop: ${pop}
+
${genres ? escapeHtml(genres) : ''}${genres && popMeta ? ' · ' : ''}${popMeta}
`; @@ -34603,8 +34609,16 @@ async function _searchLinkedProviderArtists(currentArtistId, watchlistName) { * Link a watchlist artist to a new provider artist. */ async function _linkProviderArtist(currentArtistId, newProviderId, newProviderName) { - // Determine provider type from ID format - const provider = /^\d+$/.test(newProviderId) ? 'itunes' : 'spotify'; + // Determine provider from active metadata source + // Spotify IDs are alphanumeric (e.g. "4Z8W4fKeB5YxbusRsdQVPb"), iTunes/Deezer are numeric + let provider; + if (/^\d+$/.test(newProviderId)) { + // Numeric ID — check configured fallback source to distinguish iTunes from Deezer + const fallbackSrc = document.getElementById('metadata-fallback-source')?.value || 'itunes'; + provider = fallbackSrc === 'deezer' ? 'deezer' : 'itunes'; + } else { + provider = 'spotify'; + } try { const response = await fetch(`/api/watchlist/artist/${currentArtistId}/link-provider`, { @@ -34652,10 +34666,10 @@ async function openWatchlistArtistConfigModal(artistId, artistName) { return; } - const { config, artist, spotify_artist_id, itunes_artist_id, watchlist_name } = data; + const { config, artist, spotify_artist_id, itunes_artist_id, deezer_artist_id, watchlist_name } = data; // Populate linked provider section (use DB watchlist_name for mismatch comparison) - _populateLinkedProviderSection(artistId, watchlist_name || artistName, spotify_artist_id, itunes_artist_id, artist); + _populateLinkedProviderSection(artistId, watchlist_name || artistName, spotify_artist_id, itunes_artist_id, artist, deezer_artist_id); // Check if global override is active let globalOverrideActive = false; diff --git a/webui/static/style.css b/webui/static/style.css index bfda0860..273ac14b 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -14095,6 +14095,12 @@ body { border: 1px solid rgba(252, 60, 68, 0.3); } +.watchlist-provider-badge.deezer { + background: rgba(162, 56, 255, 0.15); + color: #a238ff; + border: 1px solid rgba(162, 56, 255, 0.3); +} + .watchlist-linked-mismatch-warning { margin-top: 6px; font-size: 12px;