From 2170ffa99e98e4a7b5dba01b26c6f90ce292b443 Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Sat, 24 Jan 2026 14:25:27 -0800 Subject: [PATCH] Fix discovery modal fix button for iTunes source and ListenBrainz playlists - Fix platform detection to include is_listenbrainz_playlist check when generating fix buttons - Update openDiscoveryFixModal to check both listenbrainzPlaylistStates and youtubePlaylistStates - Update searchDiscoveryFix to detect discovery_source and use appropriate search API - Add /api/itunes/search_tracks endpoint for manual track search when using iTunes source - Update selectDiscoveryFixTrack state lookup to include ListenBrainz Fix button now works correctly for all platforms (YouTube, ListenBrainz, Tidal, Beatport) with both Spotify and iTunes discovery sources. --- web_server.py | 34 ++++++++++++++++++++++++++++++++++ webui/static/script.js | 32 +++++++++++++++++++++++--------- 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/web_server.py b/web_server.py index c18871be..27858c83 100644 --- a/web_server.py +++ b/web_server.py @@ -14293,6 +14293,40 @@ def search_spotify_tracks(): return jsonify({"error": str(e)}), 500 +@app.route('/api/itunes/search_tracks', methods=['GET']) +def search_itunes_tracks(): + """Search for tracks on iTunes - used by discovery fix modal when iTunes is the source""" + try: + from core.itunes_client import iTunesClient + + query = request.args.get('query', '').strip() + limit = int(request.args.get('limit', 20)) + + if not query: + return jsonify({"error": "Query parameter is required"}), 400 + + # Search using iTunes client + itunes_client = iTunesClient() + tracks = itunes_client.search_tracks(query, limit=limit) + + # Convert tracks to dict format matching Spotify structure for frontend compatibility + tracks_dict = [{ + 'id': t.id, + 'name': t.name, + 'artists': t.artists, # Already a list + 'album': t.album, + 'duration_ms': t.duration_ms, + 'image_url': t.image_url, + 'source': 'itunes' + } for t in tracks] + + return jsonify({'tracks': tracks_dict}) + + except Exception as e: + print(f"❌ Error searching iTunes tracks: {e}") + return jsonify({"error": str(e)}), 500 + + # =================================================================== # TIDAL PLAYLIST API ENDPOINTS # =================================================================== diff --git a/webui/static/script.js b/webui/static/script.js index 88969d05..98cd6e44 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -9946,7 +9946,8 @@ function openDiscoveryFixModal(platform, identifier, trackIndex) { // Note: Beatport, Tidal, and ListenBrainz have their own states, but reuse YouTube modal infrastructure let state, result; if (platform === 'youtube') { - state = youtubePlaylistStates[identifier]; + // Check both states - ListenBrainz also uses YouTube modal infrastructure + state = listenbrainzPlaylistStates[identifier] || youtubePlaylistStates[identifier]; } else if (platform === 'tidal') { state = youtubePlaylistStates[identifier]; // Tidal uses YouTube state infrastructure } else if (platform === 'beatport') { @@ -10099,15 +10100,23 @@ 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 useItunes = discoverySource === 'itunes'; + const resultsContainer = fixModalOverlay.querySelector('#fix-modal-results'); - resultsContainer.innerHTML = '
🔍 Searching Spotify...
'; + const sourceLabel = useItunes ? 'iTunes' : 'Spotify'; + resultsContainer.innerHTML = `
🔍 Searching ${sourceLabel}...
`; try { // Build search query const query = `${artistInput} ${trackInput}`.trim(); - // Call Spotify search API - const response = await fetch(`/api/spotify/search_tracks?query=${encodeURIComponent(query)}&limit=20`); + // Call appropriate search API based on discovery source + const searchEndpoint = useItunes ? '/api/itunes/search_tracks' : '/api/spotify/search_tracks'; + const response = await fetch(`${searchEndpoint}?query=${encodeURIComponent(query)}&limit=20`); const data = await response.json(); if (data.error) { @@ -10120,7 +10129,7 @@ async function searchDiscoveryFix() { return; } - // Render results + // Render results (same format for both Spotify and iTunes) renderDiscoveryFixResults(data.tracks, fixModalOverlay); } catch (error) { @@ -10216,13 +10225,16 @@ async function selectDiscoveryFixTrack(track) { // Update frontend state // Note: Beatport and Tidal reuse youtubePlaylistStates for discovery results + // ListenBrainz uses its own state but may also be accessed via YouTube let state; if (platform === 'youtube') { - state = youtubePlaylistStates[identifier]; + state = listenbrainzPlaylistStates[identifier] || youtubePlaylistStates[identifier]; } else if (platform === 'tidal') { state = youtubePlaylistStates[identifier]; } else if (platform === 'beatport') { state = youtubePlaylistStates[identifier]; + } else if (platform === 'listenbrainz') { + state = listenbrainzPlaylistStates[identifier]; } // Support both camelCase and snake_case @@ -18602,15 +18614,17 @@ function updateYouTubeDiscoveryModal(urlHash, status) { // Update actions cell with appropriate button if (actionsCell) { - const state = youtubePlaylistStates[urlHash]; - const platform = state?.is_tidal_playlist ? 'tidal' : (state?.is_beatport_playlist ? 'beatport' : 'youtube'); + const state = listenbrainzPlaylistStates[urlHash] || youtubePlaylistStates[urlHash]; + const platform = state?.is_listenbrainz_playlist ? 'listenbrainz' : + (state?.is_tidal_playlist ? 'tidal' : + (state?.is_beatport_playlist ? 'beatport' : 'youtube')); actionsCell.innerHTML = generateDiscoveryActionButton(result, urlHash, platform); } }); // Update action buttons if discovery is complete (progress = 100%) if (status.progress >= 100) { - const state = youtubePlaylistStates[urlHash]; + const state = listenbrainzPlaylistStates[urlHash] || youtubePlaylistStates[urlHash]; if (state && state.phase === 'discovered') { const actionButtonsContainer = document.querySelector(`#youtube-discovery-modal-${urlHash} .modal-footer-left`); if (actionButtonsContainer) {