From a401c7a607f87325b3c645724d84a73c1fc32df8 Mon Sep 17 00:00:00 2001 From: Honey Date: Wed, 13 May 2026 01:36:33 +0200 Subject: [PATCH] fix: use Public().song_info() for tracks, Public().album_info() for albums Both are the correct unauthenticated API methods on the Public class. Song class requires a logged-in session and has no public equivalent. Co-Authored-By: Claude Sonnet 4.6 --- backend/spotify_resolve.py | 34 +++++----------------------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/backend/spotify_resolve.py b/backend/spotify_resolve.py index 783d501..65babc4 100644 --- a/backend/spotify_resolve.py +++ b/backend/spotify_resolve.py @@ -64,33 +64,12 @@ def resolve(sp_type, sp_id): url = f'https://open.spotify.com/{sp_type}/{sp_id}' if sp_type == 'track': - # Log Public methods first so we always see them regardless of Song failures - pub = sp_mod.Public() - eprint(f'[spotapi] Public methods: {[m for m in dir(pub) if not m.startswith("_")]}') - raw = None - # Try every plausible call pattern — Song requires a Login session, not a URL, - # so we also try Public directly in case it exposes track methods. - attempts = [ - ('Public(url).get_song_info()', lambda: sp_mod.Public(url).get_song_info()), - ('Public().get_song_info(url)', lambda: sp_mod.Public().get_song_info(url)), - ('Song(url).get_song_info(url)', lambda: sp_mod.Song(url).get_song_info(url)), - ('Song(url).get_song_info()', lambda: sp_mod.Song(url).get_song_info()), - ] - for label, fn in attempts: - try: - raw = fn() - eprint(f'[spotapi] track: {label} succeeded') - break - except Exception as e: - eprint(f'[spotapi] track [{label}] failed: {type(e).__name__}: {e}') - if raw is None: - raise Exception('Song resolution: all attempts failed') + raw = sp_mod.Public().song_info(url) 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(): - eprint(f'[spotapi] track[{k!r}] = {repr(v)[:120]}') - # Partner API nests track under data.trackUnion - d = raw.get('data') or {} + eprint(f'[spotapi] track[{k!r}] = {repr(v)[:160]}') + d = (raw.get('data') or {}) if isinstance(raw, dict) else {} tu = d.get('trackUnion') or d tr = _parse_track({'itemV2': {'data': {'trackUnion': tu}}}) or { 'trackName': raw.get('name', ''), @@ -107,11 +86,7 @@ def resolve(sp_type, sp_id): 'tracks': [tr]} if sp_type == 'album': - cls_name = next((n for n in ('PublicAlbum', 'Album') if n in available), None) - if not cls_name: - raise Exception(f'spotapi has no album class (available: {available})') - alb = getattr(sp_mod, cls_name)(url) - alb_info = alb.get_album_info() + 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(): @@ -130,6 +105,7 @@ 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 [] + 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 def _alb_track(item):