refactor: simplify codebase (-209 lines)
This commit is contained in:
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user