mirror of
https://github.com/Superredstone/spotiflac-cli.git
synced 2026-03-07 20:18:07 +01:00
fix: various bugs and general code improvements
This commit is contained in:
@@ -1,27 +1,29 @@
|
|||||||
package lib
|
package lib
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
UserAgent string // User agent used for scraping requests
|
|
||||||
SelectedTidalApiUrl string
|
SelectedTidalApiUrl string
|
||||||
Verbose bool
|
Verbose bool
|
||||||
SpotifyClient *SpotifyClient
|
SpotifyClient *SpotifyClient
|
||||||
ApiInterval int // How many ms to wait between one call to apis and the other
|
ApiInterval int // How many ms to wait between one call to apis and the other
|
||||||
|
NoFallback bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApp() App {
|
func NewApp() App {
|
||||||
return App{
|
return App{
|
||||||
UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36",
|
|
||||||
Verbose: false,
|
Verbose: false,
|
||||||
ApiInterval: 800,
|
ApiInterval: 800,
|
||||||
|
NoFallback: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) Init() error {
|
func (app *App) Init() error {
|
||||||
|
app.log("Initializing Tidal")
|
||||||
err := app.LoadTidalApis()
|
err := app.LoadTidalApis()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.log("Initializing Spotify")
|
||||||
if err := app.InitSpotifyClient(); err != nil {
|
if err := app.InitSpotifyClient(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,11 +40,20 @@ func (app *App) Download(url string, outputFile string, service string, quality
|
|||||||
|
|
||||||
switch urlType {
|
switch urlType {
|
||||||
case UrlTypeTrack:
|
case UrlTypeTrack:
|
||||||
outputFileRune := []rune(outputFile)
|
metadata, err := app.GetTrackMetadata(url)
|
||||||
lastCharacter := string(outputFileRune[len(outputFileRune)-1:])
|
if err != nil {
|
||||||
downloadInFolder := lastCharacter == "/"
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err := app.DownloadTrack(url, outputFile, service, quality, downloadInFolder); err != nil {
|
isDir := IsPathDirectory(outputFile)
|
||||||
|
if outputFile == "" && !isDir {
|
||||||
|
outputFile, err = BuildFileName(metadata, "flac")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := app.DownloadTrack(url, outputFile, service, quality, isDir, metadata); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +99,7 @@ func (app *App) DownloadPlaylist(url string, outputFile string, service string,
|
|||||||
|
|
||||||
fmt.Println("[" + strconv.Itoa(idx+1) + "/" + strconv.Itoa(trackListSize) + "] " + metadata.Data.TrackUnion.Name + " - " + artists)
|
fmt.Println("[" + strconv.Itoa(idx+1) + "/" + strconv.Itoa(trackListSize) + "] " + metadata.Data.TrackUnion.Name + " - " + artists)
|
||||||
|
|
||||||
if err := app.DownloadTrack(url, outputFile+"/", service, quality, true); err != nil {
|
if err := app.DownloadTrack(url, outputFile+"/", service, quality, true, metadata); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,6 +120,11 @@ func (app *App) GetDownloadUrlOrFallback(askedService string, quality string, so
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This could have been implemented in a more clear way
|
||||||
|
if app.NoFallback {
|
||||||
|
servicesToTry = []string{servicesToTry[0]}
|
||||||
|
}
|
||||||
|
|
||||||
var downloadUrl string
|
var downloadUrl string
|
||||||
var lastError error
|
var lastError error
|
||||||
for idx, service := range servicesToTry {
|
for idx, service := range servicesToTry {
|
||||||
@@ -118,19 +132,19 @@ func (app *App) GetDownloadUrlOrFallback(askedService string, quality string, so
|
|||||||
app.log("Falling back to " + service)
|
app.log("Falling back to " + service)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
songId, err := app.GetIdFromSonglink(songlink)
|
||||||
|
if err != nil {
|
||||||
|
lastError = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
switch service {
|
switch service {
|
||||||
case "tidal":
|
case "tidal":
|
||||||
if songlink.LinksByPlatform.Tidal == nil {
|
if songlink.LinksByPlatform.Tidal == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
tidalId, err := app.GetTidalIdFromSonglink(songlink)
|
downloadUrl, err = app.GetTidalDownloadUrl(songId, quality)
|
||||||
if err != nil {
|
|
||||||
lastError = err
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadUrl, err = app.GetTidalDownloadUrl(tidalId, quality)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lastError = err
|
lastError = err
|
||||||
continue
|
continue
|
||||||
@@ -147,7 +161,7 @@ func (app *App) GetDownloadUrlOrFallback(askedService string, quality string, so
|
|||||||
return downloadUrl, nil
|
return downloadUrl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) DownloadTrack(url string, outputFile string, service string, quality string, downloadInFolder bool) error {
|
func (app *App) DownloadTrack(url string, outputFile string, service string, quality string, downloadInFolder bool, metadata TrackMetadata) error {
|
||||||
songlink, err := app.ConvertSongUrl(url)
|
songlink, err := app.ConvertSongUrl(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -158,11 +172,6 @@ func (app *App) DownloadTrack(url string, outputFile string, service string, qua
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata, err := app.GetTrackMetadata(url)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
extension, err := GetFormatFromQuality(quality)
|
extension, err := GetFormatFromQuality(quality)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ type SongLinkResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type LinksByPlatform struct {
|
type LinksByPlatform struct {
|
||||||
Deezer *LinkByPlatform `json:"deezer,omitempty"`
|
|
||||||
Tidal *LinkByPlatform `json:"tidal,omitempty"`
|
Tidal *LinkByPlatform `json:"tidal,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
25
lib/tidal.go
25
lib/tidal.go
@@ -10,12 +10,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrTidalUrlNotFound = errors.New("Tidal URL not found.")
|
|
||||||
|
|
||||||
func (app *App) LoadTidalApis() error {
|
func (app *App) LoadTidalApis() error {
|
||||||
var found bool
|
var found bool
|
||||||
|
|
||||||
for _, url := range app.GetAvailableApis() {
|
for _, url := range app.GetAvailableTidalApis() {
|
||||||
res, err := http.Get(url)
|
res, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
@@ -35,7 +33,7 @@ func (app *App) LoadTidalApis() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) GetAvailableApis() []string {
|
func (app *App) GetAvailableTidalApis() []string {
|
||||||
// TODO: Make this load from a JSON file inside of $HOME/.config/spotiflac-cli/apis.json
|
// TODO: Make this load from a JSON file inside of $HOME/.config/spotiflac-cli/apis.json
|
||||||
return []string{
|
return []string{
|
||||||
"https://triton.squid.wtf",
|
"https://triton.squid.wtf",
|
||||||
@@ -64,14 +62,7 @@ type TidalAPIResponseV2 struct {
|
|||||||
func (app *App) GetTidalDownloadUrl(tidalId string, quality string) (string, error) {
|
func (app *App) GetTidalDownloadUrl(tidalId string, quality string) (string, error) {
|
||||||
url := fmt.Sprintf("%s/track/?id=%s&quality=%s", app.SelectedTidalApiUrl, tidalId, quality)
|
url := fmt.Sprintf("%s/track/?id=%s&quality=%s", app.SelectedTidalApiUrl, tidalId, quality)
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
rawResponse, err := http.Get(url)
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Set("User-Agent", app.UserAgent)
|
|
||||||
|
|
||||||
rawResponse, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -126,10 +117,10 @@ func (app *App) ParseTidalManifestFromBase64(manifestBase64 string) (TidalManife
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) GetTidalIdFromSonglink(songlink SongLinkResponse) (string, error) {
|
func (app *App) GetIdFromSonglink(songlink SongLinkResponse) (string, error) {
|
||||||
if songlink.LinksByPlatform.Tidal == nil {
|
if songlink.LinksByPlatform.Tidal != nil {
|
||||||
return "", ErrTidalUrlNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return ParseTrackId(songlink.LinksByPlatform.Tidal.Url)
|
return ParseTrackId(songlink.LinksByPlatform.Tidal.Url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return "", errors.New("No link found.")
|
||||||
|
}
|
||||||
|
|||||||
10
lib/utils.go
10
lib/utils.go
@@ -145,3 +145,13 @@ func SpotifyUriToLink(uri string) (string, error) {
|
|||||||
|
|
||||||
return BASE_SPOTIFY_TRACK_URL + spotifyId[2], nil
|
return BASE_SPOTIFY_TRACK_URL + spotifyId[2], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsPathDirectory(path string) bool {
|
||||||
|
pathRune := []rune(path)
|
||||||
|
if len(pathRune) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
lastCharacter := string(pathRune[len(pathRune)-1:])
|
||||||
|
return lastCharacter == "/"
|
||||||
|
}
|
||||||
|
|||||||
16
main.go
16
main.go
@@ -15,7 +15,10 @@ func main() {
|
|||||||
service := ""
|
service := ""
|
||||||
|
|
||||||
app := lib.NewApp()
|
app := lib.NewApp()
|
||||||
app.Init()
|
err := app.Init()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
cmd := &cli.Command{
|
cmd := &cli.Command{
|
||||||
Name: "spotiflac-cli",
|
Name: "spotiflac-cli",
|
||||||
@@ -31,13 +34,14 @@ func main() {
|
|||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "output",
|
Name: "output",
|
||||||
Aliases: []string{"o"},
|
Aliases: []string{"o"},
|
||||||
Usage: "set output folder",
|
Usage: "set output folder/file",
|
||||||
|
DefaultText: outputFolder,
|
||||||
Destination: &outputFolder,
|
Destination: &outputFolder,
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "service",
|
Name: "service",
|
||||||
Aliases: []string{"s"},
|
Aliases: []string{"s"},
|
||||||
Usage: "set service to tidal/amazon/qobuz (FFmpeg is required for amazon and qobuz)",
|
Usage: "set default service (only tidal is supported at the moment)",
|
||||||
Destination: &service,
|
Destination: &service,
|
||||||
},
|
},
|
||||||
&cli.IntFlag{
|
&cli.IntFlag{
|
||||||
@@ -47,6 +51,12 @@ func main() {
|
|||||||
DefaultText: strconv.Itoa(app.ApiInterval),
|
DefaultText: strconv.Itoa(app.ApiInterval),
|
||||||
Destination: &app.ApiInterval,
|
Destination: &app.ApiInterval,
|
||||||
},
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "no-fallback",
|
||||||
|
Usage: "do not fallback in case a source is not found",
|
||||||
|
DefaultText: strconv.FormatBool(app.NoFallback),
|
||||||
|
Destination: &app.NoFallback,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||||
song_url := cmd.Args().First()
|
song_url := cmd.Args().First()
|
||||||
|
|||||||
Reference in New Issue
Block a user