Migrate to yandex-music-api-go v0.0.0-20261018120746-dbf9a46bb2e7
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.
This commit is contained in:
1 parent
d7692875db
commit
923705c7fc
4 files changed
+11
-111
No files matched your search
@@ -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{
|
||||
|
||||
+3
-108
@@ -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)
|
||||
}
|
||||
Reference in new issue
Block a user