From 5d0c5ec6ebc5bfe55ffb43691ee05b532e8e55ed Mon Sep 17 00:00:00 2001 From: Honey Date: Wed, 13 May 2026 01:15:43 +0200 Subject: [PATCH] fix: album track wrapping + try multiple Song() call patterns for tracks - Album: wrap item.get('track') as {itemV2: ...} so _parse_track navigates trackUnion correctly - Track: try get_song_info(url) then get_song_info(); log Song/Public methods on failure Co-Authored-By: Claude Sonnet 4.6 --- backend/spotify_resolve.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/backend/spotify_resolve.py b/backend/spotify_resolve.py index 75e57e4..681a9da 100644 --- a/backend/spotify_resolve.py +++ b/backend/spotify_resolve.py @@ -64,10 +64,26 @@ def resolve(sp_type, sp_id): url = f'https://open.spotify.com/{sp_type}/{sp_id}' if sp_type == 'track': - # Song requires a client instance as first arg; Public() is the unauthenticated client - public = sp_mod.Public() - song = sp_mod.Song(public, url) - raw = song.get_song_info() + # Song(url) stores url as the client object, then get_song_info() or init + # tries to call url.login which fails. Try: Song(url) + get_song_info(url), + # then Song(url) + get_song_info(), log methods as last resort. + song = sp_mod.Song(url) + song_methods = [m for m in dir(song) if not m.startswith('_')] + eprint(f'[spotapi] Song methods: {song_methods}') + raw = None + for attempt in [ + lambda: song.get_song_info(url), + lambda: song.get_song_info(), + ]: + try: + raw = attempt() + break + except Exception as e: + eprint(f'[spotapi] track attempt failed: {type(e).__name__}: {e}') + if raw is None: + pub = sp_mod.Public() + eprint(f'[spotapi] Public methods: {[m for m in dir(pub) if not m.startswith("_")]}') + raise Exception('Song resolution: all attempts failed') 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(): @@ -113,10 +129,11 @@ def resolve(sp_type, sp_id): art = cover_items[0].get('url', '') if cover_items else '' # tracks raw_tracks = (au.get('tracks') or {}).get('items') or [] - # album track items look like {uid, track: {data: {trackUnion: {...}}}} + # album track items: {uid, track: {__typename, data: {trackUnion: {...}}}} + # wrap as {itemV2: track_obj} so _parse_track can navigate itemV2.data.trackUnion def _alb_track(item): - inner = item.get('track') or item - return _parse_track(inner, + track_obj = item.get('track') or item + return _parse_track({'itemV2': track_obj}, default_album=name, default_art=art, default_artist=artist) tracks = [t for t in (_alb_track(i) for i in raw_tracks) if t]