diff --git a/internal/provider/tidal/client.go b/internal/provider/tidal/client.go index b2a1192..42368b8 100644 --- a/internal/provider/tidal/client.go +++ b/internal/provider/tidal/client.go @@ -310,31 +310,49 @@ func (c *Client) fetchArtistAlbums(ctx context.Context, artistID string) ([]map[ out := make([]map[string]any, 0) seen := map[string]struct{}{} for _, p := range paths { - resp, status, err := c.apiRequest(ctx, p.path, p.params, c.baseURL) - if err != nil { - return nil, err - } - if status != http.StatusOK { - return nil, fmt.Errorf("tidal artist albums failed: status=%d", status) - } - items, _ := resp["items"].([]any) - for _, raw := range items { - itm, ok := raw.(map[string]any) - if !ok { - continue + offset := 0 + for { + params := url.Values{} + for k, values := range p.params { + for _, v := range values { + params.Add(k, v) + } } - if wrapped, ok := itm["item"].(map[string]any); ok { - itm = wrapped + params.Set("offset", strconv.Itoa(offset)) + + resp, status, err := c.apiRequest(ctx, p.path, params, c.baseURL) + if err != nil { + return nil, err } - id := stringify(itm["id"]) - if id == "" { - continue + if status != http.StatusOK { + return nil, fmt.Errorf("tidal artist albums failed: status=%d offset=%d", status, offset) } - if _, dup := seen[id]; dup { - continue + items, _ := resp["items"].([]any) + if len(items) == 0 { + break } - seen[id] = struct{}{} - out = append(out, itm) + for _, raw := range items { + itm, ok := raw.(map[string]any) + if !ok { + continue + } + if wrapped, ok := itm["item"].(map[string]any); ok { + itm = wrapped + } + id := stringify(itm["id"]) + if id == "" { + continue + } + if _, dup := seen[id]; dup { + continue + } + seen[id] = struct{}{} + out = append(out, itm) + } + if len(items) < 100 { + break + } + offset += 100 } } return out, nil diff --git a/internal/provider/tidal/client_test.go b/internal/provider/tidal/client_test.go index 6a0ea1f..23c062f 100644 --- a/internal/provider/tidal/client_test.go +++ b/internal/provider/tidal/client_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "net/http" "net/http/httptest" + "strconv" "testing" "streamrip-go/internal/config" @@ -98,3 +99,64 @@ func TestBestHLSVariantURLFallsBackToMaster(t *testing.T) { t.Fatalf("url = %q, want %q", got, master) } } + +func TestGetMetadataArtistPaginatesAlbums(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/v1/sessions": + _ = json.NewEncoder(w).Encode(map[string]any{"countryCode": "US", "userId": 123}) + case "/v1/artists/9": + _ = json.NewEncoder(w).Encode(map[string]any{"id": 9, "name": "Artist X"}) + case "/v1/artists/9/albums": + offset, _ := strconv.Atoi(r.URL.Query().Get("offset")) + filter := r.URL.Query().Get("filter") + if filter == "" { + if offset == 0 { + items := make([]any, 0, 100) + for i := 0; i < 100; i++ { + items = append(items, map[string]any{"id": i + 1}) + } + _ = json.NewEncoder(w).Encode(map[string]any{"items": items}) + return + } + if offset == 100 { + _ = json.NewEncoder(w).Encode(map[string]any{"items": []any{map[string]any{"id": 101}}}) + return + } + _ = json.NewEncoder(w).Encode(map[string]any{"items": []any{}}) + return + } + if filter == "EPSANDSINGLES" { + if offset == 0 { + _ = json.NewEncoder(w).Encode(map[string]any{"items": []any{map[string]any{"id": 101}, map[string]any{"id": 102}}}) + return + } + _ = json.NewEncoder(w).Encode(map[string]any{"items": []any{}}) + return + } + w.WriteHeader(http.StatusBadRequest) + default: + w.WriteHeader(http.StatusNotFound) + } + })) + defer ts.Close() + + cfgData := config.DefaultConfigData() + cfgData.Tidal.AccessToken = "token" + cfgData.Tidal.CountryCode = "US" + c := New(&config.Config{File: cfgData, Session: cfgData}) + c.baseURL = ts.URL + "/v1" + + if err := c.Login(context.Background()); err != nil { + t.Fatalf("login err = %v", err) + } + meta, err := c.GetMetadata(context.Background(), "9", "artist") + if err != nil { + t.Fatalf("GetMetadata() err = %v", err) + } + albumsObj, _ := meta["albums"].(map[string]any) + items, _ := albumsObj["items"].([]map[string]any) + if len(items) != 102 { + t.Fatalf("albums len = %d, want 102", len(items)) + } +}