diff --git a/web_server.py b/web_server.py index a33e0597..de84c8dc 100644 --- a/web_server.py +++ b/web_server.py @@ -11983,10 +11983,38 @@ def get_playlist_tracks(playlist_id): try: # Handle special "Liked Songs" virtual playlist if playlist_id == "spotify:liked-songs": - saved_tracks = spotify_client.get_saved_tracks() user_info = spotify_client.get_user_info() owner_name = user_info.get('display_name', 'You') if user_info else 'You' + # Fetch raw saved tracks with full album data + tracks = [] + limit = 50 + offset = 0 + + while True: + results = spotify_client.sp.current_user_saved_tracks(limit=limit, offset=offset) + + if not results or 'items' not in results: + break + + for item in results['items']: + if item['track'] and item['track']['id']: + track_data = item['track'] + tracks.append({ + 'id': track_data['id'], + 'name': track_data['name'], + 'artists': [artist['name'] for artist in track_data['artists']], + 'album': track_data['album'], # Full album object + 'duration_ms': track_data['duration_ms'], + 'popularity': track_data.get('popularity', 0), + 'spotify_track_id': track_data['id'] + }) + + if len(results['items']) < limit or not results.get('next'): + break + + offset += limit + # Create virtual playlist dict for Liked Songs playlist_dict = { 'id': 'spotify:liked-songs', @@ -11995,31 +12023,49 @@ def get_playlist_tracks(playlist_id): 'owner': owner_name, 'public': False, 'collaborative': False, - 'track_count': len(saved_tracks), + 'track_count': len(tracks), 'image_url': None, 'snapshot_id': '', - 'tracks': [{'id': t.id, 'name': t.name, 'artists': t.artists, 'album': t.album, 'duration_ms': t.duration_ms, 'popularity': t.popularity} for t in saved_tracks] + 'tracks': tracks } return jsonify(playlist_dict) # Handle regular playlists - # This reuses the robust track fetching logic from your GUI's sync.py - full_playlist = spotify_client.get_playlist_by_id(playlist_id) - if not full_playlist: - return jsonify({}) + # Fetch raw playlist data to preserve full album objects + playlist_data = spotify_client.sp.playlist(playlist_id) - # Convert playlist to dict manually since core class doesn't have to_dict method + # Fetch all tracks with full album data + tracks = [] + results = spotify_client.sp.playlist_tracks(playlist_id, limit=100) + + while results: + for item in results['items']: + if item['track'] and item['track']['id']: + track_data = item['track'] + tracks.append({ + 'id': track_data['id'], + 'name': track_data['name'], + 'artists': [artist['name'] for artist in track_data['artists']], + 'album': track_data['album'], # Full album object with album_type, total_tracks, etc. + 'duration_ms': track_data['duration_ms'], + 'popularity': track_data.get('popularity', 0), + 'spotify_track_id': track_data['id'] # Also include as spotify_track_id for consistency + }) + + results = spotify_client.sp.next(results) if results['next'] else None + + # Convert playlist to dict playlist_dict = { - 'id': full_playlist.id, - 'name': full_playlist.name, - 'description': full_playlist.description, - 'owner': full_playlist.owner, - 'public': full_playlist.public, - 'collaborative': full_playlist.collaborative, - 'track_count': full_playlist.total_tracks, - 'image_url': getattr(full_playlist, 'image_url', None), - 'snapshot_id': getattr(full_playlist, 'snapshot_id', ''), - 'tracks': [{'id': t.id, 'name': t.name, 'artists': t.artists, 'album': t.album, 'duration_ms': t.duration_ms, 'popularity': t.popularity} for t in full_playlist.tracks] + 'id': playlist_data['id'], + 'name': playlist_data['name'], + 'description': playlist_data.get('description', ''), + 'owner': playlist_data['owner']['display_name'], + 'public': playlist_data.get('public', False), + 'collaborative': playlist_data.get('collaborative', False), + 'track_count': len(tracks), + 'image_url': playlist_data['images'][0]['url'] if playlist_data.get('images') else None, + 'snapshot_id': playlist_data.get('snapshot_id', ''), + 'tracks': tracks } return jsonify(playlist_dict) except Exception as e: @@ -12053,6 +12099,21 @@ def get_album_tracks(album_id): return jsonify({"error": str(e)}), 500 +@app.route('/api/spotify/track/', methods=['GET']) +def get_spotify_track(track_id): + """Fetches full track details including album data for a specific track.""" + if not spotify_client or not spotify_client.is_authenticated(): + return jsonify({"error": "Spotify not authenticated."}), 401 + try: + track_data = spotify_client.get_track_details(track_id) + if not track_data: + return jsonify({"error": "Track not found"}), 404 + + return jsonify(track_data) + except Exception as e: + return jsonify({"error": str(e)}), 500 + + @app.route('/api/spotify/search_tracks', methods=['GET']) def search_spotify_tracks(): """Search for tracks on Spotify - used by discovery fix modal""" diff --git a/webui/static/script.js b/webui/static/script.js index 73cc485f..fe3561fb 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -8808,15 +8808,38 @@ async function addModalTracksToWishlist(playlistId) { }; // Use track's own album data if available (has correct album_type) - // Don't fall back to process album/playlist if track has no album - // (better to skip than add with wrong data) - if (!track.album || !track.album.name) { - console.warn(`⚠️ Skipping track "${track.name}" - missing album data`); - continue; - } + // If missing, fetch from Spotify using track ID + let trackAlbum = track.album; + let trackAlbumType = track.album?.album_type || 'album'; - const trackAlbum = track.album; - const trackAlbumType = track.album.album_type || 'album'; + if (!trackAlbum || !trackAlbum.name) { + // Try to fetch track data from Spotify to get album info + if (track.spotify_track_id || track.id) { + try { + const spotifyTrackId = track.spotify_track_id || track.id; + const trackInfoResponse = await fetch(`/api/spotify/track/${spotifyTrackId}`); + const trackInfo = await trackInfoResponse.json(); + + if (trackInfo && trackInfo.album) { + trackAlbum = trackInfo.album; + trackAlbumType = trackInfo.album.album_type || 'album'; + console.log(`✅ Fetched album data for "${track.name}"`); + } else { + console.warn(`⚠️ Skipping track "${track.name}" - could not fetch album data`); + errorCount++; + continue; + } + } catch (fetchError) { + console.error(`❌ Error fetching track data for "${track.name}":`, fetchError); + errorCount++; + continue; + } + } else { + console.warn(`⚠️ Skipping track "${track.name}" - missing album data and track ID`); + errorCount++; + continue; + } + } const response = await fetch('/api/add-album-to-wishlist', { method: 'POST',