diff --git a/web_server.py b/web_server.py index d99476de..b04a74cc 100644 --- a/web_server.py +++ b/web_server.py @@ -30984,6 +30984,48 @@ def search_itunes_tracks(): return jsonify({"error": str(e)}), 500 +@app.route('/api/deezer/search_tracks', methods=['GET']) +def search_deezer_tracks(): + """Search for tracks on Deezer - used by discovery fix modal when Deezer is the source""" + try: + track_q = request.args.get('track', '').strip() + artist_q = request.args.get('artist', '').strip() + legacy_query = request.args.get('query', '').strip() + limit = int(request.args.get('limit', 20)) + + if track_q or artist_q: + parts = [] + if track_q: + parts.append(track_q) + if artist_q: + parts.append(artist_q) + query = ' '.join(parts) + elif legacy_query: + query = legacy_query + else: + return jsonify({"error": "Query parameter is required"}), 400 + + from core.deezer_client import DeezerClient + client = _get_deezer_client() + tracks = client.search_tracks(query, limit=limit) + + tracks_dict = [{ + 'id': t.id, + 'name': t.name, + 'artists': t.artists, + 'album': t.album, + 'duration_ms': t.duration_ms, + 'image_url': getattr(t, 'image_url', None), + 'source': 'deezer' + } for t in tracks] + + return jsonify({'tracks': tracks_dict}) + + except Exception as e: + print(f"❌ Error searching Deezer tracks: {e}") + return jsonify({"error": str(e)}), 500 + + @app.route('/api/itunes/album/', methods=['GET']) def get_itunes_album_tracks(album_id): """Fetches full track details for a specific iTunes album.""" diff --git a/webui/static/script.js b/webui/static/script.js index 7adcc057..caf3bcce 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -18414,44 +18414,54 @@ async function searchDiscoveryFix() { return; } - // Determine discovery source from state - const identifier = currentDiscoveryFix.identifier; - const state = listenbrainzPlaylistStates[identifier] || youtubePlaylistStates[identifier]; - const discoverySource = state?.discovery_source || state?.discoverySource || 'spotify'; - const useFallback = discoverySource !== 'spotify'; - const resultsContainer = fixModalOverlay.querySelector('#fix-modal-results'); - const sourceLabel = discoverySource === 'spotify' ? 'Spotify' : discoverySource === 'deezer' ? 'Deezer' : 'iTunes'; - resultsContainer.innerHTML = `
🔍 Searching ${sourceLabel}...
`; + + // Build search params + const params = new URLSearchParams(); + if (trackInput) params.set('track', trackInput); + if (artistInput) params.set('artist', artistInput); + if (!trackInput && !artistInput) { + resultsContainer.innerHTML = '
Enter a track name or artist.
'; + return; + } + params.set('limit', '50'); + + // Use the user's active metadata source first, then fall back to others + const activeSource = (currentMusicSourceName || 'Spotify').toLowerCase(); + const allSources = [ + { key: 'spotify', endpoint: '/api/spotify/search_tracks', label: 'Spotify' }, + { key: 'deezer', endpoint: '/api/deezer/search_tracks', label: 'Deezer' }, + { key: 'itunes', endpoint: '/api/itunes/search_tracks', label: 'iTunes' }, + ]; + // Put the active source first, keep others as fallbacks + const activeIdx = allSources.findIndex(s => activeSource.includes(s.key)); + const searchSources = activeIdx > 0 + ? [allSources[activeIdx], ...allSources.filter((_, i) => i !== activeIdx)] + : allSources; + + resultsContainer.innerHTML = `
🔍 Searching ${searchSources[0].label}...
`; try { - // Build search params — send track and artist separately for field-specific filtering - const params = new URLSearchParams(); - if (trackInput) params.set('track', trackInput); - if (artistInput) params.set('artist', artistInput); - if (!trackInput && !artistInput) { - resultsContainer.innerHTML = '
Enter a track name or artist.
'; - return; + for (let i = 0; i < searchSources.length; i++) { + const source = searchSources[i]; + try { + const response = await fetch(`${source.endpoint}?${params.toString()}`); + const data = await response.json(); + + if (data.tracks && data.tracks.length > 0) { + renderDiscoveryFixResults(data.tracks, fixModalOverlay); + return; + } + // No results from this source — show next source status if there is one + if (i < searchSources.length - 1) { + resultsContainer.innerHTML = `
🔍 Trying ${searchSources[i + 1].label}...
`; + } + } catch (e) { + console.warn(`Discovery fix search failed on ${source.label}: ${e.message}`); + } } - params.set('limit', '50'); - - // Call appropriate search API based on discovery source - const searchEndpoint = useFallback ? '/api/itunes/search_tracks' : '/api/spotify/search_tracks'; - const response = await fetch(`${searchEndpoint}?${params.toString()}`); - const data = await response.json(); - - if (data.error) { - resultsContainer.innerHTML = `
❌ ${data.error}
`; - return; - } - - if (!data.tracks || data.tracks.length === 0) { - resultsContainer.innerHTML = '
No matches found. Try different search terms.
'; - return; - } - - // Render results (same format for both Spotify and iTunes) - renderDiscoveryFixResults(data.tracks, fixModalOverlay); + // All sources exhausted + resultsContainer.innerHTML = '
No matches found on any source. Try different search terms.
'; } catch (error) { console.error('Search error:', error);