This commit is contained in:
117
internal/admin/handler.go
Normal file
117
internal/admin/handler.go
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.mrixs.me/Mrixs/yamusic-bot/internal/interfaces"
|
||||||
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler обрабатывает команды администратора.
|
||||||
|
type Handler struct {
|
||||||
|
storage interfaces.TrackStorage
|
||||||
|
telegram interfaces.TelegramClient
|
||||||
|
yandex interfaces.YandexMusicClient
|
||||||
|
startTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHandler создает новый обработчик команд администратора.
|
||||||
|
func NewHandler(storage interfaces.TrackStorage, telegram interfaces.TelegramClient, yandex interfaces.YandexMusicClient, startTime time.Time) *Handler {
|
||||||
|
return &Handler{
|
||||||
|
storage: storage,
|
||||||
|
telegram: telegram,
|
||||||
|
yandex: yandex,
|
||||||
|
startTime: startTime,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleCommand обрабатывает входящую команду.
|
||||||
|
func (h *Handler) HandleCommand(ctx context.Context, message *tgbotapi.Message) {
|
||||||
|
command := message.Command()
|
||||||
|
args := message.CommandArguments()
|
||||||
|
|
||||||
|
slog.Info("Handling admin command", "user_id", message.From.ID, "command", command, "args", args)
|
||||||
|
|
||||||
|
switch command {
|
||||||
|
case "help":
|
||||||
|
h.handleHelp(ctx, message.Chat.ID)
|
||||||
|
case "stats":
|
||||||
|
h.handleStats(ctx, message.Chat.ID)
|
||||||
|
case "find":
|
||||||
|
h.handleFind(ctx, message.Chat.ID, args)
|
||||||
|
case "warm":
|
||||||
|
h.handleWarm(ctx, message.Chat.ID, args)
|
||||||
|
default:
|
||||||
|
if err := h.telegram.SendMessage(ctx, message.Chat.ID, "Неизвестная команда. Используйте /help для списка команд."); err != nil {
|
||||||
|
slog.Error("Failed to send 'unknown command' message", "error", err, "chat_id", message.Chat.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) handleHelp(ctx context.Context, chatID int64) {
|
||||||
|
helpText := "Команды администратора:\n" +
|
||||||
|
"/help - Показать это сообщение\n" +
|
||||||
|
"/stats - Показать статистику бота\n" +
|
||||||
|
"/find <yandex_track_id> - Найти трек в кэше по ID\n" +
|
||||||
|
"/warm <URL> - \"Прогреть\" кэш для альбома или исполнителя (в разработке)"
|
||||||
|
|
||||||
|
if err := h.telegram.SendMessage(ctx, chatID, helpText); err != nil {
|
||||||
|
slog.Error("Failed to send help message", "error", err, "chat_id", chatID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) handleStats(ctx context.Context, chatID int64) {
|
||||||
|
cachedTracks, err := h.storage.Count(ctx)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to get stats from storage", "error", err)
|
||||||
|
if err := h.telegram.SendMessage(ctx, chatID, "Не удалось получить статистику из хранилища."); err != nil {
|
||||||
|
slog.Error("Failed to send stats error message", "error", err, "chat_id", chatID)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uptime := time.Since(h.startTime).Round(time.Second)
|
||||||
|
|
||||||
|
statsText := fmt.Sprintf(
|
||||||
|
"📊 Статистика бота\n\n"+
|
||||||
|
"Время работы: %s\n"+
|
||||||
|
"Треков в кэше: %d",
|
||||||
|
uptime,
|
||||||
|
cachedTracks,
|
||||||
|
)
|
||||||
|
if err := h.telegram.SendMessage(ctx, chatID, statsText); err != nil {
|
||||||
|
slog.Error("Failed to send stats message", "error", err, "chat_id", chatID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) handleFind(ctx context.Context, chatID int64, trackID string) {
|
||||||
|
if trackID == "" {
|
||||||
|
if err := h.telegram.SendMessage(ctx, chatID, "Пожалуйста, укажите Yandex Track ID. Пример: /find 123456"); err != nil {
|
||||||
|
slog.Error("Failed to send 'missing args' message for /find", "error", err, "chat_id", chatID)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileID, err := h.storage.Get(ctx, trackID)
|
||||||
|
if err != nil {
|
||||||
|
msg := fmt.Sprintf("Трек с ID `%s` не найден в кэше.", trackID)
|
||||||
|
if err := h.telegram.SendMessage(ctx, chatID, msg); err != nil {
|
||||||
|
slog.Error("Failed to send 'track not found' message for /find", "error", err, "chat_id", chatID)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := fmt.Sprintf("Трек найден! Telegram File ID: `%s`", fileID)
|
||||||
|
if err := h.telegram.SendMessage(ctx, chatID, msg); err != nil {
|
||||||
|
slog.Error("Failed to send 'track found' message for /find", "error", err, "chat_id", chatID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) handleWarm(ctx context.Context, chatID int64, url string) {
|
||||||
|
if err := h.telegram.SendMessage(ctx, chatID, "Команда /warm находится в разработке."); err != nil {
|
||||||
|
slog.Error("Failed to send 'warm in development' message", "error", err, "chat_id", chatID)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,11 +5,13 @@ type TrackInfo struct {
|
|||||||
YandexTrackID string
|
YandexTrackID string
|
||||||
YandexAlbumID string
|
YandexAlbumID string
|
||||||
Title string
|
Title string
|
||||||
Album string
|
|
||||||
Artist string
|
Artist string
|
||||||
|
Album string
|
||||||
|
AlbumArtist string // Исполнитель альбома (для сборников)
|
||||||
Year int
|
Year int
|
||||||
Genre string
|
Genre string
|
||||||
TrackPosition int
|
DiscNumber int // Номер диска
|
||||||
|
TrackPosition int // Номер трека на диске
|
||||||
CoverURL string
|
CoverURL string
|
||||||
DownloadURL string
|
DownloadURL string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTPDownloader реализует интерфейс interfaces.FileDownloader.
|
// HTTPDownloader реализует интерфейс interfaces.FileDownloader.
|
||||||
@@ -40,8 +38,8 @@ func (d *HTTPDownloader) Download(ctx context.Context, url string) (string, erro
|
|||||||
return "", fmt.Errorf("bad status code on download: %s", resp.Status)
|
return "", fmt.Errorf("bad status code on download: %s", resp.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Создаем временный файл с правильным расширением
|
// Создаем временный файл с правильным расширением .mp3
|
||||||
tmpFile, err := os.CreateTemp("", "track-*.tmp")
|
tmpFile, err := os.CreateTemp("", "track-*.mp3")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to create temp file: %w", err)
|
return "", fmt.Errorf("failed to create temp file: %w", err)
|
||||||
}
|
}
|
||||||
@@ -54,11 +52,5 @@ func (d *HTTPDownloader) Download(ctx context.Context, url string) (string, erro
|
|||||||
return "", fmt.Errorf("failed to write to temp file: %w", err)
|
return "", fmt.Errorf("failed to write to temp file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Переименовываем файл, чтобы убрать .tmp расширение (не обязательно, но красиво)
|
return tmpFile.Name(), nil
|
||||||
finalPath := strings.TrimSuffix(tmpFile.Name(), filepath.Ext(tmpFile.Name()))
|
|
||||||
if err := os.Rename(tmpFile.Name(), finalPath); err != nil {
|
|
||||||
return "", fmt.Errorf("failed to rename temp file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return finalPath, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,11 +31,21 @@ func (t *ID3Tagger) WriteTags(filePath string, coverPath string, info *model.Tra
|
|||||||
tag.SetYear(strconv.Itoa(info.Year))
|
tag.SetYear(strconv.Itoa(info.Year))
|
||||||
tag.SetGenre(info.Genre)
|
tag.SetGenre(info.Genre)
|
||||||
|
|
||||||
// Добавляем номер трека, если он есть
|
// Добавляем исполнителя альбома (TPE2)
|
||||||
|
if info.AlbumArtist != "" {
|
||||||
|
tag.AddTextFrame(tag.CommonID("Band/Orchestra/Accompaniment"), id3v2.EncodingUTF8, info.AlbumArtist)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавляем номер трека (TRCK)
|
||||||
if info.TrackPosition > 0 {
|
if info.TrackPosition > 0 {
|
||||||
tag.AddTextFrame(tag.CommonID("Track number/Position in set"), id3v2.EncodingUTF8, strconv.Itoa(info.TrackPosition))
|
tag.AddTextFrame(tag.CommonID("Track number/Position in set"), id3v2.EncodingUTF8, strconv.Itoa(info.TrackPosition))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Добавляем номер диска (TPOS)
|
||||||
|
if info.DiscNumber > 0 {
|
||||||
|
tag.AddTextFrame(tag.CommonID("Part of a set"), id3v2.EncodingUTF8, strconv.Itoa(info.DiscNumber))
|
||||||
|
}
|
||||||
|
|
||||||
// Встраиваем обложку
|
// Встраиваем обложку
|
||||||
if coverPath != "" {
|
if coverPath != "" {
|
||||||
artwork, err := os.ReadFile(coverPath)
|
artwork, err := os.ReadFile(coverPath)
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ package yamusic
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -14,8 +18,18 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
yandexMusicAPIHost = "https://api.music.yandex.net"
|
yandexMusicAPIHost = "https://api.music.yandex.net"
|
||||||
|
downloadSalt = "XGRlBW9FXlekgbPrRHuSiA" // "Магическая" соль для подписи ссылки
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DownloadInfoXML описывает структуру XML-файла с информацией для скачивания.
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
trackURLRegex = regexp.MustCompile(`/album/(\d+)/track/(\d+)`)
|
trackURLRegex = regexp.MustCompile(`/album/(\d+)/track/(\d+)`)
|
||||||
albumURLRegex = regexp.MustCompile(`/album/(\d+)`)
|
albumURLRegex = regexp.MustCompile(`/album/(\d+)`)
|
||||||
@@ -60,18 +74,32 @@ func NewApiClient(token string) (*ApiClient, error) {
|
|||||||
return &ApiClient{api: apiClient}, nil
|
return &ApiClient{api: apiClient}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ApiClient) GetTrackInfo(ctx context.Context, trackID string) (*model.TrackInfo, error) {
|
func (c *ApiClient) getTracksByIDs(ctx context.Context, trackIDs []string) ([]Track, error) {
|
||||||
body := GetTracksFormdataRequestBody{TrackIds: &[]string{trackID}}
|
formPayload := "track-ids=" + strings.Join(trackIDs, ",")
|
||||||
resp, err := c.api.GetTracksWithFormdataBodyWithResponse(ctx, body)
|
bodyReader := strings.NewReader(formPayload)
|
||||||
|
rawResp, err := c.api.ClientInterface.GetTracksWithBody(ctx, "application/x-www-form-urlencoded", bodyReader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get track info from api: %w", err)
|
return nil, fmt.Errorf("failed to execute get tracks request: %w", err)
|
||||||
}
|
}
|
||||||
if resp.StatusCode() != http.StatusOK || resp.JSON200 == nil || resp.JSON200.Result == nil || len(resp.JSON200.Result) == 0 {
|
resp, err := ParseGetTracksResponse(rawResp)
|
||||||
return nil, fmt.Errorf("failed to get track info, status: %d, body: %s", resp.StatusCode(), string(resp.Body))
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse get tracks response: %w", err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode() != http.StatusOK || resp.JSON200 == nil || resp.JSON200.Result == nil {
|
||||||
|
return nil, fmt.Errorf("failed to get tracks, status: %d, body: %s", resp.StatusCode(), string(resp.Body))
|
||||||
|
}
|
||||||
|
return resp.JSON200.Result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
track := (resp.JSON200.Result)[0]
|
func (c *ApiClient) GetTrackInfo(ctx context.Context, trackID string) (*model.TrackInfo, error) {
|
||||||
return c.convertTrackToTrackInfo(&track)
|
tracks, err := c.getTracksByIDs(ctx, []string{trackID})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(tracks) == 0 {
|
||||||
|
return nil, fmt.Errorf("no track info returned for id %s", trackID)
|
||||||
|
}
|
||||||
|
return c.convertTrackToTrackInfo(&tracks[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ApiClient) GetAlbumTrackInfos(ctx context.Context, albumID string) ([]*model.TrackInfo, error) {
|
func (c *ApiClient) GetAlbumTrackInfos(ctx context.Context, albumID string) ([]*model.TrackInfo, error) {
|
||||||
@@ -86,17 +114,27 @@ func (c *ApiClient) GetAlbumTrackInfos(ctx context.Context, albumID string) ([]*
|
|||||||
|
|
||||||
album := resp.JSON200.Result
|
album := resp.JSON200.Result
|
||||||
var trackInfos []*model.TrackInfo
|
var trackInfos []*model.TrackInfo
|
||||||
|
|
||||||
|
// Определяем исполнителя альбома
|
||||||
|
var albumArtist string
|
||||||
|
if len(album.Artists) > 0 {
|
||||||
|
albumArtist = album.Artists[0].Name
|
||||||
|
}
|
||||||
|
|
||||||
if album.Volumes != nil {
|
if album.Volumes != nil {
|
||||||
trackNum := 1
|
for i, volume := range *album.Volumes {
|
||||||
for _, volume := range *album.Volumes {
|
discNumber := i + 1
|
||||||
for _, track := range volume {
|
for j, track := range volume {
|
||||||
|
trackPosition := j + 1
|
||||||
info, err := c.convertTrackToTrackInfo(&track)
|
info, err := c.convertTrackToTrackInfo(&track)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Warn("Failed to convert track, skipping", "trackID", track.Id, "error", err)
|
slog.Warn("Failed to convert track, skipping", "trackID", track.Id, "error", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
info.TrackPosition = trackNum
|
// Заполняем дополнительные поля
|
||||||
trackNum++
|
info.AlbumArtist = albumArtist
|
||||||
|
info.DiscNumber = discNumber
|
||||||
|
info.TrackPosition = trackPosition
|
||||||
trackInfos = append(trackInfos, info)
|
trackInfos = append(trackInfos, info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,26 +150,19 @@ func (c *ApiClient) GetArtistTrackInfos(ctx context.Context, artistID string) ([
|
|||||||
if resp.StatusCode() != http.StatusOK || resp.JSON200 == nil {
|
if resp.StatusCode() != http.StatusOK || resp.JSON200 == nil {
|
||||||
return nil, fmt.Errorf("failed to get artist popular tracks, status: %d, body: %s", resp.StatusCode(), string(resp.Body))
|
return nil, fmt.Errorf("failed to get artist popular tracks, status: %d, body: %s", resp.StatusCode(), string(resp.Body))
|
||||||
}
|
}
|
||||||
|
|
||||||
trackIDs := resp.JSON200.Result.Tracks
|
trackIDs := resp.JSON200.Result.Tracks
|
||||||
if len(trackIDs) == 0 {
|
if len(trackIDs) == 0 {
|
||||||
return []*model.TrackInfo{}, nil
|
return []*model.TrackInfo{}, nil
|
||||||
}
|
}
|
||||||
|
tracks, err := c.getTracksByIDs(ctx, trackIDs)
|
||||||
body := GetTracksFormdataRequestBody{TrackIds: &trackIDs}
|
|
||||||
tracksResp, err := c.api.GetTracksWithFormdataBodyWithResponse(ctx, body)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get full info for popular tracks: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
if tracksResp.StatusCode() != http.StatusOK || tracksResp.JSON200 == nil || tracksResp.JSON200.Result == nil {
|
|
||||||
return nil, fmt.Errorf("failed to get full info for popular tracks, status: %d, body: %s", tracksResp.StatusCode(), string(tracksResp.Body))
|
|
||||||
}
|
|
||||||
|
|
||||||
var trackInfos []*model.TrackInfo
|
var trackInfos []*model.TrackInfo
|
||||||
for _, track := range tracksResp.JSON200.Result {
|
for i := range tracks {
|
||||||
info, err := c.convertTrackToTrackInfo(&track)
|
info, err := c.convertTrackToTrackInfo(&tracks[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Warn("Failed to convert track, skipping", "trackID", track.Id, "error", err)
|
slog.Warn("Failed to convert track, skipping", "trackID", tracks[i].Id, "error", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
trackInfos = append(trackInfos, info)
|
trackInfos = append(trackInfos, info)
|
||||||
@@ -156,13 +187,44 @@ func (c *ApiClient) GetDownloadURL(ctx context.Context, trackID string) (string,
|
|||||||
bestURL = info.DownloadInfoUrl
|
bestURL = info.DownloadInfoUrl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if bestURL == "" {
|
if bestURL == "" {
|
||||||
return "", fmt.Errorf("no suitable mp3 download link found for track %s", trackID)
|
return "", fmt.Errorf("no suitable mp3 download link found for track %s", trackID)
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Warn("Returning XML info URL instead of direct download link. Real implementation needed.", "url", bestURL)
|
// Получили ссылку на XML, теперь скачиваем и парсим его
|
||||||
return "https://example.com/download/track.mp3", nil
|
xmlReq, err := http.NewRequestWithContext(ctx, "GET", bestURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to create request for download info xml: %w", err)
|
||||||
|
}
|
||||||
|
xmlResp, err := http.DefaultClient.Do(xmlReq)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get download info xml: %w", err)
|
||||||
|
}
|
||||||
|
defer xmlResp.Body.Close()
|
||||||
|
|
||||||
|
if xmlResp.StatusCode != http.StatusOK {
|
||||||
|
return "", fmt.Errorf("bad status on getting download info xml: %s", xmlResp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
xmlBody, err := io.ReadAll(xmlResp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to read download info xml body: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var infoXML DownloadInfoXML
|
||||||
|
if err := xml.Unmarshal(xmlBody, &infoXML); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to unmarshal download info xml: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Генерируем финальную ссылку
|
||||||
|
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)
|
||||||
|
slog.Debug("Constructed final download URL", "url", finalURL)
|
||||||
|
|
||||||
|
return finalURL, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ApiClient) convertTrackToTrackInfo(track *Track) (*model.TrackInfo, error) {
|
func (c *ApiClient) convertTrackToTrackInfo(track *Track) (*model.TrackInfo, error) {
|
||||||
@@ -189,9 +251,12 @@ func (c *ApiClient) convertTrackToTrackInfo(track *Track) (*model.TrackInfo, err
|
|||||||
info.Album = album.Title
|
info.Album = album.Title
|
||||||
info.Year = int(album.Year)
|
info.Year = int(album.Year)
|
||||||
info.Genre = album.Genre
|
info.Genre = album.Genre
|
||||||
|
// По умолчанию исполнитель альбома - это исполнитель трека, если не переопределено
|
||||||
|
info.AlbumArtist = info.Artist
|
||||||
}
|
}
|
||||||
|
|
||||||
info.CoverURL = "https://" + strings.Replace(track.CoverUri, "%%", "400x400", 1)
|
// Запрашиваем обложку максимального качества
|
||||||
|
info.CoverURL = "https://" + strings.Replace(track.CoverUri, "%%", "1000x1000", 1)
|
||||||
info.DownloadURL = ""
|
info.DownloadURL = ""
|
||||||
|
|
||||||
return info, nil
|
return info, nil
|
||||||
|
|||||||
@@ -1366,7 +1366,7 @@ type TrackDownloadInfo struct {
|
|||||||
Gain bool `json:"gain"`
|
Gain bool `json:"gain"`
|
||||||
|
|
||||||
// Preview Предварительный просмотр
|
// Preview Предварительный просмотр
|
||||||
Preview string `json:"preview"`
|
Preview bool `json:"preview"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrackDownloadInfoCodec Кодек аудиофайла
|
// TrackDownloadInfoCodec Кодек аудиофайла
|
||||||
@@ -1667,7 +1667,7 @@ type GetTokenFormdataBodyGrantType string
|
|||||||
// GetTracksFormdataBody defines parameters for GetTracks.
|
// GetTracksFormdataBody defines parameters for GetTracks.
|
||||||
type GetTracksFormdataBody struct {
|
type GetTracksFormdataBody struct {
|
||||||
// TrackIds Уникальные идентификаторы треков
|
// TrackIds Уникальные идентификаторы треков
|
||||||
TrackIds *[]string `form:"track-ids,omitempty" json:"track-ids,omitempty"`
|
TrackIds *[]string `form:"trackIds,omitempty" json:"trackIds,omitempty"`
|
||||||
|
|
||||||
// WithPositions С позициями
|
// WithPositions С позициями
|
||||||
WithPositions *bool `form:"with-positions,omitempty" json:"with-positions,omitempty"`
|
WithPositions *bool `form:"with-positions,omitempty" json:"with-positions,omitempty"`
|
||||||
|
|||||||
Reference in New Issue
Block a user