refactor: simplify codebase (-209 lines)

This commit is contained in:
Mathis Maquenne
2026-03-01 21:55:42 +01:00
parent cd45c3f40a
commit 08b608ec80
31 changed files with 389 additions and 598 deletions
+1 -3
View File
@@ -67,9 +67,7 @@ func (a *Album) SetSongs(s []*Song) {
func (a *Album) GetOutputDir(outputDir string) string {
base := fmt.Sprintf("%s - %s", a.Results.Data.Artist, a.Results.Data.Title)
base, _ = filenamify.Filenamify(base, filenamify.Options{})
outputDir = path.Join(outputDir, base)
return outputDir
return path.Join(outputDir, base)
}
func (a *Album) Unmarshal(data []byte) error {
+30 -35
View File
@@ -22,6 +22,35 @@ type Artist struct {
} `json:"results"`
}
func (a *Artist) String() string {
songs := a.Results.Songs.Data
count := len(songs)
totalSec := 0
for _, s := range songs {
if d, err := strconv.Atoi(s.Duration); err == nil {
totalSec += d
}
}
limit := min(3, count)
var b strings.Builder
fmt.Fprintf(&b, "============= [ Artist Info ] =============\n")
fmt.Fprintf(&b, "Artist: %s\n", a.Results.Data.Name)
fmt.Fprintf(&b, "Tracks: %d\n", count)
fmt.Fprintf(&b, "Playtime: %s\n", time.Duration(totalSec)*time.Second)
fmt.Fprintf(&b, "-------------------------------------------\n")
fmt.Fprintf(&b, "Top %d most popular tracks:\n", limit)
for i := 0; i < limit; i++ {
s := songs[i]
fmt.Fprintf(&b, " %2d. %s %s\n", i+1, s.Artist, s.GetTitle())
}
fmt.Fprintf(&b, "===========================================\n")
return b.String()
}
func (a *Artist) GetType() string {
return "Artist"
}
@@ -39,44 +68,10 @@ func (a *Artist) SetSongs(s []*Song) {
}
func (a *Artist) GetOutputDir(outputDir string) string {
base, _ := filenamify.Filenamify(a.GetTitle(), filenamify.Options{})
base, _ := filenamify.Filenamify(a.Results.Data.Name, filenamify.Options{})
return path.Join(outputDir, base)
}
func (a *Artist) Unmarshal(data []byte) error {
return json.Unmarshal(data, a)
}
func (a *Artist) String() string {
tracks := a.GetSongs()
count := len(tracks)
limit := 3
if count < limit {
limit = count
}
totalSec := 0
for _, s := range tracks {
if d, err := strconv.Atoi(s.Duration); err == nil {
totalSec += d
}
}
totalDuration := time.Duration(totalSec) * time.Second
var b strings.Builder
fmt.Fprintf(&b, "============= [ Artist Info ] =============\n")
fmt.Fprintf(&b, "Artist: %s\n", a.GetTitle())
fmt.Fprintf(&b, "Tracks: %d\n", count)
fmt.Fprintf(&b, "Playtime: %s\n", totalDuration)
fmt.Fprintf(&b, "-------------------------------------------\n")
fmt.Fprintf(&b, "Top %d most popular tracks:\n", limit)
for i := 0; i < limit; i++ {
s := tracks[i]
title := s.GetTitle()
fmt.Fprintf(&b, " %2d. %s %s\n", i+1, s.Artist, title)
}
fmt.Fprintf(&b, "===========================================\n")
return b.String()
}
+31 -33
View File
@@ -38,18 +38,21 @@ func (c *Client) FetchResource(ctx context.Context, resource Resource, id string
"tags": true,
"header": true,
}
switch r := resource.(type) {
var idKey string
switch resource.(type) {
case *Playlist:
payload["playlist_id"] = id
idKey = "playlist_id"
case *Album:
payload["alb_id"] = id
idKey = "alb_id"
case *Artist:
payload["art_id"] = id
idKey = "art_id"
case *Track:
payload["sng_id"] = id
idKey = "sng_id"
default:
return fmt.Errorf("unsupported resource type: %T", r)
return fmt.Errorf("unsupported resource type: %T", resource)
}
payload[idKey] = id
jsonData, err := json.Marshal(payload)
if err != nil {
@@ -77,18 +80,22 @@ func (c *Client) FetchResource(ctx context.Context, resource Resource, id string
return err
}
switch {
case strings.Contains(string(body), `"DATA_ERROR":"playlist::getData"`):
return fmt.Errorf("invalid playlist ID")
case strings.Contains(string(body), `"DATA_ERROR":"album::getData"`):
return fmt.Errorf("invalid album ID")
case strings.Contains(string(body), `"DATA_ERROR":"artist::getData"`):
return fmt.Errorf("invalid artist ID")
case strings.Contains(string(body), `"DATA_ERROR":"song::getData"`):
return fmt.Errorf("invalid track ID")
bodyStr := string(body)
for _, check := range []struct {
marker string
errMsg string
}{
{`"DATA_ERROR":"playlist::getData"`, "invalid playlist ID"},
{`"DATA_ERROR":"album::getData"`, "invalid album ID"},
{`"DATA_ERROR":"artist::getData"`, "invalid artist ID"},
{`"DATA_ERROR":"song::getData"`, "invalid track ID"},
} {
if strings.Contains(bodyStr, check.marker) {
return fmt.Errorf("%s", check.errMsg)
}
}
if strings.Contains(string(body), `"results":{}`) {
if strings.Contains(bodyStr, `"results":{}`) {
return fmt.Errorf("unexpected response")
}
@@ -96,18 +103,13 @@ func (c *Client) FetchResource(ctx context.Context, resource Resource, id string
}
func (c *Client) FetchMedia(ctx context.Context, song *Song, quality string) (*Media, error) {
var formats string
switch quality {
case "mp3_128":
formats = `[{"cipher":"BF_CBC_STRIPE","format":"MP3_128"}]`
case "mp3_320":
formats = `[{"cipher":"BF_CBC_STRIPE","format":"MP3_320"},{"cipher":"BF_CBC_STRIPE","format":"MP3_128"}]`
case "flac":
formats = `[{"cipher":"BF_CBC_STRIPE","format":"FLAC"},{"cipher":"BF_CBC_STRIPE","format":"MP3_320"},{"cipher":"BF_CBC_STRIPE","format":"MP3_128"}]`
qualityFormats := map[string]string{
"mp3_128": `[{"cipher":"BF_CBC_STRIPE","format":"MP3_128"}]`,
"mp3_320": `[{"cipher":"BF_CBC_STRIPE","format":"MP3_320"},{"cipher":"BF_CBC_STRIPE","format":"MP3_128"}]`,
"flac": `[{"cipher":"BF_CBC_STRIPE","format":"FLAC"},{"cipher":"BF_CBC_STRIPE","format":"MP3_320"},{"cipher":"BF_CBC_STRIPE","format":"MP3_128"}]`,
}
reqBody := fmt.Sprintf(`{"license_token":"%s","media":[{"type":"FULL","formats":%s}],"track_tokens":["%s"]}`, c.Session.LicenseToken, formats, song.TrackToken)
reqBody := fmt.Sprintf(`{"license_token":"%s","media":[{"type":"FULL","formats":%s}],"track_tokens":["%s"]}`, c.Session.LicenseToken, qualityFormats[quality], song.TrackToken)
req, err := http.NewRequestWithContext(ctx, "POST", "https://media.deezer.com/v1/get_url", bytes.NewBuffer([]byte(reqBody)))
if err != nil {
return nil, err
@@ -129,8 +131,7 @@ func (c *Client) FetchMedia(ctx context.Context, song *Song, quality string) (*M
}
var media Media
err = json.Unmarshal(body, &media)
if err != nil {
if err := json.Unmarshal(body, &media); err != nil {
return nil, err
}
@@ -138,7 +139,6 @@ func (c *Client) FetchMedia(ctx context.Context, song *Song, quality string) (*M
if media.Errors[0].Code == 1000 {
return nil, fmt.Errorf("invalid license token")
}
return nil, fmt.Errorf("%s", media.Errors[0].Message)
}
@@ -146,7 +146,6 @@ func (c *Client) FetchMedia(ctx context.Context, song *Song, quality string) (*M
if media.Data[0].Errors[0].Code == 2002 {
return nil, fmt.Errorf("invalid track token")
}
return nil, fmt.Errorf("%s", media.Data[0].Errors[0].Message)
}
@@ -177,9 +176,8 @@ func (c *Client) FetchCoverImage(ctx context.Context, song *Song) ([]byte, error
return io.ReadAll(resp.Body)
}
func (c *Client) GetMediaStream(ctx context.Context, media *Media, songID string) (io.ReadCloser, error) {
url := media.GetURL()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
func (c *Client) GetMediaStream(ctx context.Context, media *Media) (io.ReadCloser, error) {
req, err := http.NewRequestWithContext(ctx, "GET", media.GetURL(), nil)
if err != nil {
return nil, err
}
+7 -16
View File
@@ -1,32 +1,23 @@
package deezer
type Media struct {
Errors []MediaError `json:"errors"`
Errors []mediaError `json:"errors"`
Data []struct {
Media []struct {
Type string `json:"media_type"`
Cipher Cipher `json:"cipher"`
Format string `json:"format"`
Sources []Source `json:"sources"`
Format string `json:"format"`
Sources []struct {
URL string `json:"url"`
} `json:"sources"`
}
Errors []MediaError `json:"errors"`
Errors []mediaError `json:"errors"`
} `json:"data"`
}
type MediaError struct {
type mediaError struct {
Code int `json:"code"`
Message string `json:"message"`
}
type Cipher struct {
Type string `json:"type"`
}
type Source struct {
URL string `json:"url"`
Provider string `json:"provider"`
}
func (m *Media) GetURL() string {
return m.Data[0].Media[0].Sources[0].URL
}
+2 -5
View File
@@ -13,7 +13,6 @@ type Playlist struct {
Results struct {
Data struct {
Title string `json:"TITLE"`
Status int `json:"STATUS"`
Creator string `json:"PARENT_USERNAME"`
Duration int `json:"DURATION"`
} `json:"DATA"`
@@ -55,10 +54,8 @@ func (p *Playlist) SetSongs(s []*Song) {
}
func (p *Playlist) GetOutputDir(outputDir string) string {
p.Results.Data.Title, _ = filenamify.Filenamify(p.Results.Data.Title, filenamify.Options{})
outputDir = path.Join(outputDir, p.Results.Data.Title)
return outputDir
base, _ := filenamify.Filenamify(p.Results.Data.Title, filenamify.Options{})
return path.Join(outputDir, base)
}
func (p *Playlist) Unmarshal(data []byte) error {
+17 -22
View File
@@ -10,22 +10,7 @@ import (
"time"
)
type UserDataResponse struct {
Results struct {
APIToken string `json:"checkForm"`
User struct {
Id int `json:"USER_ID"`
Options struct {
LicenseToken string `json:"license_token"`
MobileOffline bool `json:"mobile_offline"`
WebOffline bool `json:"web_offline"`
} `json:"OPTIONS"`
} `json:"USER"`
} `json:"results"`
}
type Session struct {
ArlCookie string
APIToken string
LicenseToken string
HttpClient *http.Client
@@ -68,22 +53,32 @@ func Authenticate(ctx context.Context, arlCookie string) (*Session, error) {
return nil, err
}
var res UserDataResponse
var res struct {
Results struct {
APIToken string `json:"checkForm"`
User struct {
ID int `json:"USER_ID"`
Options struct {
LicenseToken string `json:"license_token"`
MobileOffline bool `json:"mobile_offline"`
WebOffline bool `json:"web_offline"`
} `json:"OPTIONS"`
} `json:"USER"`
} `json:"results"`
}
if err := json.Unmarshal(body, &res); err != nil {
return nil, err
}
if res.Results.User.Id == 0 {
if res.Results.User.ID == 0 {
return nil, fmt.Errorf("invalid arl cookie")
}
isPremium := res.Results.User.Options.MobileOffline || res.Results.User.Options.WebOffline
opts := res.Results.User.Options
return &Session{
ArlCookie: arlCookie,
APIToken: res.Results.APIToken,
LicenseToken: res.Results.User.Options.LicenseToken,
LicenseToken: opts.LicenseToken,
HttpClient: client,
Premium: isPremium,
Premium: opts.MobileOffline || opts.WebOffline,
}, nil
}
+7 -9
View File
@@ -40,26 +40,24 @@ type Song struct {
}
func (s *Song) GetTitle() string {
songTitle := s.Title
if s.Version != "" {
songTitle = fmt.Sprintf("%s %s", s.Title, s.Version)
return s.Title + " " + s.Version
}
return songTitle
return s.Title
}
func (s *Song) GetFileName(resourceType, mediaFormat string, song *Song) string {
func (s *Song) GetFileName(resourceType, mediaFormat string) string {
ext := "mp3"
if mediaFormat == "FLAC" {
ext = "flac"
}
trackNumber := ""
prefix := ""
if resourceType == "album" {
trackNumber = song.TrackNumber + ". "
prefix = s.TrackNumber + ". "
}
fileName := fmt.Sprintf("%s%s - %s.%s", trackNumber, s.Artist, s.GetTitle(), ext)
fileName := fmt.Sprintf("%s%s - %s.%s", prefix, s.Artist, s.GetTitle(), ext)
fileName, _ = filenamify.Filenamify(fileName, filenamify.Options{MaxLength: 255})
return fileName
}
+7 -19
View File
@@ -4,9 +4,8 @@ import (
"encoding/json"
"fmt"
"path"
"strconv"
"time"
"github.com/flytam/filenamify"
)
type Track struct {
@@ -20,11 +19,9 @@ func (t *Track) String() string {
return "Track: No data available"
}
duration := "Unknown"
if t.Results.Data.Duration != "" {
if d, err := time.ParseDuration(t.Results.Data.Duration + "s"); err == nil {
duration = d.String()
}
duration, err := strconv.Atoi(t.Results.Data.Duration)
if err != nil {
duration = 0
}
return fmt.Sprintf(
@@ -35,7 +32,7 @@ Duration: %s
==================================================`,
t.Results.Data.GetTitle(),
t.Results.Data.Artist,
duration,
time.Duration(duration)*time.Second,
)
}
@@ -52,7 +49,7 @@ func (t *Track) GetTitle() string {
func (t *Track) GetSongs() []*Song {
if t.Results.Data == nil {
return []*Song{}
return nil
}
return []*Song{t.Results.Data}
}
@@ -60,16 +57,7 @@ func (t *Track) GetSongs() []*Song {
func (t *Track) SetSongs(songs []*Song) {}
func (t *Track) GetOutputDir(outputDir string) string {
if t.Results.Data == nil {
return outputDir
}
// For single tracks, create a simple "Singles" folder
base := "Singles"
base, _ = filenamify.Filenamify(base, filenamify.Options{})
outputDir = path.Join(outputDir, base)
return outputDir
return path.Join(outputDir, "Singles")
}
func (t *Track) Unmarshal(data []byte) error {