style: prefer stdlib idioms for errors and http methods

This commit is contained in:
Mathis Maquenne
2026-08-05 21:43:59 +02:00
parent a695fb4de3
commit c1775e5c04
12 changed files with 50 additions and 44 deletions
+13 -12
View File
@@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@@ -62,7 +63,7 @@ func (c *Client) FetchResource(ctx context.Context, kind Kind, id string) (Resou
return nil, err return nil, err
} }
payload := map[string]interface{}{ payload := map[string]any{
"nb": 10000, "nb": 10000,
"start": 0, "start": 0,
"lang": "en", "lang": "en",
@@ -78,7 +79,7 @@ func (c *Client) FetchResource(ctx context.Context, kind Kind, id string) (Resou
} }
url := fmt.Sprintf("https://www.deezer.com/ajax/gw-light.php?method=deezer.page%s&input=3&api_version=1.0&api_token=%s", kind.pageMethod(), c.Session.APIToken) url := fmt.Sprintf("https://www.deezer.com/ajax/gw-light.php?method=deezer.page%s&input=3&api_version=1.0&api_token=%s", kind.pageMethod(), c.Session.APIToken)
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData)) req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -109,12 +110,12 @@ func (c *Client) FetchResource(ctx context.Context, kind Kind, id string) (Resou
{`"DATA_ERROR":"song::getData"`, "invalid track ID"}, {`"DATA_ERROR":"song::getData"`, "invalid track ID"},
} { } {
if strings.Contains(bodyStr, check.marker) { if strings.Contains(bodyStr, check.marker) {
return nil, fmt.Errorf("%s", check.errMsg) return nil, errors.New(check.errMsg)
} }
} }
if strings.Contains(bodyStr, `"results":{}`) { if strings.Contains(bodyStr, `"results":{}`) {
return nil, fmt.Errorf("unexpected response") return nil, errors.New("unexpected response")
} }
if err := resource.decode(body); err != nil { if err := resource.decode(body); err != nil {
@@ -132,7 +133,7 @@ func (c *Client) FetchMedia(ctx context.Context, track *Track, quality string) (
} }
reqBody := fmt.Sprintf(`{"license_token":"%s","media":[{"type":"FULL","formats":%s}],"track_tokens":["%s"]}`, c.Session.LicenseToken, qualityFormats[quality], track.TrackToken) reqBody := fmt.Sprintf(`{"license_token":"%s","media":[{"type":"FULL","formats":%s}],"track_tokens":["%s"]}`, c.Session.LicenseToken, qualityFormats[quality], track.TrackToken)
req, err := http.NewRequestWithContext(ctx, "POST", "https://media.deezer.com/v1/get_url", bytes.NewBuffer([]byte(reqBody))) req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://media.deezer.com/v1/get_url", bytes.NewBuffer([]byte(reqBody)))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -159,20 +160,20 @@ func (c *Client) FetchMedia(ctx context.Context, track *Track, quality string) (
if len(media.Errors) > 0 { if len(media.Errors) > 0 {
if media.Errors[0].Code == 1000 { if media.Errors[0].Code == 1000 {
return nil, fmt.Errorf("invalid license token") return nil, errors.New("invalid license token")
} }
return nil, fmt.Errorf("%s", media.Errors[0].Message) return nil, errors.New(media.Errors[0].Message)
} }
if len(media.Data) > 0 && len(media.Data[0].Errors) > 0 { if len(media.Data) > 0 && len(media.Data[0].Errors) > 0 {
if media.Data[0].Errors[0].Code == 2002 { if media.Data[0].Errors[0].Code == 2002 {
return nil, fmt.Errorf("invalid track token") return nil, errors.New("invalid track token")
} }
return nil, fmt.Errorf("%s", media.Data[0].Errors[0].Message) return nil, errors.New(media.Data[0].Errors[0].Message)
} }
if len(media.Data) == 0 || len(media.Data[0].Media) == 0 || len(media.Data[0].Media[0].Sources) == 0 { if len(media.Data) == 0 || len(media.Data[0].Media) == 0 || len(media.Data[0].Media[0].Sources) == 0 {
return nil, fmt.Errorf("no sources found") return nil, errors.New("no sources found")
} }
return &media, nil return &media, nil
@@ -180,7 +181,7 @@ func (c *Client) FetchMedia(ctx context.Context, track *Track, quality string) (
func (c *Client) FetchCoverImage(ctx context.Context, track *Track) ([]byte, error) { func (c *Client) FetchCoverImage(ctx context.Context, track *Track) ([]byte, error) {
url := fmt.Sprintf("https://e-cdn-images.dzcdn.net/images/cover/%s/500x500-000000-80-0-0.jpg", track.Cover) url := fmt.Sprintf("https://e-cdn-images.dzcdn.net/images/cover/%s/500x500-000000-80-0-0.jpg", track.Cover)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -199,7 +200,7 @@ func (c *Client) FetchCoverImage(ctx context.Context, track *Track) ([]byte, err
} }
func (c *Client) MediaStream(ctx context.Context, media *Media) (io.ReadCloser, error) { func (c *Client) MediaStream(ctx context.Context, media *Media) (io.ReadCloser, error) {
req, err := http.NewRequestWithContext(ctx, "GET", media.URL(), nil) req, err := http.NewRequestWithContext(ctx, http.MethodGet, media.URL(), nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+12 -11
View File
@@ -6,6 +6,7 @@ import (
"crypto/aes" "crypto/aes"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"math/rand/v2" "math/rand/v2"
@@ -47,7 +48,7 @@ func gatewayEnv() (string, string, error) {
apiKey := os.Getenv("DEEZER_MOBILE_API_KEY") apiKey := os.Getenv("DEEZER_MOBILE_API_KEY")
gwKey := os.Getenv("DEEZER_MOBILE_GW_KEY") gwKey := os.Getenv("DEEZER_MOBILE_GW_KEY")
if apiKey == "" || gwKey == "" { if apiKey == "" || gwKey == "" {
return "", "", fmt.Errorf("DEEZER_MOBILE_API_KEY and DEEZER_MOBILE_GW_KEY must be set to use email/password login") return "", "", errors.New("DEEZER_MOBILE_API_KEY and DEEZER_MOBILE_GW_KEY must be set to use email/password login")
} }
if len(gwKey) != aes.BlockSize { if len(gwKey) != aes.BlockSize {
@@ -89,7 +90,7 @@ func (m *mobileClient) login(ctx context.Context, email, password string) (*Cred
} }
func (m *mobileClient) authenticate(ctx context.Context) (string, string, string, error) { func (m *mobileClient) authenticate(ctx context.Context) (string, string, string, error) {
body, err := m.gatewayRequest(ctx, "mobile_auth", "GET", "uniq_id", genUniqID(), nil) body, err := m.gatewayRequest(ctx, "mobile_auth", http.MethodGet, "uniq_id", genUniqID(), nil)
if err != nil { if err != nil {
return "", "", "", err return "", "", "", err
} }
@@ -104,10 +105,10 @@ func (m *mobileClient) authenticate(ctx context.Context) (string, string, string
} }
if strings.Contains(string(body), "Undefined or invalid API key") { if strings.Contains(string(body), "Undefined or invalid API key") {
return "", "", "", fmt.Errorf("DEEZER_MOBILE_API_KEY is invalid") return "", "", "", errors.New("DEEZER_MOBILE_API_KEY is invalid")
} }
if strings.Contains(string(body), "GATEWAY_ERROR") || res.Results.Token == "" { if strings.Contains(string(body), "GATEWAY_ERROR") || res.Results.Token == "" {
return "", "", "", fmt.Errorf("unexpected response from gateway") return "", "", "", errors.New("unexpected response from gateway")
} }
encrypted, err := hex.DecodeString(res.Results.Token) encrypted, err := hex.DecodeString(res.Results.Token)
@@ -121,7 +122,7 @@ func (m *mobileClient) authenticate(ctx context.Context) (string, string, string
} }
if len(decrypted) < 96 { if len(decrypted) < 96 {
return "", "", "", fmt.Errorf("unexpected response from gateway") return "", "", "", errors.New("unexpected response from gateway")
} }
token := string(decrypted[0:64]) token := string(decrypted[0:64])
@@ -138,7 +139,7 @@ func (m *mobileClient) checkToken(ctx context.Context, token, tokenKey string) e
} }
authToken := hex.EncodeToString(encrypted) authToken := hex.EncodeToString(encrypted)
body, err := m.gatewayRequest(ctx, "api_checkToken", "GET", "auth_token", authToken, nil) body, err := m.gatewayRequest(ctx, "api_checkToken", http.MethodGet, "auth_token", authToken, nil)
if err != nil { if err != nil {
return err return err
} }
@@ -150,7 +151,7 @@ func (m *mobileClient) checkToken(ctx context.Context, token, tokenKey string) e
return err return err
} }
if res.Results == "" { if res.Results == "" {
return fmt.Errorf("unexpected response from gateway") return errors.New("unexpected response from gateway")
} }
m.sid = res.Results m.sid = res.Results
@@ -183,13 +184,13 @@ func (m *mobileClient) userAuth(ctx context.Context, email, password, userKey st
return "", "", err return "", "", err
} }
body, err := m.gatewayRequest(ctx, "mobile_userAuth", "POST", "", "", jsonBody) body, err := m.gatewayRequest(ctx, "mobile_userAuth", http.MethodPost, "", "", jsonBody)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
if strings.Contains(string(body), "USER_AUTH_ERROR") { if strings.Contains(string(body), "USER_AUTH_ERROR") {
return "", "", fmt.Errorf("invalid email or password") return "", "", errors.New("invalid email or password")
} }
var res struct { var res struct {
@@ -204,7 +205,7 @@ func (m *mobileClient) userAuth(ctx context.Context, email, password, userKey st
} }
if res.Results.ARL == "" || res.Results.UserID == 0 { if res.Results.ARL == "" || res.Results.UserID == 0 {
return "", "", fmt.Errorf("unexpected response from gateway") return "", "", errors.New("unexpected response from gateway")
} }
return res.Results.ARL, res.Results.BlogName, nil return res.Results.ARL, res.Results.BlogName, nil
@@ -220,7 +221,7 @@ func (m *mobileClient) gatewayRequest(ctx context.Context, method, httpMethod, p
q.Set("method", method) q.Set("method", method)
q.Set("api_key", m.apiKey) q.Set("api_key", m.apiKey)
q.Set("output", "3") q.Set("output", "3")
if httpMethod == "POST" { if httpMethod == http.MethodPost {
q.Set("input", "3") q.Set("input", "3")
} }
if m.sid != "" { if m.sid != "" {
+1 -1
View File
@@ -32,7 +32,7 @@ func resolveARL(ctx context.Context, validate func(ctx context.Context, arl stri
return arl, nil return arl, nil
} }
return "", fmt.Errorf("run 'godeez login' or export DEEZER_ARL environment variable") return "", errors.New("run 'godeez login' or export DEEZER_ARL environment variable")
} }
func Login(ctx context.Context, email, password string) (string, string, error) { func Login(ctx context.Context, email, password string) (string, string, error) {
+1 -1
View File
@@ -31,7 +31,7 @@ func authenticate(ctx context.Context, arlCookie string) (*Session, error) {
} }
url := "https://www.deezer.com/ajax/gw-light.php?method=deezer.getUserData&input=3&api_version=1.0&api_token=" url := "https://www.deezer.com/ajax/gw-light.php?method=deezer.getUserData&input=3&api_version=1.0&api_token="
req, err := http.NewRequestWithContext(ctx, "GET", url, nil) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+5 -4
View File
@@ -3,6 +3,7 @@ package download
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@@ -40,7 +41,7 @@ func findTrackURL(ctx context.Context, httpClient *http.Client, artist, title, d
values := neturl.Values{} values := neturl.Values{}
values.Add("query", fmt.Sprintf("%s %s", artist, title)) values.Add("query", fmt.Sprintf("%s %s", artist, title))
req, err := http.NewRequestWithContext(ctx, "POST", rootURL+"/searches", bytes.NewBufferString(values.Encode())) req, err := http.NewRequestWithContext(ctx, http.MethodPost, rootURL+"/searches", bytes.NewBufferString(values.Encode()))
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -102,14 +103,14 @@ func findTrackURL(ctx context.Context, httpClient *http.Client, artist, title, d
}) })
if matchURL == "" { if matchURL == "" {
return "", fmt.Errorf("no data found") return "", errors.New("no data found")
} }
return rootURL + matchURL, nil return rootURL + matchURL, nil
} }
func fetchBPMPage(ctx context.Context, httpClient *http.Client, url string) (string, error) { func fetchBPMPage(ctx context.Context, httpClient *http.Client, url string) (string, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -138,7 +139,7 @@ func parseBPM(html string) (bpmKey, error) {
modeMatch := modeRegex.FindStringSubmatch(html) modeMatch := modeRegex.FindStringSubmatch(html)
if len(bpmMatch) != 2 || len(keyMatch) != 2 || len(modeMatch) != 2 { if len(bpmMatch) != 2 || len(keyMatch) != 2 || len(modeMatch) != 2 {
return bpmKey{}, fmt.Errorf("no data found") return bpmKey{}, errors.New("no data found")
} }
bpm := bpmMatch[1] bpm := bpmMatch[1]
+3 -2
View File
@@ -2,6 +2,7 @@ package download
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
@@ -50,14 +51,14 @@ func fetchGenre(ctx context.Context, httpClient *http.Client, artist, title stri
filtered := filterTags(tags) filtered := filterTags(tags)
if len(filtered) == 0 { if len(filtered) == 0 {
return "", fmt.Errorf("no data found") return "", errors.New("no data found")
} }
return formatTags(filtered), nil return formatTags(filtered), nil
} }
func fetchGenrePage(ctx context.Context, httpClient *http.Client, url string) (*goquery.Document, error) { func fetchGenrePage(ctx context.Context, httpClient *http.Client, url string) (*goquery.Document, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+4 -3
View File
@@ -1,6 +1,7 @@
package download package download
import ( import (
"errors"
"fmt" "fmt"
"time" "time"
@@ -27,14 +28,14 @@ func (o *Options) Validate(kind deezer.Kind) error {
return fmt.Errorf("invalid quality option: %s", o.Quality) return fmt.Errorf("invalid quality option: %s", o.Quality)
} }
if o.Timeout <= 0 { if o.Timeout <= 0 {
return fmt.Errorf("timeout must be a positive duration") return errors.New("timeout must be a positive duration")
} }
if kind == deezer.KindArtist { if kind == deezer.KindArtist {
if o.Limit <= 0 { if o.Limit <= 0 {
return fmt.Errorf("limit must be a positive integer") return errors.New("limit must be a positive integer")
} }
if o.Limit > 100 { if o.Limit > 100 {
return fmt.Errorf("limit must not exceed 100") return errors.New("limit must not exceed 100")
} }
} }
+1 -1
View File
@@ -2,7 +2,7 @@ package download
import ( import (
"fmt" "fmt"
"math/rand" "math/rand/v2"
"os" "os"
"time" "time"
+3 -2
View File
@@ -2,6 +2,7 @@ package store
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"time" "time"
@@ -24,12 +25,12 @@ func (s *Store) DownloadInfo(trackID string) (*DownloadInfo, error) {
if err := s.db.View(func(tx *bbolt.Tx) error { if err := s.db.View(func(tx *bbolt.Tx) error {
b := tx.Bucket(trackBucket) b := tx.Bucket(trackBucket)
if b == nil { if b == nil {
return fmt.Errorf("bucket not found") return errors.New("bucket not found")
} }
data := b.Get([]byte(trackID)) data := b.Get([]byte(trackID))
if data == nil { if data == nil {
return fmt.Errorf("not found") return errors.New("not found")
} }
return json.Unmarshal(data, &info) return json.Unmarshal(data, &info)
}); err != nil { }); err != nil {
+4 -4
View File
@@ -6,19 +6,19 @@ import (
"path/filepath" "path/filepath"
"time" "time"
bolt "go.etcd.io/bbolt" "go.etcd.io/bbolt"
bolterrors "go.etcd.io/bbolt/errors" bolterrors "go.etcd.io/bbolt/errors"
) )
type Store struct { type Store struct {
db *bolt.DB db *bbolt.DB
} }
func Open(dir string) (*Store, error) { func Open(dir string) (*Store, error) {
db, err := bolt.Open(filepath.Join(dir, ".tracks.db"), 0600, &bolt.Options{Timeout: 5 * time.Second}) db, err := bbolt.Open(filepath.Join(dir, ".tracks.db"), 0600, &bbolt.Options{Timeout: 5 * time.Second})
if err != nil { if err != nil {
if errors.Is(err, bolterrors.ErrTimeout) { if errors.Is(err, bolterrors.ErrTimeout) {
return nil, fmt.Errorf("database is already in use by another process") return nil, errors.New("database is already in use by another process")
} }
return nil, fmt.Errorf("failed to open database: %w", err) return nil, fmt.Errorf("failed to open database: %w", err)
} }
+1 -2
View File
@@ -1,7 +1,6 @@
package tag package tag
import ( import (
"fmt"
"strconv" "strconv"
"strings" "strings"
@@ -37,7 +36,7 @@ func (t *id3v2Tagger) write(m Metadata) error {
t.addTag("TEXT", m.Lyricists) t.addTag("TEXT", m.Lyricists)
t.addTag("TCON", m.Genre) t.addTag("TCON", m.Genre)
if duration, err := strconv.Atoi(m.Duration); err == nil { if duration, err := strconv.Atoi(m.Duration); err == nil {
t.addTag("TLEN", fmt.Sprintf("%d", duration*1000)) t.addTag("TLEN", strconv.Itoa(duration*1000))
} }
t.addTag("TBPM", m.BPM) t.addTag("TBPM", m.BPM)
t.addTag("TKEY", m.Key) t.addTag("TKEY", m.Key)
+2 -1
View File
@@ -3,6 +3,7 @@ package update
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"runtime" "runtime"
@@ -46,7 +47,7 @@ func (u *Updater) Latest(ctx context.Context) (*Release, error) {
return nil, fmt.Errorf("failed to decode release: %w", err) return nil, fmt.Errorf("failed to decode release: %w", err)
} }
if release.TagName == "" { if release.TagName == "" {
return nil, fmt.Errorf("release has no tag name") return nil, errors.New("release has no tag name")
} }
return &release, nil return &release, nil