mirror of
https://git.sr.ht/~joren/streamrip-go
synced 2026-06-17 15:05:39 +02:00
paginate artist album fetching for deezer and qobuz
This commit is contained in:
@@ -203,7 +203,7 @@ func (c *Client) GetMetadata(ctx context.Context, item, mediaType string) (map[s
|
|||||||
resp["tracks"] = map[string]any{"items": items}
|
resp["tracks"] = map[string]any{"items": items}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
case "artist":
|
case "artist":
|
||||||
resp, err := c.apiGet(ctx, "/artist/"+item+"/albums", nil)
|
resp, err := c.getArtistAlbums(ctx, item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -224,6 +224,35 @@ func (c *Client) GetMetadata(ctx context.Context, item, mediaType string) (map[s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) getArtistAlbums(ctx context.Context, artistID string) (map[string]any, error) {
|
||||||
|
const pageSize = 100
|
||||||
|
index := 0
|
||||||
|
total := -1
|
||||||
|
all := make([]any, 0)
|
||||||
|
for {
|
||||||
|
params := url.Values{}
|
||||||
|
params.Set("limit", strconv.Itoa(pageSize))
|
||||||
|
params.Set("index", strconv.Itoa(index))
|
||||||
|
resp, err := c.apiGet(ctx, "/artist/"+strings.TrimSpace(artistID)+"/albums", params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data, _ := resp["data"].([]any)
|
||||||
|
all = append(all, data...)
|
||||||
|
if total < 0 {
|
||||||
|
total = intFromAny(resp["total"])
|
||||||
|
}
|
||||||
|
if len(data) < pageSize {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
index += len(data)
|
||||||
|
if total > 0 && index >= total {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map[string]any{"data": all, "total": total}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) GetDownloadable(ctx context.Context, item string, _ int) (*provider.Downloadable, error) {
|
func (c *Client) GetDownloadable(ctx context.Context, item string, _ int) (*provider.Downloadable, error) {
|
||||||
if strings.TrimSpace(c.license) == "" {
|
if strings.TrimSpace(c.license) == "" {
|
||||||
if strings.TrimSpace(c.arl) != "" {
|
if strings.TrimSpace(c.arl) != "" {
|
||||||
|
|||||||
@@ -39,6 +39,58 @@ func TestSearchTrack(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetMetadataArtistPaginatesAlbums(t *testing.T) {
|
||||||
|
callCount := 0
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path != "/artist/9/albums" {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
callCount++
|
||||||
|
index := r.URL.Query().Get("index")
|
||||||
|
limit := r.URL.Query().Get("limit")
|
||||||
|
if limit != "100" {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]any{"error": map[string]any{"message": "bad limit"}})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch index {
|
||||||
|
case "0":
|
||||||
|
items := make([]any, 0, 100)
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
items = append(items, map[string]any{"id": i + 1, "title": "Album"})
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]any{"data": items, "total": 101})
|
||||||
|
case "100":
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]any{"data": []any{map[string]any{"id": 101, "title": "Album 101"}}, "total": 101})
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
cfgData := config.DefaultConfigData()
|
||||||
|
c := New(&config.Config{File: cfgData, Session: cfgData})
|
||||||
|
c.loggedIn = true
|
||||||
|
|
||||||
|
origBase := baseURL
|
||||||
|
baseURL = ts.URL
|
||||||
|
defer func() { baseURL = origBase }()
|
||||||
|
|
||||||
|
meta, err := c.GetMetadata(context.Background(), "9", "artist")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetMetadata() error = %v", err)
|
||||||
|
}
|
||||||
|
albumsObj, _ := meta["albums"].(map[string]any)
|
||||||
|
items, _ := albumsObj["items"].([]any)
|
||||||
|
if len(items) != 101 {
|
||||||
|
t.Fatalf("albums len = %d, want 101", len(items))
|
||||||
|
}
|
||||||
|
if callCount != 2 {
|
||||||
|
t.Fatalf("call count = %d, want 2", callCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetDownloadableNativeCipher(t *testing.T) {
|
func TestGetDownloadableNativeCipher(t *testing.T) {
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
switch r.URL.Path {
|
switch r.URL.Path {
|
||||||
|
|||||||
@@ -122,6 +122,9 @@ func (c *Client) GetMetadata(ctx context.Context, item, mediaType string) (map[s
|
|||||||
if mediaType == "label" {
|
if mediaType == "label" {
|
||||||
return c.getLabel(ctx, item)
|
return c.getLabel(ctx, item)
|
||||||
}
|
}
|
||||||
|
if mediaType == "artist" {
|
||||||
|
return c.getArtist(ctx, item)
|
||||||
|
}
|
||||||
|
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
params.Set("app_id", c.cfg.Session.Qobuz.AppID)
|
params.Set("app_id", c.cfg.Session.Qobuz.AppID)
|
||||||
@@ -130,8 +133,6 @@ func (c *Client) GetMetadata(ctx context.Context, item, mediaType string) (map[s
|
|||||||
params.Set("offset", "0")
|
params.Set("offset", "0")
|
||||||
|
|
||||||
switch mediaType {
|
switch mediaType {
|
||||||
case "artist":
|
|
||||||
params.Set("extra", "albums")
|
|
||||||
case "playlist":
|
case "playlist":
|
||||||
params.Set("extra", "tracks")
|
params.Set("extra", "tracks")
|
||||||
case "label":
|
case "label":
|
||||||
@@ -358,6 +359,77 @@ func (c *Client) getLabel(ctx context.Context, labelID string) (map[string]any,
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) getArtist(ctx context.Context, artistID string) (map[string]any, error) {
|
||||||
|
pageLimit := 500
|
||||||
|
params := url.Values{}
|
||||||
|
params.Set("app_id", c.cfg.Session.Qobuz.AppID)
|
||||||
|
params.Set("artist_id", artistID)
|
||||||
|
params.Set("limit", strconv.Itoa(pageLimit))
|
||||||
|
params.Set("offset", "0")
|
||||||
|
params.Set("extra", "albums")
|
||||||
|
|
||||||
|
resp, status, err := c.apiRequest(ctx, "artist/get", params, c.authHeaders())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if status != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("artist/get failed: status=%d", status)
|
||||||
|
}
|
||||||
|
|
||||||
|
albumsObj, ok := mapValue(resp["albums"])
|
||||||
|
if !ok {
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
items, ok := albumsObj["items"].([]any)
|
||||||
|
if !ok {
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
total, _ := intValue(resp["albums_count"])
|
||||||
|
if total <= 0 {
|
||||||
|
total, _ = intValue(albumsObj["total"])
|
||||||
|
}
|
||||||
|
if total <= pageLimit && len(items) < pageLimit {
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for offset := pageLimit; ; offset += pageLimit {
|
||||||
|
if total > 0 && offset >= total {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pageParams := url.Values{}
|
||||||
|
pageParams.Set("app_id", c.cfg.Session.Qobuz.AppID)
|
||||||
|
pageParams.Set("artist_id", artistID)
|
||||||
|
pageParams.Set("limit", strconv.Itoa(pageLimit))
|
||||||
|
pageParams.Set("offset", strconv.Itoa(offset))
|
||||||
|
pageParams.Set("extra", "albums")
|
||||||
|
|
||||||
|
pageResp, pageStatus, pageErr := c.apiRequest(ctx, "artist/get", pageParams, c.authHeaders())
|
||||||
|
if pageErr != nil {
|
||||||
|
return nil, pageErr
|
||||||
|
}
|
||||||
|
if pageStatus != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("artist/get pagination failed: status=%d offset=%d", pageStatus, offset)
|
||||||
|
}
|
||||||
|
pageAlbums, ok := mapValue(pageResp["albums"])
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pageItems, ok := pageAlbums["items"].([]any)
|
||||||
|
if !ok || len(pageItems) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
items = append(items, pageItems...)
|
||||||
|
if len(pageItems) < pageLimit {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
albumsObj["items"] = items
|
||||||
|
resp["albums"] = albumsObj
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) authHeaders() map[string]string {
|
func (c *Client) authHeaders() map[string]string {
|
||||||
headers := map[string]string{"X-App-Id": c.cfg.Session.Qobuz.AppID}
|
headers := map[string]string{"X-App-Id": c.cfg.Session.Qobuz.AppID}
|
||||||
if c.uat != "" {
|
if c.uat != "" {
|
||||||
|
|||||||
@@ -157,6 +157,53 @@ func TestGetLabelPagination(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetArtistPagination(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
offset := r.URL.Query().Get("offset")
|
||||||
|
if offset == "" {
|
||||||
|
offset = "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := map[string]any{}
|
||||||
|
switch offset {
|
||||||
|
case "0":
|
||||||
|
resp = map[string]any{
|
||||||
|
"albums_count": 620,
|
||||||
|
"albums": map[string]any{"items": makeItems(0, 500)},
|
||||||
|
}
|
||||||
|
case "500":
|
||||||
|
resp = map[string]any{"albums": map[string]any{"items": makeItems(500, 620)}}
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]any{"message": "not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = json.NewEncoder(w).Encode(resp)
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
c := newTestClient(t)
|
||||||
|
c.loggedIn = true
|
||||||
|
c.baseURL = ts.URL
|
||||||
|
|
||||||
|
raw, err := c.GetMetadata(context.Background(), "artist-id", "artist")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetMetadata() error = %v", err)
|
||||||
|
}
|
||||||
|
albums, ok := mapValue(raw["albums"])
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("albums missing")
|
||||||
|
}
|
||||||
|
items, ok := albums["items"].([]any)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("items missing")
|
||||||
|
}
|
||||||
|
if len(items) != 620 {
|
||||||
|
t.Fatalf("len(items) = %d, want 620", len(items))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func newTestClient(t *testing.T) *Client {
|
func newTestClient(t *testing.T) *Client {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
d := config.DefaultConfigData()
|
d := config.DefaultConfigData()
|
||||||
|
|||||||
Reference in New Issue
Block a user