fix: various bugs and general code improvements

This commit is contained in:
2026-02-28 16:18:50 +01:00
parent 2fea9a79df
commit fcae48753b
6 changed files with 68 additions and 47 deletions

View File

@@ -1,27 +1,29 @@
package lib
type App struct {
UserAgent string // User agent used for scraping requests
SelectedTidalApiUrl string
Verbose bool
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 {
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,
NoFallback: false,
}
}
func (app *App) Init() error {
app.log("Initializing Tidal")
err := app.LoadTidalApis()
if err != nil {
return err
}
app.log("Initializing Spotify")
if err := app.InitSpotifyClient(); err != nil {
return err
}

View File

@@ -40,11 +40,20 @@ func (app *App) Download(url string, outputFile string, service string, quality
switch urlType {
case UrlTypeTrack:
outputFileRune := []rune(outputFile)
lastCharacter := string(outputFileRune[len(outputFileRune)-1:])
downloadInFolder := lastCharacter == "/"
metadata, err := app.GetTrackMetadata(url)
if err != nil {
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
}
@@ -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)
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
}
@@ -111,6 +120,11 @@ func (app *App) GetDownloadUrlOrFallback(askedService string, quality string, so
break
}
// This could have been implemented in a more clear way
if app.NoFallback {
servicesToTry = []string{servicesToTry[0]}
}
var downloadUrl string
var lastError error
for idx, service := range servicesToTry {
@@ -118,19 +132,19 @@ func (app *App) GetDownloadUrlOrFallback(askedService string, quality string, so
app.log("Falling back to " + service)
}
songId, err := app.GetIdFromSonglink(songlink)
if err != nil {
lastError = err
continue
}
switch service {
case "tidal":
if songlink.LinksByPlatform.Tidal == nil {
continue
}
tidalId, err := app.GetTidalIdFromSonglink(songlink)
if err != nil {
lastError = err
continue
}
downloadUrl, err = app.GetTidalDownloadUrl(tidalId, quality)
downloadUrl, err = app.GetTidalDownloadUrl(songId, quality)
if err != nil {
lastError = err
continue
@@ -147,7 +161,7 @@ func (app *App) GetDownloadUrlOrFallback(askedService string, quality string, so
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)
if err != nil {
return err
@@ -158,11 +172,6 @@ func (app *App) DownloadTrack(url string, outputFile string, service string, qua
return err
}
metadata, err := app.GetTrackMetadata(url)
if err != nil {
return err
}
extension, err := GetFormatFromQuality(quality)
if err != nil {
return err

View File

@@ -20,8 +20,7 @@ type SongLinkResponse struct {
}
type LinksByPlatform struct {
Deezer *LinkByPlatform `json:"deezer,omitempty"`
Tidal *LinkByPlatform `json:"tidal,omitempty"`
Tidal *LinkByPlatform `json:"tidal,omitempty"`
}
type LinkByPlatform struct {

View File

@@ -10,12 +10,10 @@ import (
"strings"
)
var ErrTidalUrlNotFound = errors.New("Tidal URL not found.")
func (app *App) LoadTidalApis() error {
var found bool
for _, url := range app.GetAvailableApis() {
for _, url := range app.GetAvailableTidalApis() {
res, err := http.Get(url)
if err != nil {
continue
@@ -35,7 +33,7 @@ func (app *App) LoadTidalApis() error {
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
return []string{
"https://triton.squid.wtf",
@@ -64,14 +62,7 @@ type TidalAPIResponseV2 struct {
func (app *App) GetTidalDownloadUrl(tidalId string, quality string) (string, error) {
url := fmt.Sprintf("%s/track/?id=%s&quality=%s", app.SelectedTidalApiUrl, tidalId, quality)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
req.Header.Set("User-Agent", app.UserAgent)
rawResponse, err := http.DefaultClient.Do(req)
rawResponse, err := http.Get(url)
if err != nil {
return "", err
}
@@ -126,10 +117,10 @@ func (app *App) ParseTidalManifestFromBase64(manifestBase64 string) (TidalManife
return result, nil
}
func (app *App) GetTidalIdFromSonglink(songlink SongLinkResponse) (string, error) {
if songlink.LinksByPlatform.Tidal == nil {
return "", ErrTidalUrlNotFound
func (app *App) GetIdFromSonglink(songlink SongLinkResponse) (string, error) {
if songlink.LinksByPlatform.Tidal != nil {
return ParseTrackId(songlink.LinksByPlatform.Tidal.Url)
}
return ParseTrackId(songlink.LinksByPlatform.Tidal.Url)
return "", errors.New("No link found.")
}

View File

@@ -137,7 +137,7 @@ func (app *App) InitSpotifyClient() error {
}
func SpotifyUriToLink(uri string) (string, error) {
spotifyId := strings.Split(uri, ":")
spotifyId := strings.Split(uri, ":")
if len(spotifyId) != 3 {
return "", errors.New("Invalid URI parsed.")
@@ -145,3 +145,13 @@ func SpotifyUriToLink(uri string) (string, error) {
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 == "/"
}

22
main.go
View File

@@ -15,7 +15,10 @@ func main() {
service := ""
app := lib.NewApp()
app.Init()
err := app.Init()
if err != nil {
log.Fatal(err)
}
cmd := &cli.Command{
Name: "spotiflac-cli",
@@ -31,22 +34,29 @@ func main() {
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "set output folder",
Usage: "set output folder/file",
DefaultText: outputFolder,
Destination: &outputFolder,
},
&cli.StringFlag{
Name: "service",
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,
},
&cli.IntFlag{
Name: "interval",
Aliases: []string{"i"},
Usage: "interval between api requests in milliseconds",
Name: "interval",
Aliases: []string{"i"},
Usage: "interval between api requests in milliseconds",
DefaultText: strconv.Itoa(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 {
song_url := cmd.Args().First()