From 923705c7fcf17e061df65e956a48d44fa770ac20 Mon Sep 17 00:00:00 2001 From: Vladimir Zagainov Date: Sun, 18 Oct 2026 16:06:17 +0300 Subject: [PATCH] Migrate to yandex-music-api-go v0.0.0-20261018120746-dbf9a46bb2e7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Library now provides GetTrackDownloadURL (signed URL via XML download_info_url), so ym-dl delegates to it instead of reimplementing XML parsing and MD5 signing. Removed local DownloadTrackXML/DownloadInfoXML and unused crypto/md5, encoding/hex, encoding/xml, io imports from client.go. Track.Lyrics replaced by LyricsInfo (availability flags only, no text). Lyrics text requires separate GetTrackLyrics + GetTrackLyricsText calls — not implemented yet, lyrics field left empty. --- go.mod | 2 +- go.sum | 2 + internal/storage/storage.go | 7 ++- internal/yandex/client.go | 111 +----------------------------------- 4 files changed, 11 insertions(+), 111 deletions(-) diff --git a/go.mod b/go.mod index b0cadf5..d5302b1 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module ym-dl go 1.27.2 require ( - gitea.mrixs.me/Mrixs/yandex-music-api-go v0.0.0-20261018080126-dbb823ade349 + gitea.mrixs.me/Mrixs/yandex-music-api-go v0.0.0-20261018120746-dbf9a46bb2e7 github.com/bogem/id3v2 v1.2.0 gopkg.in/yaml.v3 v3.0.1 modernc.org/sqlite v1.60.1 diff --git a/go.sum b/go.sum index ab3fca0..ef3b5f8 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ gitea.mrixs.me/Mrixs/yandex-music-api-go v0.0.0-20261018072517-d0173f795d2a h1:y gitea.mrixs.me/Mrixs/yandex-music-api-go v0.0.0-20261018072517-d0173f795d2a/go.mod h1:L+GcqPRZiu0Srq6+YxfdLztltXV8DcxKjNUOXWJvR9o= gitea.mrixs.me/Mrixs/yandex-music-api-go v0.0.0-20261018080126-dbb823ade349 h1:3Rru+kVwHrDZwO1AcJ6e/ia/O4XKt6A3Nux1WXmDBDE= gitea.mrixs.me/Mrixs/yandex-music-api-go v0.0.0-20261018080126-dbb823ade349/go.mod h1:L+GcqPRZiu0Srq6+YxfdLztltXV8DcxKjNUOXWJvR9o= +gitea.mrixs.me/Mrixs/yandex-music-api-go v0.0.0-20261018120746-dbf9a46bb2e7 h1:efp/oD3K2Pwm+fRuGKrQo/VMYKfj0VVenn1zvZEBB/0= +gitea.mrixs.me/Mrixs/yandex-music-api-go v0.0.0-20261018120746-dbf9a46bb2e7/go.mod h1:L+GcqPRZiu0Srq6+YxfdLztltXV8DcxKjNUOXWJvR9o= github.com/bogem/id3v2 v1.2.0 h1:hKDF+F1gOgQ5r1QmBCEZUk4MveJbKxCeIDSBU7CQ4oI= github.com/bogem/id3v2 v1.2.0/go.mod h1:t78PK5AQ56Q47kizpYiV6gtjj3jfxlz87oFpty8DYs8= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= diff --git a/internal/storage/storage.go b/internal/storage/storage.go index 01c6513..2a7ca30 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -584,9 +584,12 @@ func BuildTrackMetaFromYandex(track *yandexmusic.Track, album *yandexmusic.Album } // Lyrics + // Note: track.LyricsInfo only contains availability flags, not the text. + // To fetch full lyrics text, call yandexClient.GetTrackLyrics + GetTrackLyricsText separately. var lyrics string - if track.Lyrics != nil && track.Lyrics.HasText { - lyrics = track.Lyrics.Text + if track.LyricsInfo != nil && track.LyricsInfo.HasAvailableTextLyrics { + // Lyrics text available but not fetched here — requires separate API call + lyrics = "" } return TrackMeta{ diff --git a/internal/yandex/client.go b/internal/yandex/client.go index 5439190..cd5e379 100644 --- a/internal/yandex/client.go +++ b/internal/yandex/client.go @@ -2,24 +2,17 @@ package yandex import ( "context" - "crypto/md5" - "encoding/hex" - "encoding/xml" "errors" "fmt" - "io" "log/slog" "net/http" "os" - "strings" "time" "ym-dl/internal/config" "gitea.mrixs.me/Mrixs/yandex-music-api-go/yandexmusic" ) -const downloadSalt = "XGRlBW9FXlekgbPrRHuSiA" // Magic salt for URL signing - // TrackPosition represents track position in album type TrackPosition struct { Volume int `json:"volume"` @@ -158,106 +151,8 @@ func (c *YandexClient) GetArtistInfo(ctx context.Context, artistID int) (*yandex return c.client.GetArtist(ctx, fmt.Sprintf("%d", artistID)) } -// DownloadTrackXML downloads the XML with track download URLs -func (c *YandexClient) DownloadTrackXML(ctx context.Context, downloadInfoURL string) ([]byte, error) { - req, err := http.NewRequestWithContext(ctx, "GET", downloadInfoURL, nil) - if err != nil { - return nil, fmt.Errorf("create download XML request: %w", err) - } - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("download XML request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(resp.Body) - return nil, fmt.Errorf("download XML failed: %s: %s", resp.Status, string(body)) - } - - return io.ReadAll(resp.Body) -} - -// DownloadInfoXML describes the structure of the XML file with download info -type DownloadInfoXML struct { - XMLName xml.Name `xml:"download-info"` - Host string `xml:"host"` - Path string `xml:"path"` - Ts string `xml:"ts"` - S string `xml:"s"` -} - -// GetTrackDownloadURL gets the direct MP3 download URL for a track +// GetTrackDownloadURL gets the direct MP3 download URL for a track. +// Delegates to the library which handles XML download, parsing and URL signing. func (c *YandexClient) GetTrackDownloadURL(ctx context.Context, trackID string) (string, error) { - // Get download info to find best MP3 quality - downloadInfos, err := c.GetTrackDownloadInfo(ctx, trackID) - if err != nil { - return "", fmt.Errorf("get download info: %w", err) - } - - // Find best quality MP3 - var bestInfo *yandexmusic.DownloadInfo - for _, info := range downloadInfos { - if info.Codec == "mp3" && info.DirectLink { - if bestInfo == nil || info.Bitrate > bestInfo.Bitrate { - bestInfo = &info - } - } - } - if bestInfo == nil { - for _, info := range downloadInfos { - if info.Codec == "mp3" { - if bestInfo == nil || info.Bitrate > bestInfo.Bitrate { - bestInfo = &info - } - } - } - } - if bestInfo == nil && len(downloadInfos) > 0 { - bestInfo = &downloadInfos[0] - } - if bestInfo == nil { - return "", fmt.Errorf("no suitable download info found") - } - - if c.debug { - for i, info := range downloadInfos { - c.logger.Debug("download info candidate", "index", i, "codec", info.Codec, "bitrate", info.Bitrate, "direct_link", info.DirectLink, "url", info.URL) - } - } - - // If we have a direct URL, use it - if bestInfo.URL != "" && bestInfo.DirectLink { - return bestInfo.URL, nil - } - - // If we have a download info URL, use it to get the signed URL - if bestInfo.URL != "" { - // Download and parse XML - xmlData, err := c.DownloadTrackXML(ctx, bestInfo.URL) - if err != nil { - return "", fmt.Errorf("download XML: %w", err) - } - - var infoXML DownloadInfoXML - if err := xml.Unmarshal(xmlData, &infoXML); err != nil { - return "", fmt.Errorf("parse XML: %w", err) - } - - // Generate signed URL - signData := []byte(downloadSalt + strings.TrimPrefix(infoXML.Path, "/") + infoXML.S) - sign := md5.Sum(signData) - hexSign := hex.EncodeToString(sign[:]) - - finalURL := fmt.Sprintf("https://%s/get-mp3/%s/%s%s", infoXML.Host, hexSign, infoXML.Ts, infoXML.Path) - - if c.debug { - c.logger.Debug("constructed download URL", "url", finalURL) - } - - return finalURL, nil - } - - return "", fmt.Errorf("no download URL available: bestInfo.URL is empty (codec=%s, bitrate=%d, direct_link=%v)", bestInfo.Codec, bestInfo.Bitrate, bestInfo.DirectLink) + return c.client.GetTrackDownloadURL(ctx, trackID, "mp3", 0) }