diff --git a/backend/spotify_resolve.py b/backend/spotify_resolve.py index 65babc4..5bd239b 100644 --- a/backend/spotify_resolve.py +++ b/backend/spotify_resolve.py @@ -64,7 +64,9 @@ def resolve(sp_type, sp_id): url = f'https://open.spotify.com/{sp_type}/{sp_id}' if sp_type == 'track': - raw = sp_mod.Public().song_info(url) + # song_info expects a Spotify URI, not a full URL + uri = f'spotify:track:{sp_id}' + raw = sp_mod.Public().song_info(uri) eprint(f'[spotapi] track raw type={type(raw).__name__} keys={list(raw.keys()) if isinstance(raw, dict) else "?"}') if isinstance(raw, dict): for k, v in raw.items(): @@ -86,33 +88,29 @@ def resolve(sp_type, sp_id): 'tracks': [tr]} if sp_type == 'album': - alb_info = sp_mod.Public().album_info(url) - eprint(f'[spotapi] album raw type={type(alb_info).__name__} keys={list(alb_info.keys()) if isinstance(alb_info, dict) else "?"}') - if isinstance(alb_info, dict): - for k, v in alb_info.items(): - eprint(f'[spotapi] alb[{k!r}] = {repr(v)[:160]}') - # Partner API: data.albumUnion.{name, artists, coverArt, tracks.items[]} - d = (alb_info.get('data') or {}) if isinstance(alb_info, dict) else {} - au = d.get('albumUnion') or d.get('albumV2') or (alb_info if isinstance(alb_info, dict) else {}) + # PublicAlbum.get_album_info() → metadata (data.albumUnion.{name,artists,coverArt}) + # Public().album_info(url) → generator that yields track items + cls_name = next((n for n in ('PublicAlbum', 'Album') if n in available), None) + alb_meta = getattr(sp_mod, cls_name)(url).get_album_info() if cls_name else {} + d = (alb_meta.get('data') or {}) if isinstance(alb_meta, dict) else {} + au = d.get('albumUnion') or d.get('albumV2') or {} name = au.get('name', '') - # artist(s) artist_items = (au.get('artists') or {}).get('items') or [] artist = ', '.join( (a.get('profile') or {}).get('name', '') or a.get('name', '') for a in artist_items - ) or au.get('artist', '') - # artwork - cover_items = (au.get('coverArt') or {}).get('sources') or (au.get('images') or {}).get('items') or [] + ) + cover_items = (au.get('coverArt') or {}).get('sources') or [] art = cover_items[0].get('url', '') if cover_items else '' - # tracks - raw_tracks = (au.get('tracks') or {}).get('items') or [] - eprint(f'[spotapi] album tracks count={len(raw_tracks)} first={repr(raw_tracks[0])[:200] if raw_tracks else "none"}') - # album track items: {uid, track: {__typename, data: {trackUnion: {...}}}} - # wrap as {itemV2: track_obj} so _parse_track can navigate itemV2.data.trackUnion + + # album_info(url) yields track items — consume the generator + raw_tracks = list(sp_mod.Public().album_info(url)) + eprint(f'[spotapi] album_info generator → {len(raw_tracks)} items; first={repr(raw_tracks[0])[:200] if raw_tracks else "none"}') def _alb_track(item): - track_obj = item.get('track') or item - return _parse_track({'itemV2': track_obj}, - default_album=name, default_art=art, - default_artist=artist) + # items may be dicts with various shapes — log on parse failure + tr = _parse_track({'itemV2': item}, default_album=name, default_art=art, default_artist=artist) + if not tr: + tr = _parse_track(item, default_album=name, default_art=art, default_artist=artist) + return tr tracks = [t for t in (_alb_track(i) for i in raw_tracks) if t] eprint(f'[spotapi] album ok — {len(tracks)} tracks') return {'type': 'album', 'name': name, 'artist': artist, 'artworkUrl': art, 'tracks': tracks}