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
@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
# ===================================================================

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
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 = '<div class="loading">🔍 Searching Spotify...</div>';
const sourceLabel = useItunes ? 'iTunes' : 'Spotify';
resultsContainer.innerHTML = `<div class="loading">🔍 Searching ${sourceLabel}...</div>`;
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) {