diff --git a/core/playlists/sources/spotify_public.py b/core/playlists/sources/spotify_public.py index 51189687..4ab21164 100644 --- a/core/playlists/sources/spotify_public.py +++ b/core/playlists/sources/spotify_public.py @@ -35,14 +35,19 @@ class SpotifyPublicPlaylistSource(PlaylistSource): """``playlist_id`` is a Spotify URL or ``open.spotify.com`` URI.""" from core.spotify_public_scraper import ( parse_spotify_url, - scrape_spotify_embed, + fetch_spotify_public, ) parsed = parse_spotify_url(playlist_id) if not parsed: return None - data = scrape_spotify_embed(parsed["type"], parsed["id"]) + # #838: use the full-fetch wrapper (paginates past the embed widget's + # ~100-track cap), NOT scrape_spotify_embed directly. The rest of the app + # already uses fetch_spotify_public; the adapter calling the capped embed + # scraper is why auto-sync truncated >100-track playlists to 100 while the + # initial discovery got the whole thing. Same return shape, so drop-in. + data = fetch_spotify_public(parsed["type"], parsed["id"]) if not isinstance(data, dict) or data.get("error"): return None diff --git a/tests/test_playlist_sources_adapters.py b/tests/test_playlist_sources_adapters.py index 18ef4d3b..38e7b518 100644 --- a/tests/test_playlist_sources_adapters.py +++ b/tests/test_playlist_sources_adapters.py @@ -915,3 +915,34 @@ def test_mirror_dict_spotify_public_emits_spotify_hint(): extra = _json.loads(d["extra_data"]) assert extra["discovered"] is False assert extra["spotify_hint"]["id"] == "sptrk" + + +def test_spotify_public_adapter_paginates_past_100(monkeypatch): + """#838: auto-sync truncated >100-track playlists to 100 because the adapter + called the embed scraper (≤100) directly instead of the full-fetch wrapper. + With the wrapper, a playlist whose full fetch returns 150 tracks keeps all 150.""" + src = SpotifyPublicPlaylistSource() + + monkeypatch.setattr( + "core.spotify_public_scraper.parse_spotify_url", + lambda url: {"type": "playlist", "id": "big"}, + ) + full = { + "id": "big", "type": "playlist", "name": "Big PL", "subtitle": "owner", + "url": "https://open.spotify.com/playlist/big", "url_hash": "bighash", + "tracks": [ + {"id": f"t{i}", "name": f"Song {i}", "artists": [{"name": "A"}], + "duration_ms": 1000, "is_explicit": False, "track_number": i + 1} + for i in range(150) + ], + } + # The full paginated path succeeds → wrapper returns all 150 (no embed cap). + monkeypatch.setattr( + "core.spotify_public_api.fetch_public_playlist_full", + lambda spotify_id: full, + ) + + detail = src.get_playlist("https://open.spotify.com/playlist/big") + assert detail is not None + assert len(detail.tracks) == 150, "pre-#838 the adapter capped this at 100" + assert detail.meta.track_count == 150