mirror of
https://git.sr.ht/~joren/streamrip-go
synced 2026-06-17 15:05:39 +02:00
harden deezer quality fallback and metadata handling
Improve Deezer full-quality mode behavior by returning explicit errors when yt-dlp mode fails with fallback disabled, parse structured API errors, and correctly map explicit_lyrics booleans into explicit tags.
This commit is contained in:
@@ -175,7 +175,7 @@ func (c *Client) GetDownloadable(ctx context.Context, item string, _ int) (*prov
|
|||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
if !c.cfg.Session.Deezer.LowerQualityIfNotAvailable {
|
if !c.cfg.Session.Deezer.LowerQualityIfNotAvailable {
|
||||||
return nil, dlErr
|
return nil, fmt.Errorf("deezer full-quality mode failed and fallback is disabled: %w", dlErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
preview := strings.TrimSpace(stringFromAny(meta["preview"]))
|
preview := strings.TrimSpace(stringFromAny(meta["preview"]))
|
||||||
@@ -352,8 +352,15 @@ func (c *Client) apiGet(ctx context.Context, path string, params url.Values) (ma
|
|||||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
return nil, fmt.Errorf("deezer api failed: status=%d body=%s", resp.StatusCode, string(body))
|
return nil, fmt.Errorf("deezer api failed: status=%d body=%s", resp.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
if e := stringFromAny(out["error"]); e != "" {
|
if errObj, ok := out["error"].(map[string]any); ok {
|
||||||
return nil, fmt.Errorf("deezer api error: %s", e)
|
msg := strings.TrimSpace(stringFromAny(errObj["message"]))
|
||||||
|
if msg == "" {
|
||||||
|
msg = strings.TrimSpace(stringFromAny(errObj["type"]))
|
||||||
|
}
|
||||||
|
if msg == "" {
|
||||||
|
msg = "unknown deezer error"
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("deezer api error: %s", msg)
|
||||||
}
|
}
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
@@ -375,7 +382,7 @@ func enrichTrack(track map[string]any) {
|
|||||||
track["media_number"] = d
|
track["media_number"] = d
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if v := stringFromAny(track["explicit_lyrics"]); v == "true" {
|
if boolFromAny(track["explicit_lyrics"]) {
|
||||||
track["explicit"] = true
|
track["explicit"] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -441,6 +448,11 @@ func intFromAny(v any) int {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func boolFromAny(v any) bool {
|
||||||
|
b, ok := v.(bool)
|
||||||
|
return ok && b
|
||||||
|
}
|
||||||
|
|
||||||
func runCommand(ctx context.Context, name string, args ...string) ([]byte, error) {
|
func runCommand(ctx context.Context, name string, args ...string) ([]byte, error) {
|
||||||
cmd := exec.CommandContext(ctx, name, args...)
|
cmd := exec.CommandContext(ctx, name, args...)
|
||||||
b, err := cmd.CombinedOutput()
|
b, err := cmd.CombinedOutput()
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package deezer
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"streamrip-go/internal/config"
|
"streamrip-go/internal/config"
|
||||||
@@ -64,3 +66,87 @@ func TestGetDownloadableUsesPreview(t *testing.T) {
|
|||||||
t.Fatalf("unexpected downloadable: %+v", d)
|
t.Fatalf("unexpected downloadable: %+v", d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetMetadataSetsExplicitFromBool(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.URL.Path {
|
||||||
|
case "/track/9":
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||||
|
"id": 9,
|
||||||
|
"title": "X",
|
||||||
|
"explicit_lyrics": true,
|
||||||
|
"artist": map[string]any{"name": "Artist"},
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
cfgData := config.DefaultConfigData()
|
||||||
|
c := New(&config.Config{File: cfgData, Session: cfgData})
|
||||||
|
c.loggedIn = true
|
||||||
|
orig := baseURL
|
||||||
|
baseURL = ts.URL
|
||||||
|
defer func() { baseURL = orig }()
|
||||||
|
|
||||||
|
meta, err := c.GetMetadata(context.Background(), "9", "track")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetMetadata() error = %v", err)
|
||||||
|
}
|
||||||
|
if explicit, _ := meta["explicit"].(bool); !explicit {
|
||||||
|
t.Fatalf("expected explicit=true, got %#v", meta["explicit"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSearchReturnsStructuredAPIError(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path == "/search/track" {
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]any{"error": map[string]any{"message": "invalid query"}})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
cfgData := config.DefaultConfigData()
|
||||||
|
c := New(&config.Config{File: cfgData, Session: cfgData})
|
||||||
|
c.loggedIn = true
|
||||||
|
orig := baseURL
|
||||||
|
baseURL = ts.URL
|
||||||
|
defer func() { baseURL = orig }()
|
||||||
|
|
||||||
|
_, err := c.Search(context.Background(), "track", "", 5)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "invalid query") {
|
||||||
|
t.Fatalf("expected structured deezer error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetDownloadableErrorsWhenFullQualityFailsAndFallbackDisabled(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path == "/track/42" {
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]any{"id": 42, "title": "X", "preview": "https://cdn.example/p.mp3"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
cfgData := config.DefaultConfigData()
|
||||||
|
cfgData.Deezer.UseDeezloader = true
|
||||||
|
cfgData.Deezer.LowerQualityIfNotAvailable = false
|
||||||
|
c := New(&config.Config{File: cfgData, Session: cfgData})
|
||||||
|
c.loggedIn = true
|
||||||
|
c.bin = "definitely-not-a-real-yt-dlp-bin"
|
||||||
|
c.run = func(context.Context, string, ...string) ([]byte, error) {
|
||||||
|
return nil, fmt.Errorf("unexpected run call")
|
||||||
|
}
|
||||||
|
orig := baseURL
|
||||||
|
baseURL = ts.URL
|
||||||
|
defer func() { baseURL = orig }()
|
||||||
|
|
||||||
|
_, err := c.GetDownloadable(context.Background(), "42", 2)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "full-quality mode failed") {
|
||||||
|
t.Fatalf("expected full-quality failure error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user