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.
This commit is contained in:
Broque Thomas 2026-01-24 14:25:27 -08:00
parent 84e6b01cc6
commit 2170ffa99e
2 changed files with 57 additions and 9 deletions

View file

@ -14293,6 +14293,40 @@ def search_spotify_tracks():
return jsonify({"error": str(e)}), 500 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 # TIDAL PLAYLIST API ENDPOINTS
# =================================================================== # ===================================================================

View file

@ -9946,7 +9946,8 @@ function openDiscoveryFixModal(platform, identifier, trackIndex) {
// Note: Beatport, Tidal, and ListenBrainz have their own states, but reuse YouTube modal infrastructure // Note: Beatport, Tidal, and ListenBrainz have their own states, but reuse YouTube modal infrastructure
let state, result; let state, result;
if (platform === 'youtube') { if (platform === 'youtube') {
state = youtubePlaylistStates[identifier]; // Check both states - ListenBrainz also uses YouTube modal infrastructure
state = listenbrainzPlaylistStates[identifier] || youtubePlaylistStates[identifier];
} else if (platform === 'tidal') { } else if (platform === 'tidal') {
state = youtubePlaylistStates[identifier]; // Tidal uses YouTube state infrastructure state = youtubePlaylistStates[identifier]; // Tidal uses YouTube state infrastructure
} else if (platform === 'beatport') { } else if (platform === 'beatport') {
@ -10099,15 +10100,23 @@ async function searchDiscoveryFix() {
return; 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'); const resultsContainer = fixModalOverlay.querySelector('#fix-modal-results');
resultsContainer.innerHTML = '<div class="loading">🔍 Searching Spotify...</div>'; const sourceLabel = useItunes ? 'iTunes' : 'Spotify';
resultsContainer.innerHTML = `<div class="loading">🔍 Searching ${sourceLabel}...</div>`;
try { try {
// Build search query // Build search query
const query = `${artistInput} ${trackInput}`.trim(); const query = `${artistInput} ${trackInput}`.trim();
// Call Spotify search API // Call appropriate search API based on discovery source
const response = await fetch(`/api/spotify/search_tracks?query=${encodeURIComponent(query)}&limit=20`); 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(); const data = await response.json();
if (data.error) { if (data.error) {
@ -10120,7 +10129,7 @@ async function searchDiscoveryFix() {
return; return;
} }
// Render results // Render results (same format for both Spotify and iTunes)
renderDiscoveryFixResults(data.tracks, fixModalOverlay); renderDiscoveryFixResults(data.tracks, fixModalOverlay);
} catch (error) { } catch (error) {
@ -10216,13 +10225,16 @@ async function selectDiscoveryFixTrack(track) {
// Update frontend state // Update frontend state
// Note: Beatport and Tidal reuse youtubePlaylistStates for discovery results // Note: Beatport and Tidal reuse youtubePlaylistStates for discovery results
// ListenBrainz uses its own state but may also be accessed via YouTube
let state; let state;
if (platform === 'youtube') { if (platform === 'youtube') {
state = youtubePlaylistStates[identifier]; state = listenbrainzPlaylistStates[identifier] || youtubePlaylistStates[identifier];
} else if (platform === 'tidal') { } else if (platform === 'tidal') {
state = youtubePlaylistStates[identifier]; state = youtubePlaylistStates[identifier];
} else if (platform === 'beatport') { } else if (platform === 'beatport') {
state = youtubePlaylistStates[identifier]; state = youtubePlaylistStates[identifier];
} else if (platform === 'listenbrainz') {
state = listenbrainzPlaylistStates[identifier];
} }
// Support both camelCase and snake_case // Support both camelCase and snake_case
@ -18602,15 +18614,17 @@ function updateYouTubeDiscoveryModal(urlHash, status) {
// Update actions cell with appropriate button // Update actions cell with appropriate button
if (actionsCell) { if (actionsCell) {
const state = youtubePlaylistStates[urlHash]; const state = listenbrainzPlaylistStates[urlHash] || youtubePlaylistStates[urlHash];
const platform = state?.is_tidal_playlist ? 'tidal' : (state?.is_beatport_playlist ? 'beatport' : 'youtube'); const platform = state?.is_listenbrainz_playlist ? 'listenbrainz' :
(state?.is_tidal_playlist ? 'tidal' :
(state?.is_beatport_playlist ? 'beatport' : 'youtube'));
actionsCell.innerHTML = generateDiscoveryActionButton(result, urlHash, platform); actionsCell.innerHTML = generateDiscoveryActionButton(result, urlHash, platform);
} }
}); });
// Update action buttons if discovery is complete (progress = 100%) // Update action buttons if discovery is complete (progress = 100%)
if (status.progress >= 100) { if (status.progress >= 100) {
const state = youtubePlaylistStates[urlHash]; const state = listenbrainzPlaylistStates[urlHash] || youtubePlaylistStates[urlHash];
if (state && state.phase === 'discovered') { if (state && state.phase === 'discovered') {
const actionButtonsContainer = document.querySelector(`#youtube-discovery-modal-${urlHash} .modal-footer-left`); const actionButtonsContainer = document.querySelector(`#youtube-discovery-modal-${urlHash} .modal-footer-left`);
if (actionButtonsContainer) { if (actionButtonsContainer) {