Compare commits

..
14 Commits
Author SHA1 Message Date
Mrixs dbf9a46bb2 Fix TrackShort.Lyrics type mismatch
TrackShort.Lyrics was SupplementLyrics but Python reference shows it should be TrackLyricsInfo
(has_available_sync_lyrics, has_available_text_lyrics fields)
2026-10-18 15:07:46 +03:00
Mrixs 73775a8526 Add remaining model types and client methods
Models added:
- Disclaimer, ForeignAgent (credits.go)
- ArtistConcerts (concert.go)
- Dashboard, StationResult, Station, AdParams, GenerativeStream,
  GenerativeStreamData, GenerativeStreamInfo (radio.go)
- FileInfo, FileDownloadInfo, TrackFullInfo, SimilarTracks, TrackTrailer (track.go)
- Removed duplicate AdParams from feed.go

Client methods added:
- artists.go: GetArtistBriefInfo, GetArtistSimilar, GetArtistLinks,
  GetArtistAlsoAlbums, GetArtistDiscographyAlbums, GetArtistSafeDirectAlbums,
  GetArtistTrackIds, GetArtistAbout, GetArtistDonation, GetArtistInfo,
  GetArtistSkeleton, GetArtistTrailer
- albums.go: GetAlbumSimilarEntities, GetAlbumTrailer
- clips.go: GetClipsDisclaimer
- concerts.go: GetArtistConcerts, GetConcertSkeleton
- radio.go: GetRotorStationsDashboard, GetRotorStationsList,
  GetRotorStationInfo, GetRotorStationStream, GetRotorStationSettings
- supplements.go: GetTracksCredits, GetClipsCredits
- queue.go: GetQueuesList
- tracks.go: GetTracksFileInfo, GetTracksSimilar, GetTrackTrailer, GetTrackFullInfo
2026-10-18 13:32:45 +03:00
Mrixs 49920d8908 Add missing Track, Album, Artist model types and client methods
Track: Added FileInfo, FileDownloadInfo, TrackFullInfo, SimilarTracks types
Album: Added AlbumSimilarEntities, AlbumTrailer, SimilarEntityItem types
Artist: Added ArtistSimilar, ArtistLinks, ArtistAbout, ArtistInfo, ArtistSkeleton,
       ArtistTrailerStatus, ArtistLink, Stats, SkeletonBlock types

Client methods added:
- artists.go: GetArtistBriefInfo, GetArtistSimilar, GetArtistLinks, GetArtistAlsoAlbums,
  GetArtistDiscographyAlbums, GetArtistSafeDirectAlbums, GetArtistTrackIds, GetArtistAbout,
  GetArtistDonation, GetArtistInfo, GetArtistSkeleton, GetArtistTrailer
- albums.go: GetAlbumSimilarEntities, GetAlbumTrailer
- tracks.go: GetTracksFileInfo, GetTracksSimilar, GetTrackTrailer, GetTrackFullInfo
2026-10-18 13:24:41 +03:00
Mrixs 9d3bcc4a9b Add missing model fields from Python audit
LandingList: Added type, type_for_from, title, id, new_releases, new_playlists, podcasts
New type: PlaylistID
Presaves: Added upcoming_albums, released_albums
Presaves: Added DeJSON method
QueueItem: Added id, context, modified fields (fixed duplicate context)
Clip: Fixed PreviewURI → PreviewURL field name
2026-10-18 13:18:24 +03:00
Mrixs 463e0bcd1a Add missing model fields from Python reference
Feed: Added can_get_more_events, pumpkin, is_wizard_passed, headlines, today, next_revision
Label: Added description_formatted, image, links ([]Link), type
Landing: Added pumpkin, content_id
Pin: Added content_restrictions
Search: Added search_request_id, text, best, podcasts, podcast_episodes, clips, banner, page, per_page, misspell fields

Clip: Rewrote with all missing fields (version, player_id, uuid, thumbnail, track_ids, disclaimers, explicit, cover, content_restrictions)
Concert: Added datetime, afisha_url, city, place, address, content_rating, data_session_id, event_info
2026-10-18 13:14:15 +03:00
Mrixs bc026d82b4 Add missing Track and Album model fields from Python reference
Track: Added 50+ missing fields (storage_dir, file_size, real_id, og_image, type,
normalization, error, can_publish, state, filename, user_info, meta_data, regions,
content_warning, preview_duration_ms, remember_position, background_video_uri,
short_description, is_suitable_for_children, track_source, available_for_options,
derived_colors, fade, smart_preview_params, special_audio_resources, disclaimers,
background_video_id, player_id, mix_fade, chart, clip_ids, isrc, ugc_artist_name,
podcast_episode_type, pub_date, and more)

New types: Normalization, MetaData, Fade, SmartPreviewParams, CoverDerivedColors,
CustomWave, TrackId, TrackFullInfo, SimilarTracks, TrackTrailer

Album: Added 40+ missing fields (error, version, original_release_year, text_color,
short_description, is_premiere, is_banner, meta_type, storage_dir, og_image, buy,
recent, very_important, bests, duplicates, prerolls, regions, available_as_rbt,
lyrics_available, remember_position, duration_ms, explicit, start_date, likes_count,
deprecation, available_regions, available_for_options, disclaimers, action_button,
cover, derived_colors, meta_tag_id, child_content, background_image_url, duration_sec,
has_trailer, pager, sort_order)

New types: Deprecation, AlbumActionButton
2026-10-18 13:12:13 +03:00
Mrixs a9be10b5e5 Fix remaining type mismatches vs Python reference
- RotorSettings: Diversity float64→string, Energy float64→int, add Mood int and MoodEnergy string
- Ratings: Rating float64→int, add Week int and Day int
- MetatagArtistEntry: Remove Score float64 (not in Python), add PopularTracks []Track
- MusicHistoryItemData: Remove Progress float64 (not in Python)
2026-10-18 12:57:21 +03:00
Mrixs 577c4a418c Fix Artist model to match Python reference
- Link field removed (not in Python Artist model)
- Description: string → *Description object (text, uri fields)
- SocialLinks []SocialLink → Links []Link (title, href, type, social_network)
- Donations *ArtistDonations → DonationInfo *ArtistDonationInfo (title, url)
- Decomposed []Artist → []Decomposed (accepts string or Artist object)
- Added Decomposed, Link, Description, ArtistDonationInfo types
2026-10-18 12:44:17 +03:00
Mrixs 277a3e6924 Fix Album model to match Python reference
- Labels: changed from []Label to []LabelOrString (API returns both objects and strings)
- Trailer: changed from *TrailerInfo to *AlbumTrailerStatus (Python has AlbumTrailerStatus with only 'available' field)
- Added LabelOrString type with custom UnmarshalJSON
- Added AlbumTrailerStatus type with Available field
2026-10-18 12:39:45 +03:00
Mrixs 6312c24b0f Fix Track model to match Python reference
- R128: changed from JSONR128 float64 to R128 struct with i, tp, important_secs fields
  (Python R128 is an object, not a number)
- StorageID -> StorageDir with json tag storage_dir (Python API uses storage_dir)
- Lyrics: replaced TrackLyricsInfo with Lyrics (from supplement/lyrics.py) and
  TrackLyricsInfo (from track/lyrics_info.py with has_available_sync_lyrics,
  has_available_text_lyrics fields)
- Track.Lyrics field now maps to lyrics_info JSON key with TrackLyricsInfo type
2026-10-18 12:34:58 +03:00
Mrixs ad83d84531 Fix GetTrackDownloadInfo to return signed download URLs
- Pass quality parameter to /tracks/{track_id}/download-info API request
- Add GetTrackDownloadURL method that resolves signed download URLs by
  downloading XML from download_info_url, parsing host/path/ts/s,
  computing MD5 signature (salt: XGRlBW9FXlekgbPrRHuSiA), and
  returning https://{host}/get-mp3/{sign}/{ts}{path}
- Add DownloadInfoURL field to DownloadInfo model for the XML URL
- Update DownloadTrack to use GetDirectLink resolution when URL is empty
- Port signing logic from yandex-music-api-python download_info.py
2026-10-18 12:13:54 +03:00
Mrixs dbb823ade3 Fix R128 and remaining ID fields to accept both JSON types
- Track.R128 and TrackShort.R128: float64 → JSONR128 (accepts both JSON number and object)
- All remaining primary ID fields: string → JSONID (accepts both JSON string and number)
  Fixed in: account, artist, clip, concert, feed, label, landing, metatag,
  music_history, pin, queue, radio, search, settings, supplement
2026-10-18 11:01:26 +03:00
Mrixs d0173f795d Remove .serena from git tracking
- Remove .serena/.gitignore and .serena/project.yml from version control
- Add .serena/ to .gitignore to prevent future tracking
- Files remain on disk, only removed from git tracking
2026-10-18 10:25:17 +03:00
MrixsandClaude Code 5535d2a941 Fix ID fields to accept both JSON strings and numbers
The Yandex Music API returns IDs as both JSON strings and numbers depending
on the endpoint. Added JSONID custom type in internal/models/base.go that
implements json.Unmarshaler to accept both formats and store as string.

Changed ID fields from string to JSONID:
- Track.ID, Major.ID, TrackLyricsInfo.ID (track.go)
- Album.ID, TrailerInfo.TrackID, TrailerInfo.AlbumID (album.go)

Updated example to convert JSONID to string when passing to client methods.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-18 10:21:58 +03:00
35 changed files with 2684 additions and 398 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
.serena/
-2
View File
@@ -1,2 +0,0 @@
/cache
/project.local.yml
-163
View File
@@ -1,163 +0,0 @@
# the name by which the project can be referenced within Serena/when chatting with the LLM.
project_name: "yandex-music-api-go"
# list of languages for which language servers are started (LSP backend only); choose from:
# ada al angular ansible bash
# bsl clojure cpp cpp_ccls crystal
# csharp csharp_omnisharp cue dart elixir
# elm erlang fortran fsharp gdscript
# go groovy haskell haxe hlsl
# html java json julia kotlin
# latex lean4 lua luau markdown
# matlab msl nix ocaml pascal
# perl php php_phpactor php_phpantom powershell
# python python_jedi python_pyrefly python_ty r
# rego ruby ruby_solargraph rust scala
# scss solidity svelte swift systemverilog
# terraform toml typescript typescript_vts vue
# yaml zig
# (This list may be outdated; generated with scripts/print_language_list.py;
# For the current list, see values of Language enum here:
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py)
# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
# Note:
# - For C, use cpp
# - For JavaScript, use typescript
# - For Angular projects, use angular (subsumes typescript+html; requires `npm install` in the project root)
# - For Svelte projects, use svelte (subsumes typescript/javascript for .svelte projects; requires npm)
# - For SCSS / Sass / plain CSS, use scss (some-sass-language-server handles all three)
# - For Free Pascal/Lazarus, use pascal
# Special requirements:
# Some languages require additional setup/installations.
# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
# When using multiple languages, the first language server that supports a given file will be used for that file.
# The first language is the default language and the respective language server will be used as a fallback.
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
languages:
- python
- go
- markdown
- json
# the encoding used by text files in the project
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
encoding: "utf-8"
# optional shell command to run before the language backend (LSP or JetBrains) is initialised.
# the command runs in the project root directory and is only executed if the project is trusted
# (see trusted_project_path_patterns in the global configuration).
# serena waits for the command to exit: a non-zero exit code is logged as an error but does not
# abort activation. a per-project timeout (activation_command_timeout, default 180s) is the safety
# backstop for non-terminating commands; on expiry the process is killed and activation continues.
# example: activation_command: "npx nx run-many -t build"
activation_command:
# maximum time in seconds to wait for activation_command to complete before killing it (default 180s).
# must be a positive number.
activation_command_timeout: 180.0
# line ending convention to use when writing source files.
# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default)
# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings.
line_ending:
# The language backend to use for this project.
# If not set, the global setting from serena_config.yml is used.
# Valid values: LSP, JetBrains
# Note: the backend is fixed at startup. If a project with a different backend
# is activated post-init, an error will be returned.
language_backend:
# whether to use project's .gitignore files to ignore files
ignore_all_files_in_gitignore: true
# advanced configuration option allowing to configure language server-specific options.
# Maps the language key to the options.
# The settings are considered only if the project is trusted (see global configuration to define trusted projects).
# See https://oraios.github.io/serena/02-usage/050_configuration.html#language-server-specific-settings
ls_specific_settings: {}
# list of workspace folder paths (LSP backend only).
# These folders will be used to build up Serena's symbol index.
# Paths must be within the project root and should thus be relative to the project root.
# Furthermore, the paths should not be filtered by ignore settings.
# Default setting: The entire project root folder (".") is considered.
# In (large) monorepos, this can be used to index only subfolders of the project root, e.g.
# ls_workspace_folders:
# - "./subproject1"
# - "./subproject2"
ls_workspace_folders:
- "."
# list of additional workspace folder paths for cross-package reference support.
# Paths can be absolute or relative to the project root.
# Each folder is registered as an LSP workspace folder, enabling language servers to discover
# symbols and references across package boundaries, but these folders are not indexed by Serena,
# i.e. the respective symbols will not be found using Serena's symbol search tools.
# Example:
# additional_workspace_folders:
# - ../sibling-package
# - ../shared-lib
ls_additional_workspace_folders: []
# list of additional paths to ignore in this project.
# Same syntax as gitignore, so you can use * and **.
# Note: global ignored_paths from serena_config.yml are also applied additively.
ignored_paths: []
# whether the project is in read-only mode
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
# Added on 2025-04-18
read_only: false
# list of tool names to exclude.
# This extends the existing exclusions (e.g. from the global configuration)
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
excluded_tools: []
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default).
# This extends the existing inclusions (e.g. from the global configuration).
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
included_optional_tools: []
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
fixed_tools: []
# list of mode names that are to be activated by default, overriding the setting in the global configuration.
# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
# If the setting is undefined/empty, the default_modes from the global configuration (serena_config.yml) apply.
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
# Therefore, you can set this to [] if you do not want the default modes defined in the global config to apply
# for this project.
# This setting can, in turn, be overridden by CLI parameters (--mode).
# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
default_modes:
# list of mode names to be activated additionally for this project, e.g. ["query-projects"]
# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
added_modes:
# initial prompt for the project. It will always be given to the LLM upon activating the project
# (contrary to the memories, which are loaded on demand).
initial_prompt: ""
# time budget (seconds) per tool call for the retrieval of additional symbol information
# such as docstrings or parameter information.
# This overrides the corresponding setting in the global configuration; see the documentation there.
# If null or missing, use the setting from the global configuration.
symbol_info_budget:
# list of regex patterns which, when matched, mark a memory entry as read‑only.
# Extends the list from the global configuration, merging the two lists.
read_only_memory_patterns: []
# list of regex patterns for memories to completely ignore.
# Matching memories will not appear in list_memories or activate_project output
# and cannot be accessed via read_memory or write_memory.
# To access ignored memory files, use the read_file tool on the raw file path.
# Extends the list from the global configuration, merging the two lists.
# Example: ["_archive/.*", "_episodes/.*"]
ignored_memory_patterns: []
+1 -1
View File
@@ -57,7 +57,7 @@ func main() {
// Example: Get track by ID (if we have results)
if len(tracks) > 0 {
fmt.Println("\n--- Getting track details ---")
track, err := client.GetTrack(ctx, tracks[0].ID)
track, err := client.GetTrack(ctx, string(tracks[0].ID))
if err != nil {
log.Printf("Get track failed: %v", err)
} else {
+21
View File
@@ -3,6 +3,7 @@ package client
import (
"context"
"fmt"
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/models"
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/utils"
@@ -69,3 +70,23 @@ func (c *Client) GetAlbumsWithTracks(ctx context.Context, albumIDs []string) ([]
}
return albums, nil
}
// GetAlbumSimilarEntities gets similar entities for an album (GET /albums/{id}/similar-entities).
func (c *Client) GetAlbumSimilarEntities(ctx context.Context, albumID string) (*models.AlbumSimilarEntities, error) {
var result models.AlbumSimilarEntities
if err := c.request.Get(ctx, fmt.Sprintf("/albums/%s/similar-entities", albumID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetAlbumTrailer gets album trailer (GET /albums/{id}/trailer).
func (c *Client) GetAlbumTrailer(ctx context.Context, albumID string) (*models.AlbumTrailer, error) {
var result models.AlbumTrailer
if err := c.request.Get(ctx, fmt.Sprintf("/albums/%s/trailer", albumID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
+138
View File
@@ -77,3 +77,141 @@ func (c *Client) GetArtistTracks(ctx context.Context, artistID string, page, pag
result.SetClient(c)
return &result, nil
}
// GetArtistBriefInfo gets brief info about an artist (GET /artists/{artist_id}/brief-info).
func (c *Client) GetArtistBriefInfo(ctx context.Context, artistID string) (*models.ArtistBriefInfo, error) {
var result models.ArtistBriefInfo
if err := c.request.Get(ctx, fmt.Sprintf("/artists/%s/brief-info", artistID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetArtistSimilar gets similar artists (GET /artists/{artist_id}/similar).
func (c *Client) GetArtistSimilar(ctx context.Context, artistID string) (*models.ArtistSimilar, error) {
var result models.ArtistSimilar
if err := c.request.Get(ctx, fmt.Sprintf("/artists/%s/similar", artistID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetArtistLinks gets links to artist's pages (GET /artists/{artist_id}/artist-links).
func (c *Client) GetArtistLinks(ctx context.Context, artistID string) (*models.ArtistLinks, error) {
var result models.ArtistLinks
if err := c.request.Get(ctx, fmt.Sprintf("/artists/%s/artist-links", artistID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetArtistAlsoAlbums gets albums where artist participated (GET /artists/{artist_id}/also-albums).
func (c *Client) GetArtistAlsoAlbums(ctx context.Context, artistID string, page, pageSize int, sortBy string) (*models.ArtistAlbums, error) {
params := map[string]string{
"page": fmt.Sprintf("%d", page),
"page-size": fmt.Sprintf("%d", pageSize),
"sort-by": sortBy,
}
var result models.ArtistAlbums
if err := c.request.Get(ctx, fmt.Sprintf("/artists/%s/also-albums", artistID), params, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetArtistDiscographyAlbums gets artist discography (GET /artists/{artist_id}/discography-albums).
func (c *Client) GetArtistDiscographyAlbums(ctx context.Context, artistID string, page, pageSize int, sortBy string) (*models.ArtistAlbums, error) {
params := map[string]string{
"page": fmt.Sprintf("%d", page),
"page-size": fmt.Sprintf("%d", pageSize),
"sort-by": sortBy,
}
var result models.ArtistAlbums
if err := c.request.Get(ctx, fmt.Sprintf("/artists/%s/discography-albums", artistID), params, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetArtistSafeDirectAlbums gets safe direct albums (GET /artists/{artist_id}/safe-direct-albums).
func (c *Client) GetArtistSafeDirectAlbums(ctx context.Context, artistID string, sortBy, sortOrder string, limit int) (*models.ArtistAlbums, error) {
params := map[string]string{
"sort-by": sortBy,
"sort-order": sortOrder,
"limit": fmt.Sprintf("%d", limit),
}
var result models.ArtistAlbums
if err := c.request.Get(ctx, fmt.Sprintf("/artists/%s/safe-direct-albums", artistID), params, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetArtistTrackIds gets track IDs by artist (GET /artists/{artist_id}/track-ids).
func (c *Client) GetArtistTrackIds(ctx context.Context, artistID string, page, pageSize int) ([]string, error) {
params := map[string]string{
"page": fmt.Sprintf("%d", page),
"page-size": fmt.Sprintf("%d", pageSize),
}
var result []string
if err := c.request.Get(ctx, fmt.Sprintf("/artists/%s/track-ids", artistID), params, &result); err != nil {
return nil, err
}
return result, nil
}
// GetArtistAbout gets "About artist" info (GET /artists/{artist_id}/about-artist).
func (c *Client) GetArtistAbout(ctx context.Context, artistID string) (*models.ArtistAbout, error) {
var result models.ArtistAbout
if err := c.request.Get(ctx, fmt.Sprintf("/artists/%s/about-artist", artistID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetArtistDonation gets artist donation info (GET /artists/{artist_id}/blocks/artist-donation).
func (c *Client) GetArtistDonation(ctx context.Context, artistID string) (*models.ArtistDonations, error) {
var result models.ArtistDonations
if err := c.request.Get(ctx, fmt.Sprintf("/artists/%s/blocks/artist-donation", artistID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetArtistInfo gets detailed artist info (GET /artists/{artist_id}/info).
func (c *Client) GetArtistInfo(ctx context.Context, artistID string) (*models.ArtistInfo, error) {
var result models.ArtistInfo
if err := c.request.Get(ctx, fmt.Sprintf("/artists/%s/info", artistID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetArtistSkeleton gets artist page skeleton (GET /artists/{artist_id}/skeletons/{skeleton_id}).
func (c *Client) GetArtistSkeleton(ctx context.Context, artistID, skeletonID string) (*models.ArtistSkeleton, error) {
var result models.ArtistSkeleton
if err := c.request.Get(ctx, fmt.Sprintf("/artists/%s/skeletons/%s", artistID, skeletonID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetArtistTrailer gets artist trailer (GET /artists/{artist_id}/trailer).
func (c *Client) GetArtistTrailer(ctx context.Context, artistID string) (*models.ArtistTrailerStatus, error) {
var result models.ArtistTrailerStatus
if err := c.request.Get(ctx, fmt.Sprintf("/artists/%s/trailer", artistID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
+10
View File
@@ -52,6 +52,16 @@ func (c *Client) GetArtistClips(ctx context.Context, artistID string) (*models.A
return &result, nil
}
// GetClipsDisclaimer gets clips disclaimer (GET /clips/{id}/disclaimer).
func (c *Client) GetClipsDisclaimer(ctx context.Context, clipID string) (*models.Disclaimer, error) {
var result models.Disclaimer
if err := c.request.Get(ctx, fmt.Sprintf("/clips/%s/disclaimer", clipID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetShots gets shots with pagination (GET /shots).
func (c *Client) GetShots(ctx context.Context, page, pageSize int) ([]*models.Shot, error) {
params := map[string]string{
+20
View File
@@ -63,3 +63,23 @@ func (c *Client) GetConcertTabs(ctx context.Context) (*models.ConcertTabConfig,
}
return &result, nil
}
// GetArtistConcerts gets concerts by artist (GET /artists/{id}/concerts).
func (c *Client) GetArtistConcerts(ctx context.Context, artistID string) (*models.ArtistConcerts, error) {
var result models.ArtistConcerts
if err := c.request.Get(ctx, fmt.Sprintf("/artists/%s/concerts", artistID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetConcertSkeleton gets concert skeleton (GET /concerts/{id}/skeletons/{skeleton_id}).
func (c *Client) GetConcertSkeleton(ctx context.Context, concertID, skeletonID string) (*models.ConcertSkeleton, error) {
var result models.ConcertSkeleton
if err := c.request.Get(ctx, fmt.Sprintf("/concerts/%s/skeletons/%s", concertID, skeletonID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
+15
View File
@@ -7,6 +7,21 @@ import (
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/models"
)
// GetQueuesList gets the queues list (GET /queues).
func (c *Client) GetQueuesList(ctx context.Context) ([]*models.QueueItem, error) {
var result []models.QueueItem
if err := c.request.Get(ctx, "/queues", nil, &result); err != nil {
return nil, err
}
items := make([]*models.QueueItem, len(result))
for i := range result {
result[i].SetClient(c)
items[i] = &result[i]
}
return items, nil
}
// GetQueue gets the current queue
func (c *Client) GetQueue(ctx context.Context) (*models.Queue, error) {
var result models.Queue
+58
View File
@@ -121,3 +121,61 @@ func (c *Client) RotorSessionFeedback(ctx context.Context, sessionID, trackID, f
var result map[string]any
return c.request.Post(ctx, fmt.Sprintf("/rotor/station/%s/feedback", sessionID), params, &result)
}
// GetRotorStationsDashboard gets rotor stations dashboard (GET /rotor/stations/dashboard).
func (c *Client) GetRotorStationsDashboard(ctx context.Context) (*models.Dashboard, error) {
var result models.Dashboard
if err := c.request.Get(ctx, "/rotor/stations/dashboard", nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetRotorStationsList gets rotor stations list (GET /rotor/stations/list).
func (c *Client) GetRotorStationsList(ctx context.Context) ([]*models.StationResult, error) {
var result []models.StationResult
if err := c.request.Get(ctx, "/rotor/stations/list", nil, &result); err != nil {
return nil, err
}
stations := make([]*models.StationResult, len(result))
for i := range result {
result[i].SetClient(c)
stations[i] = &result[i]
}
return stations, nil
}
// GetRotorStationInfo gets rotor station info (GET /rotor/station/{id}/info).
func (c *Client) GetRotorStationInfo(ctx context.Context, stationID string) (*models.RotorStation, error) {
var result models.RotorStation
if err := c.request.Get(ctx, fmt.Sprintf("/rotor/station/%s/info", stationID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetRotorStationStream gets rotor station stream (GET /rotor/station/{id}/stream).
func (c *Client) GetRotorStationStream(ctx context.Context, stationID string) (*models.GenerativeStream, error) {
var result models.GenerativeStream
if err := c.request.Get(ctx, fmt.Sprintf("/rotor/station/%s/stream", stationID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetRotorStationSettings gets rotor station settings (POST /rotor/station/{id}/settings3).
func (c *Client) GetRotorStationSettings(ctx context.Context, stationID string, moodEnergy, diversity, language string) (bool, error) {
params := map[string]string{
"mood-energy": moodEnergy,
"diversity": diversity,
"language": language,
}
var result bool
if err := c.request.Post(ctx, fmt.Sprintf("/rotor/station/%s/settings3", stationID), params, &result); err != nil {
return false, err
}
return result, nil
}
+20
View File
@@ -129,6 +129,26 @@ type Sign struct {
Value string
}
// GetTracksCredits gets track credits (GET /tracks/{id}/credits).
func (c *Client) GetTracksCredits(ctx context.Context, trackID string) (*models.Credits, error) {
var result models.Credits
if err := c.request.Get(ctx, fmt.Sprintf("/tracks/%s/credits", trackID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetClipsCredits gets clip credits (GET /clips/{id}/credits).
func (c *Client) GetClipsCredits(ctx context.Context, clipID string) (*models.Credits, error) {
var result models.Credits
if err := c.request.Get(ctx, fmt.Sprintf("/clips/%s/credits", clipID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetSignRequest creates a signature for a track request (exported for external use).
// It delegates to utils.GetSignRequest with the default signing key.
func GetSignRequest(trackID string) Sign {
+144 -10
View File
@@ -3,12 +3,48 @@ package client
import (
"context"
"crypto/md5"
"encoding/xml"
"fmt"
"strings"
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/models"
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/utils"
)
const downloadSignSalt = "XGRlBW9FXlekgbPrRHuSiA"
// downloadInfoXML represents the XML response from download_info_url.
type downloadInfoXML struct {
Host string `xml:"host"`
Path string `xml:"path"`
Ts string `xml:"ts"`
S string `xml:"s"`
}
// resolveDownloadURL downloads the XML from downloadInfoURL, parses host/path/ts/s,
// computes the MD5 signature, and returns the signed download URL:
// https://{host}/get-mp3/{sign}/{ts}{path}
func resolveDownloadURL(ctx context.Context, downloadInfoURL string, ri models.RequestInterface) (string, error) {
data, err := ri.Download(ctx, downloadInfoURL)
if err != nil {
return "", err
}
var xmlData downloadInfoXML
if err := xml.Unmarshal(data, &xmlData); err != nil {
return "", fmt.Errorf("failed to parse download info XML: %w", err)
}
if xmlData.Path == "" || xmlData.S == "" {
return "", utils.NewYandexMusicError("invalid download info XML: missing path or s", utils.ErrBadRequest)
}
sign := fmt.Sprintf("%x", md5.Sum([]byte(downloadSignSalt+xmlData.Path[1:]+xmlData.S)))
return fmt.Sprintf("https://%s/get-mp3/%s/%s%s", xmlData.Host, sign, xmlData.Ts, xmlData.Path), nil
}
// GetTracks gets tracks by their IDs (GET /tracks).
// Returns a slice of Track pointers, or nil if no IDs are provided.
func (c *Client) GetTracks(ctx context.Context, trackIDs []string) ([]*models.Track, error) {
@@ -49,11 +85,12 @@ func (c *Client) GetTrack(ctx context.Context, trackID string) (*models.Track, e
// GetTrackDownloadInfo gets download info for a track (GET /tracks/{track_id}/download-info).
// The quality parameter specifies the desired audio quality (e.g., "mp3").
func (c *Client) GetTrackDownloadInfo(ctx context.Context, trackID, quality string) ([]models.DownloadInfo, error) {
// This requires signing - use the sign utility
// For now, return basic implementation
params := map[string]string{
"track_id": trackID,
}
if quality != "" {
params["quality"] = quality
}
var result []models.DownloadInfo
if err := c.request.Get(ctx, fmt.Sprintf("/tracks/%s/download-info", trackID), params, &result); err != nil {
@@ -62,8 +99,45 @@ func (c *Client) GetTrackDownloadInfo(ctx context.Context, trackID, quality stri
return result, nil
}
// GetTrackDownloadURL returns a signed direct download URL for a track.
// It fetches download info, finds a matching entry, downloads the XML from its
// download_info_url, parses host/path/ts/s, computes the MD5 signature, and
// returns https://{host}/get-mp3/{sign}/{ts}{path}.
func (c *Client) GetTrackDownloadURL(ctx context.Context, trackID, format string, bitrate int) (string, error) {
info, err := c.GetTrackDownloadInfo(ctx, trackID, format)
if err != nil {
return "", err
}
// Find matching download info
var di *models.DownloadInfo
for i := range info {
if info[i].Codec == format && (bitrate == 0 || info[i].Bitrate == bitrate) {
di = &info[i]
break
}
}
if di == nil && len(info) > 0 {
di = &info[0]
}
if di == nil {
return "", utils.NewYandexMusicError("no download info available", utils.ErrNotFound)
}
if di.URL != "" {
return di.URL, nil
}
if di.DownloadInfoURL == "" {
return "", utils.NewYandexMusicError("no download URL available", utils.ErrNotFound)
}
return resolveDownloadURL(ctx, di.DownloadInfoURL, c.request)
}
// DownloadTrack downloads a track by ID.
// It first fetches download info, finds a matching format/bitrate, then downloads the file content.
// It first fetches download info, finds a matching format/bitrate, resolves the
// signed URL if needed, then downloads the file content.
func (c *Client) DownloadTrack(ctx context.Context, trackID, format string, bitrate int) ([]byte, error) {
info, err := c.GetTrackDownloadInfo(ctx, trackID, format)
if err != nil {
@@ -71,25 +145,85 @@ func (c *Client) DownloadTrack(ctx context.Context, trackID, format string, bitr
}
// Find matching download info
var url string
for _, di := range info {
if di.Codec == format && (bitrate == 0 || di.Bitrate == bitrate) {
url = di.URL
var di *models.DownloadInfo
for i := range info {
if info[i].Codec == format && (bitrate == 0 || info[i].Bitrate == bitrate) {
di = &info[i]
break
}
}
if url == "" && len(info) > 0 {
url = info[0].URL
if di == nil && len(info) > 0 {
di = &info[0]
}
if di == nil {
return nil, utils.NewYandexMusicError("no download info available", utils.ErrNotFound)
}
url := di.URL
if url == "" {
if di.DownloadInfoURL == "" {
return nil, utils.NewYandexMusicError("no download URL available", utils.ErrNotFound)
}
url, err = resolveDownloadURL(ctx, di.DownloadInfoURL, c.request)
if err != nil {
return nil, err
}
}
return c.request.Download(ctx, url)
}
// GetTracksFileInfo gets file info for a track (GET /get-file-info).
func (c *Client) GetTracksFileInfo(ctx context.Context, trackID, quality string, codecs []string, transport string) (*models.FileInfo, error) {
params := map[string]string{
"track_id": trackID,
"quality": quality,
}
if len(codecs) > 0 {
params["codecs"] = strings.Join(codecs, ",")
}
if transport != "" {
params["transport"] = transport
}
var result models.FileInfo
if err := c.request.Get(ctx, "/get-file-info", params, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetTracksSimilar gets similar tracks (GET /tracks/{track_id}/similar).
func (c *Client) GetTracksSimilar(ctx context.Context, trackID string) (*models.SimilarTracks, error) {
var result models.SimilarTracks
if err := c.request.Get(ctx, fmt.Sprintf("/tracks/%s/similar", trackID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetTrackTrailer gets track trailer (GET /tracks/{track_id}/trailer).
func (c *Client) GetTrackTrailer(ctx context.Context, trackID string) (*models.TrackTrailer, error) {
var result models.TrackTrailer
if err := c.request.Get(ctx, fmt.Sprintf("/tracks/%s/trailer", trackID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetTrackFullInfo gets full track info (GET /tracks/{track_id}/full-info).
func (c *Client) GetTrackFullInfo(ctx context.Context, trackID string) (*models.TrackFullInfo, error) {
var result models.TrackFullInfo
if err := c.request.Get(ctx, fmt.Sprintf("/tracks/%s/full-info", trackID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// SearchResult represents search results containing tracks.
type SearchResult struct {
Tracks *SearchTracksResult `json:"tracks"`
+12 -12
View File
@@ -30,7 +30,7 @@ func TestClient_GetTracks(t *testing.T) {
"id": "12345",
"title": "Test Track",
"duration_ms": 180000,
"storage_id": "67890",
"storage_dir": "67890",
"cover_uri": "https://example.com/cover.jpg",
"genre": "rock",
"version": "album",
@@ -42,7 +42,7 @@ func TestClient_GetTracks(t *testing.T) {
"artists": [{"id": "1", "name": "Artist 1", "cover_uri": "https://example.com/artist.jpg"}],
"albums": [{"id": "789", "title": "Test Album", "cover_uri": "https://example.com/album.jpg", "year": 2023}],
"major": {"id": "major1", "name": "Major Label", "description": "Big label"},
"lyrics": {"id": "lyr1", "text": "Test lyrics", "available": true, "has_text": true}
"lyrics_info": {"has_available_sync_lyrics": true, "has_available_text_lyrics": true}
}
]
}`),
@@ -63,8 +63,8 @@ func TestClient_GetTracks(t *testing.T) {
if tr.Major == nil || tr.Major.Name != "Major Label" {
t.Errorf("Expected Major 'Major Label'")
}
if tr.Lyrics == nil || tr.Lyrics.Text != "Test lyrics" {
t.Errorf("Expected Lyrics 'Test lyrics'")
if tr.LyricsInfo == nil || tr.LyricsInfo.HasAvailableSyncLyrics != true {
t.Errorf("Expected LyricsInfo.HasAvailableSyncLyrics true")
}
},
},
@@ -73,8 +73,8 @@ func TestClient_GetTracks(t *testing.T) {
trackIDs: []string{"12345", "67890"},
mockResponse: []byte(`{
"result": [
{"id": "12345", "title": "Track 1", "duration_ms": 100000, "storage_id": "1", "cover_uri": "", "genre": "", "version": "", "explicit": false, "available": true, "available_for_premium_users": true, "r128": 0, "track_sharing_flag": "", "artists": [], "albums": []},
{"id": "67890", "title": "Track 2", "duration_ms": 200000, "storage_id": "2", "cover_uri": "", "genre": "", "version": "", "explicit": false, "available": true, "available_for_premium_users": true, "r128": 0, "track_sharing_flag": "", "artists": [], "albums": []}
{"id": "12345", "title": "Track 1", "duration_ms": 100000, "storage_dir": "1", "cover_uri": "", "genre": "", "version": "", "explicit": false, "available": true, "available_for_premium_users": true, "r128": 0, "track_sharing_flag": "", "artists": [], "albums": []},
{"id": "67890", "title": "Track 2", "duration_ms": 200000, "storage_dir": "2", "cover_uri": "", "genre": "", "version": "", "explicit": false, "available": true, "available_for_premium_users": true, "r128": 0, "track_sharing_flag": "", "artists": [], "albums": []}
]
}`),
expectedTracks: 2,
@@ -189,7 +189,7 @@ func TestClient_GetTrack(t *testing.T) {
"id": "12345",
"title": "Test Track",
"duration_ms": 180000,
"storage_id": "67890",
"storage_dir": "67890",
"cover_uri": "https://example.com/cover.jpg",
"genre": "rock",
"version": "album",
@@ -201,7 +201,7 @@ func TestClient_GetTrack(t *testing.T) {
"artists": [{"id": "1", "name": "Artist 1", "cover_uri": "https://example.com/artist.jpg"}],
"albums": [{"id": "789", "title": "Test Album", "cover_uri": "https://example.com/album.jpg", "year": 2023}],
"major": {"id": "major1", "name": "Major Label", "description": "Big label"},
"lyrics": {"id": "lyr1", "text": "Test lyrics", "available": true, "has_text": true}
"lyrics_info": {"has_available_sync_lyrics": true, "has_available_text_lyrics": true}
}
]
}`),
@@ -311,7 +311,7 @@ func TestClient_SearchTracks(t *testing.T) {
"id": "1",
"title": "Track 1",
"duration_ms": 100000,
"storage_id": "1",
"storage_dir": "1",
"cover_uri": "",
"genre": "",
"version": "",
@@ -327,7 +327,7 @@ func TestClient_SearchTracks(t *testing.T) {
"id": "2",
"title": "Track 2",
"duration_ms": 200000,
"storage_id": "2",
"storage_dir": "2",
"cover_uri": "",
"genre": "",
"version": "",
@@ -408,7 +408,7 @@ func TestClient_SearchTracks_Mocked(t *testing.T) {
"id": "1",
"title": "Track 1",
"duration_ms": 100000,
"storage_id": "1",
"storage_dir": "1",
"cover_uri": "",
"genre": "",
"version": "",
@@ -424,7 +424,7 @@ func TestClient_SearchTracks_Mocked(t *testing.T) {
"id": "2",
"title": "Track 2",
"duration_ms": 200000,
"storage_id": "2",
"storage_dir": "2",
"cover_uri": "",
"genre": "",
"version": "",
+1 -1
View File
@@ -112,7 +112,7 @@ func (a *AccountSettings) DeList(data []byte, c ClientInterface) ([]Model, error
// Subscription represents subscription info.
type Subscription struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Type string `json:"type"`
Status string `json:"status"`
StartDate string `json:"start_date"`
+270 -17
View File
@@ -8,31 +8,118 @@ import (
// Album represents a full album from the Yandex Music API.
type Album struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Error string `json:"error"`
Title string `json:"title"`
CoverURI string `json:"cover_uri"`
Genre string `json:"genre"`
Year int `json:"year"`
TrackCount int `json:"track_count"`
Available bool `json:"available"`
ReleaseDate string `json:"release_date"`
Artists []Artist `json:"artists"`
Labels []Label `json:"labels"`
Volumes [][]Track `json:"volumes"`
Labels []LabelOrString `json:"labels"`
Available bool `json:"available"`
AvailableForPremiumUsers bool `json:"available_for_premium_users"`
Version string `json:"version"`
CoverURI string `json:"cover_uri"`
ContentWarning string `json:"content_warning"`
OriginalReleaseYear int `json:"original_release_year"`
Genre string `json:"genre"`
TextColor string `json:"text_color"`
ShortDescription string `json:"short_description"`
Description string `json:"description"`
IsPremiere bool `json:"is_premiere"`
IsBanner bool `json:"is_banner"`
MetaType string `json:"meta_type"`
StorageDir string `json:"storage_dir"`
OgImage string `json:"og_image"`
Buy []json.RawMessage `json:"buy"`
Recent bool `json:"recent"`
VeryImportant bool `json:"very_important"`
AvailableForMobile bool `json:"available_for_mobile"`
AvailablePartially bool `json:"available_partially"`
Bests []int `json:"bests"`
Duplicates []Album `json:"duplicates"`
Prerolls []json.RawMessage `json:"prerolls"`
Volumes [][]Track `json:"volumes"`
Year int `json:"year"`
ReleaseDate string `json:"release_date"`
Type string `json:"type"`
TrackPosition *TrackPosition `json:"track_position"`
Regions []string `json:"regions"`
AvailableAsRbt bool `json:"available_as_rbt"`
LyricsAvailable bool `json:"lyrics_available"`
RememberPosition bool `json:"remember_position"`
Albums []Album `json:"albums"`
DurationMs int `json:"duration_ms"`
Explicit bool `json:"explicit"`
StartDate string `json:"start_date"`
LikesCount int `json:"likes_count"`
Deprecation *Deprecation `json:"deprecation"`
AvailableRegions []string `json:"available_regions"`
AvailableForOptions []string `json:"available_for_options"`
ListeningFinished bool `json:"listening_finished"`
Disclaimers []string `json:"disclaimers"`
ActionButton *AlbumActionButton `json:"action_button"`
Cover *Cover `json:"cover"`
DerivedColors *CoverDerivedColors `json:"derived_colors"`
MetaTagID string `json:"meta_tag_id"`
ChildContent bool `json:"child_content"`
BackgroundImageURL string `json:"background_image_url"`
CustomWave *CustomWave `json:"custom_wave"`
DurationSec int `json:"duration_sec"`
HasTrailer bool `json:"has_trailer"`
Trailer *AlbumTrailerStatus `json:"trailer"`
Pager *Pager `json:"pager"`
SortOrder string `json:"sort_order"`
Meta *AlbumMeta `json:"meta"`
Best *Best `json:"best"`
// Additional fields
Region string `json:"region"`
AvailableForPremiumUsers bool `json:"available_for_premium_users"`
AvailableForPremiumUsersOnly bool `json:"available_for_premium_users_only"`
ContentWarning string `json:"content_warning"`
Label string `json:"label"`
CatalogNumber string `json:"catalog_number"`
Options []string `json:"options"`
TrackPositions []TrackPosition `json:"track_positions"`
SimilarAlbums []Album `json:"similar_albums"`
Trailer *TrailerInfo `json:"trailer"`
}
// LabelOrString represents a label that can be either a Label object or a string.
type LabelOrString struct {
Label *Label `json:"-"`
Str string `json:"-"`
IsStr bool `json:"-"`
}
// UnmarshalJSON implements json.Unmarshaler, accepting both Label objects and strings.
func (l *LabelOrString) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err == nil {
l.Str = s
l.IsStr = true
return nil
}
var label Label
if err := json.Unmarshal(data, &label); err != nil {
return err
}
l.Label = &label
l.IsStr = false
return nil
}
// MarshalJSON implements json.Marshaler.
func (l LabelOrString) MarshalJSON() ([]byte, error) {
if l.IsStr {
return json.Marshal(l.Str)
}
return json.Marshal(l.Label)
}
// String returns the label as a string.
func (l LabelOrString) String() string {
if l.IsStr {
return l.Str
}
if l.Label != nil {
return l.Label.Name
}
return ""
}
// DeJSON deserializes an Album from JSON data.
@@ -48,6 +135,9 @@ func (a *Album) DeJSON(data []byte, c ClientInterface) (Model, error) {
if v, ok := raw["id"]; ok {
json.Unmarshal(v, &album.ID)
}
if v, ok := raw["error"]; ok {
json.Unmarshal(v, &album.Error)
}
if v, ok := raw["title"]; ok {
json.Unmarshal(v, &album.Title)
}
@@ -101,10 +191,11 @@ func (a *Album) DeJSON(data []byte, c ClientInterface) (Model, error) {
album.Artists = artists
}
if v, ok := raw["labels"]; ok {
var labels []Label
json.Unmarshal(v, &labels)
var labels []LabelOrString
if err := json.Unmarshal(v, &labels); err == nil {
album.Labels = labels
}
}
if v, ok := raw["volumes"]; ok {
var volumes [][]Track
json.Unmarshal(v, &volumes)
@@ -131,7 +222,7 @@ func (a *Album) DeJSON(data []byte, c ClientInterface) (Model, error) {
album.SimilarAlbums = similar
}
if v, ok := raw["trailer"]; ok {
var trailer TrailerInfo
var trailer AlbumTrailerStatus
json.Unmarshal(v, &trailer)
album.Trailer = &trailer
}
@@ -226,8 +317,8 @@ type Best struct {
type TrailerInfo struct {
BaseModel
PreviewURI string `json:"preview_uri"`
TrackID string `json:"track_id"`
AlbumID string `json:"album_id"`
TrackID JSONID `json:"track_id"`
AlbumID JSONID `json:"album_id"`
Duration int `json:"duration"`
}
@@ -273,3 +364,165 @@ func (ti *TrailerInfo) DeList(data []byte, c ClientInterface) ([]Model, error) {
}
return result, nil
}
// AlbumTrailerStatus represents trailer availability status for an album.
type AlbumTrailerStatus struct {
BaseModel
Available bool `json:"available"`
}
// DeJSON deserializes AlbumTrailerStatus from JSON data.
func (ats *AlbumTrailerStatus) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
status := &AlbumTrailerStatus{BaseModel: *NewBaseModel(c)}
if v, ok := raw["available"]; ok {
json.Unmarshal(v, &status.Available)
}
return status, nil
}
// DeList deserializes a list of AlbumTrailerStatus from JSON data.
func (ats *AlbumTrailerStatus) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
status := &AlbumTrailerStatus{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, status); err != nil {
continue
}
result = append(result, status)
}
return result, nil
}
// Deprecation represents album deprecation info.
type Deprecation struct {
BaseModel
TargetAlbumID JSONID `json:"target_album_id"`
Status string `json:"status"`
Done bool `json:"done"`
}
// AlbumActionButton represents an action button for an album.
type AlbumActionButton struct {
BaseModel
Text string `json:"text"`
URL string `json:"url"`
Color string `json:"color"`
ViewBrowser bool `json:"view_browser"`
}
// AlbumSimilarEntities represents similar entities for an album.
type AlbumSimilarEntities struct {
BaseModel
Items []SimilarEntityItem `json:"items"`
}
// DeJSON deserializes AlbumSimilarEntities from JSON data.
func (ae *AlbumSimilarEntities) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
entities := &AlbumSimilarEntities{BaseModel: *NewBaseModel(c)}
if v, ok := raw["items"]; ok {
var items []SimilarEntityItem
json.Unmarshal(v, &items)
entities.Items = items
}
return entities, nil
}
// DeList deserializes a list of AlbumSimilarEntities from JSON data.
func (ae *AlbumSimilarEntities) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
entities := &AlbumSimilarEntities{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, entities); err != nil {
continue
}
result = append(result, entities)
}
return result, nil
}
// SimilarEntityItem represents a similar entity item.
type SimilarEntityItem struct {
BaseModel
ID JSONID `json:"id"`
Type string `json:"type"`
Data json.RawMessage `json:"data"`
}
// AlbumTrailer represents album trailer info.
type AlbumTrailer struct {
BaseModel
Album *Album `json:"album"`
Artists []Artist `json:"artists"`
Trailer *TrailerInfo `json:"trailer"`
}
// DeJSON deserializes AlbumTrailer from JSON data.
func (at *AlbumTrailer) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
trailer := &AlbumTrailer{BaseModel: *NewBaseModel(c)}
if v, ok := raw["album"]; ok {
var album Album
json.Unmarshal(v, &album)
trailer.Album = &album
}
if v, ok := raw["artists"]; ok {
var artists []Artist
json.Unmarshal(v, &artists)
trailer.Artists = artists
}
if v, ok := raw["trailer"]; ok {
var trailerInfo TrailerInfo
json.Unmarshal(v, &trailerInfo)
trailer.Trailer = &trailerInfo
}
return trailer, nil
}
// DeList deserializes a list of AlbumTrailer from JSON data.
func (at *AlbumTrailer) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
trailer := &AlbumTrailer{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, trailer); err != nil {
continue
}
result = append(result, trailer)
}
return result, nil
}
+18 -39
View File
@@ -30,7 +30,7 @@ func TestAlbum_DeJSON(t *testing.T) {
"best": {"tracks": [{"id": "bt1", "title": "Best Track", "duration_ms": 200000}]},
"track_positions": [{"revision": 1, "timestamp": "2023-01-01T00:00:00Z", "position": 1, "from_cache": false}],
"similar_albums": [{"id": "sim1", "title": "Similar Album", "cover_uri": "https://example.com/sim.jpg", "year": 2022}],
"trailer": {"preview_uri": "https://example.com/preview.mp3", "track_id": "t1", "album_id": "123", "duration": 30}
"trailer": {"available": true}
}`)
album := &Album{BaseModel: *NewBaseModel(client)}
@@ -101,8 +101,8 @@ func TestAlbum_DeJSON(t *testing.T) {
if len(resultAlbum.Labels) != 1 {
t.Errorf("Expected 1 label, got %d", len(resultAlbum.Labels))
}
if resultAlbum.Labels[0].Name != "Label 1" {
t.Errorf("Expected label name 'Label 1', got '%s'", resultAlbum.Labels[0].Name)
if resultAlbum.Labels[0].String() != "Label 1" {
t.Errorf("Expected label name 'Label 1', got '%s'", resultAlbum.Labels[0].String())
}
// Test volumes
@@ -164,17 +164,8 @@ func TestAlbum_DeJSON(t *testing.T) {
if resultAlbum.Trailer == nil {
t.Fatal("Expected Trailer to be non-nil")
}
if resultAlbum.Trailer.PreviewURI != "https://example.com/preview.mp3" {
t.Errorf("Expected PreviewURI 'https://example.com/preview.mp3', got '%s'", resultAlbum.Trailer.PreviewURI)
}
if resultAlbum.Trailer.TrackID != "t1" {
t.Errorf("Expected TrackID 't1', got '%s'", resultAlbum.Trailer.TrackID)
}
if resultAlbum.Trailer.AlbumID != "123" {
t.Errorf("Expected AlbumID '123', got '%s'", resultAlbum.Trailer.AlbumID)
}
if resultAlbum.Trailer.Duration != 30 {
t.Errorf("Expected Duration 30, got %d", resultAlbum.Trailer.Duration)
if !resultAlbum.Trailer.Available {
t.Errorf("Expected Trailer.Available true, got false")
}
}
@@ -308,50 +299,38 @@ func TestAlbumMeta_DeList(t *testing.T) {
}
}
func TestTrailerInfo_DeJSON(t *testing.T) {
func TestAlbumTrailerStatus_DeJSON(t *testing.T) {
client := newMockClient()
data := []byte(`{
"preview_uri": "https://example.com/preview.mp3",
"track_id": "track123",
"album_id": "album456",
"duration": 45
"available": true
}`)
trailer := &TrailerInfo{BaseModel: *NewBaseModel(client)}
result, err := trailer.DeJSON(data, client)
status := &AlbumTrailerStatus{BaseModel: *NewBaseModel(client)}
result, err := status.DeJSON(data, client)
if err != nil {
t.Fatalf("DeJSON failed: %v", err)
}
resultTrailer := result.(*TrailerInfo)
if resultTrailer.PreviewURI != "https://example.com/preview.mp3" {
t.Errorf("Expected PreviewURI 'https://example.com/preview.mp3', got '%s'", resultTrailer.PreviewURI)
}
if resultTrailer.TrackID != "track123" {
t.Errorf("Expected TrackID 'track123', got '%s'", resultTrailer.TrackID)
}
if resultTrailer.AlbumID != "album456" {
t.Errorf("Expected AlbumID 'album456', got '%s'", resultTrailer.AlbumID)
}
if resultTrailer.Duration != 45 {
t.Errorf("Expected Duration 45, got %d", resultTrailer.Duration)
resultStatus := result.(*AlbumTrailerStatus)
if !resultStatus.Available {
t.Errorf("Expected Available true, got false")
}
}
func TestTrailerInfo_DeList(t *testing.T) {
func TestAlbumTrailerStatus_DeList(t *testing.T) {
client := newMockClient()
data := []byte(`[
{"preview_uri": "https://example.com/preview1.mp3", "track_id": "t1", "album_id": "a1", "duration": 30},
{"preview_uri": "https://example.com/preview2.mp3", "track_id": "t2", "album_id": "a2", "duration": 45}
{"available": true},
{"available": false}
]`)
trailer := &TrailerInfo{BaseModel: *NewBaseModel(client)}
result, err := trailer.DeList(data, client)
status := &AlbumTrailerStatus{BaseModel: *NewBaseModel(client)}
result, err := status.DeList(data, client)
if err != nil {
t.Fatalf("DeList failed: %v", err)
}
if len(result) != 2 {
t.Errorf("Expected 2 trailers, got %d", len(result))
t.Errorf("Expected 2 statuses, got %d", len(result))
}
}
+544 -25
View File
@@ -8,26 +8,25 @@ import (
// Artist represents a full artist from the Yandex Music API.
type Artist struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Name string `json:"name"`
CoverURI string `json:"cover_uri"`
Genres []string `json:"genres"`
Tracks int `json:"tracks"`
Albums int `json:"albums"`
Link string `json:"link"`
Available bool `json:"available"`
Description string `json:"description"`
Composer bool `json:"composer"`
Various bool `json:"various"`
Decomposed []Artist `json:"decomposed"`
Decomposed []Decomposed `json:"decomposed"`
Ratings *Ratings `json:"ratings"`
Counts *Counts `json:"counts"`
SocialLinks []SocialLink `json:"social_links"`
Links []Link `json:"social_links"`
Description *Description `json:"description"`
// Additional fields
IDForFrom string `json:"id_for_from"`
Cover *Cover `json:"cover"`
BriefInfo *ArtistBriefInfo `json:"brief_info"`
Donations *ArtistDonations `json:"donations"`
DonationInfo *ArtistDonationInfo `json:"donation_info"`
Concerts []Concert `json:"concerts"`
Clips []Clip `json:"clips"`
Similar []Artist `json:"similar"`
@@ -63,15 +62,9 @@ func (a *Artist) DeJSON(data []byte, c ClientInterface) (Model, error) {
if v, ok := raw["albums"]; ok {
json.Unmarshal(v, &artist.Albums)
}
if v, ok := raw["link"]; ok {
json.Unmarshal(v, &artist.Link)
}
if v, ok := raw["available"]; ok {
json.Unmarshal(v, &artist.Available)
}
if v, ok := raw["description"]; ok {
json.Unmarshal(v, &artist.Description)
}
if v, ok := raw["composer"]; ok {
json.Unmarshal(v, &artist.Composer)
}
@@ -84,10 +77,11 @@ func (a *Artist) DeJSON(data []byte, c ClientInterface) (Model, error) {
// Nested objects
if v, ok := raw["decomposed"]; ok {
var decomposed []Artist
json.Unmarshal(v, &decomposed)
var decomposed []Decomposed
if err := json.Unmarshal(v, &decomposed); err == nil {
artist.Decomposed = decomposed
}
}
if v, ok := raw["ratings"]; ok {
var ratings Ratings
json.Unmarshal(v, &ratings)
@@ -99,9 +93,10 @@ func (a *Artist) DeJSON(data []byte, c ClientInterface) (Model, error) {
artist.Counts = &counts
}
if v, ok := raw["social_links"]; ok {
var links []SocialLink
json.Unmarshal(v, &links)
artist.SocialLinks = links
var links []Link
if err := json.Unmarshal(v, &links); err == nil {
artist.Links = links
}
}
if v, ok := raw["cover"]; ok {
var cover Cover
@@ -113,10 +108,10 @@ func (a *Artist) DeJSON(data []byte, c ClientInterface) (Model, error) {
json.Unmarshal(v, &brief)
artist.BriefInfo = &brief
}
if v, ok := raw["donations"]; ok {
var donations ArtistDonations
json.Unmarshal(v, &donations)
artist.Donations = &donations
if v, ok := raw["donation_info"]; ok {
var donation ArtistDonationInfo
json.Unmarshal(v, &donation)
artist.DonationInfo = &donation
}
if v, ok := raw["concerts"]; ok {
var concerts []Concert
@@ -167,8 +162,10 @@ func (a *Artist) DeList(data []byte, c ClientInterface) ([]Model, error) {
// Ratings represents artist ratings.
type Ratings struct {
Rating float64 `json:"rating"`
Rating int `json:"rating"`
Votes int `json:"votes"`
Week int `json:"week"`
Day int `json:"day"`
}
// Counts represents artist counts.
@@ -196,12 +193,14 @@ type Cover struct {
// ArtistBriefInfo represents brief artist info.
type ArtistBriefInfo struct {
BaseModel
Description string `json:"description"`
Available bool `json:"available"`
}
// ArtistDonations represents artist donations info.
type ArtistDonations struct {
BaseModel
Items []DonationItem `json:"items"`
Total int `json:"total"`
Goal int `json:"goal"`
@@ -210,7 +209,7 @@ type ArtistDonations struct {
// DonationItem represents a donation item.
type DonationItem struct {
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Amount int `json:"amount"`
@@ -219,10 +218,182 @@ type DonationItem struct {
Currency string `json:"currency"`
}
// Decomposed represents a decomposed artist that can be either a string or an Artist object.
type Decomposed struct {
Artist *Artist `json:"-"`
Str string `json:"-"`
IsStr bool `json:"-"`
}
// UnmarshalJSON implements json.Unmarshaler, accepting both string and Artist object.
func (d *Decomposed) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err == nil {
d.Str = s
d.IsStr = true
return nil
}
var artist Artist
if err := json.Unmarshal(data, &artist); err != nil {
return err
}
d.Artist = &artist
d.IsStr = false
return nil
}
// MarshalJSON implements json.Marshaler.
func (d Decomposed) MarshalJSON() ([]byte, error) {
if d.IsStr {
return json.Marshal(d.Str)
}
return json.Marshal(d.Artist)
}
// Link represents a link to an artist's official page or social network.
type Link struct {
BaseModel
Title string `json:"title"`
Href string `json:"href"`
Type string `json:"type"`
SocialNetwork string `json:"social_network"`
}
// DeJSON deserializes Link from JSON data.
func (l *Link) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
link := &Link{BaseModel: *NewBaseModel(c)}
if v, ok := raw["title"]; ok {
json.Unmarshal(v, &link.Title)
}
if v, ok := raw["href"]; ok {
json.Unmarshal(v, &link.Href)
}
if v, ok := raw["type"]; ok {
json.Unmarshal(v, &link.Type)
}
if v, ok := raw["social_network"]; ok {
json.Unmarshal(v, &link.SocialNetwork)
}
return link, nil
}
// DeList deserializes a list of Links from JSON data.
func (l *Link) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
link := &Link{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, link); err != nil {
continue
}
result = append(result, link)
}
return result, nil
}
// Description represents artist description.
type Description struct {
BaseModel
Text string `json:"text"`
URI string `json:"uri"`
}
// DeJSON deserializes Description from JSON data.
func (d *Description) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
desc := &Description{BaseModel: *NewBaseModel(c)}
if v, ok := raw["text"]; ok {
json.Unmarshal(v, &desc.Text)
}
if v, ok := raw["uri"]; ok {
json.Unmarshal(v, &desc.URI)
}
return desc, nil
}
// DeList deserializes a list of Descriptions from JSON data.
func (d *Description) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
desc := &Description{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, desc); err != nil {
continue
}
result = append(result, desc)
}
return result, nil
}
// ArtistDonationInfo represents artist donation info.
type ArtistDonationInfo struct {
BaseModel
Title string `json:"title"`
URL string `json:"url"`
}
// DeJSON deserializes ArtistDonationInfo from JSON data.
func (adi *ArtistDonationInfo) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
info := &ArtistDonationInfo{BaseModel: *NewBaseModel(c)}
if v, ok := raw["title"]; ok {
json.Unmarshal(v, &info.Title)
}
if v, ok := raw["url"]; ok {
json.Unmarshal(v, &info.URL)
}
return info, nil
}
// DeList deserializes a list of ArtistDonationInfo from JSON data.
func (adi *ArtistDonationInfo) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
info := &ArtistDonationInfo{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, info); err != nil {
continue
}
result = append(result, info)
}
return result, nil
}
// ConcertShort represents a concert (minimal for artist).
type ConcertShort struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
Date string `json:"date"`
Venue string `json:"venue"`
@@ -298,7 +469,7 @@ func (c *ConcertShort) DeList(data []byte, client ClientInterface) ([]Model, err
// ClipShort represents a clip (minimal for artist).
type ClipShort struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
Duration int `json:"duration"`
CoverURI string `json:"cover_uri"`
@@ -366,3 +537,351 @@ func (c *ClipShort) DeList(data []byte, client ClientInterface) ([]Model, error)
}
return result, nil
}
// ArtistSimilar represents similar artists.
type ArtistSimilar struct {
BaseModel
Artist *Artist `json:"artist"`
SimilarArtists []Artist `json:"similar_artists"`
}
// DeJSON deserializes ArtistSimilar from JSON data.
func (as *ArtistSimilar) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
similar := &ArtistSimilar{BaseModel: *NewBaseModel(c)}
if v, ok := raw["artist"]; ok {
var artist Artist
json.Unmarshal(v, &artist)
similar.Artist = &artist
}
if v, ok := raw["similar_artists"]; ok {
var artists []Artist
json.Unmarshal(v, &artists)
similar.SimilarArtists = artists
}
return similar, nil
}
// DeList deserializes a list of ArtistSimilar from JSON data.
func (as *ArtistSimilar) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
similar := &ArtistSimilar{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, similar); err != nil {
continue
}
result = append(result, similar)
}
return result, nil
}
// ArtistLinks represents links to artist's official pages or social networks.
type ArtistLinks struct {
BaseModel
Links []Link `json:"links"`
}
// DeJSON deserializes ArtistLinks from JSON data.
func (al *ArtistLinks) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
links := &ArtistLinks{BaseModel: *NewBaseModel(c)}
if v, ok := raw["links"]; ok {
var linkList []Link
json.Unmarshal(v, &linkList)
links.Links = linkList
}
return links, nil
}
// DeList deserializes a list of ArtistLinks from JSON data.
func (al *ArtistLinks) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
links := &ArtistLinks{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, links); err != nil {
continue
}
result = append(result, links)
}
return result, nil
}
// ArtistAbout represents information "About artist".
type ArtistAbout struct {
BaseModel
Artist *Artist `json:"artist"`
Stats *Stats `json:"stats"`
Description string `json:"description"`
Links []ArtistLink `json:"links"`
Covers []Cover `json:"covers"`
ArtistType string `json:"artist_type"`
}
// DeJSON deserializes ArtistAbout from JSON data.
func (aa *ArtistAbout) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
about := &ArtistAbout{BaseModel: *NewBaseModel(c)}
if v, ok := raw["artist"]; ok {
var artist Artist
json.Unmarshal(v, &artist)
about.Artist = &artist
}
if v, ok := raw["stats"]; ok {
var stats Stats
json.Unmarshal(v, &stats)
about.Stats = &stats
}
if v, ok := raw["description"]; ok {
json.Unmarshal(v, &about.Description)
}
if v, ok := raw["links"]; ok {
var linkList []ArtistLink
json.Unmarshal(v, &linkList)
about.Links = linkList
}
if v, ok := raw["covers"]; ok {
var covers []Cover
json.Unmarshal(v, &covers)
about.Covers = covers
}
if v, ok := raw["artist_type"]; ok {
json.Unmarshal(v, &about.ArtistType)
}
return about, nil
}
// DeList deserializes a list of ArtistAbout from JSON data.
func (aa *ArtistAbout) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
about := &ArtistAbout{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, about); err != nil {
continue
}
result = append(result, about)
}
return result, nil
}
// ArtistInfo represents detailed artist info.
type ArtistInfo struct {
BaseModel
Artist *Artist `json:"artist"`
LikesCount int `json:"likes_count"`
Stats *Stats `json:"stats"`
Trailer *ArtistTrailerStatus `json:"trailer"`
Covers []Cover `json:"covers"`
Description string `json:"description"`
ArtistType string `json:"artist_type"`
}
// DeJSON deserializes ArtistInfo from JSON data.
func (ai *ArtistInfo) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
info := &ArtistInfo{BaseModel: *NewBaseModel(c)}
if v, ok := raw["artist"]; ok {
var artist Artist
json.Unmarshal(v, &artist)
info.Artist = &artist
}
if v, ok := raw["likes_count"]; ok {
json.Unmarshal(v, &info.LikesCount)
}
if v, ok := raw["stats"]; ok {
var stats Stats
json.Unmarshal(v, &stats)
info.Stats = &stats
}
if v, ok := raw["trailer"]; ok {
var trailer ArtistTrailerStatus
json.Unmarshal(v, &trailer)
info.Trailer = &trailer
}
if v, ok := raw["covers"]; ok {
var covers []Cover
json.Unmarshal(v, &covers)
info.Covers = covers
}
if v, ok := raw["description"]; ok {
json.Unmarshal(v, &info.Description)
}
if v, ok := raw["artist_type"]; ok {
json.Unmarshal(v, &info.ArtistType)
}
return info, nil
}
// DeList deserializes a list of ArtistInfo from JSON data.
func (ai *ArtistInfo) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
info := &ArtistInfo{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, info); err != nil {
continue
}
result = append(result, info)
}
return result, nil
}
// ArtistSkeleton represents artist page skeleton.
type ArtistSkeleton struct {
BaseModel
ID string `json:"id"`
Title string `json:"title"`
Blocks []SkeletonBlock `json:"blocks"`
}
// DeJSON deserializes ArtistSkeleton from JSON data.
func (as *ArtistSkeleton) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
skeleton := &ArtistSkeleton{BaseModel: *NewBaseModel(c)}
if v, ok := raw["id"]; ok {
json.Unmarshal(v, &skeleton.ID)
}
if v, ok := raw["title"]; ok {
json.Unmarshal(v, &skeleton.Title)
}
if v, ok := raw["blocks"]; ok {
var blocks []SkeletonBlock
json.Unmarshal(v, &blocks)
skeleton.Blocks = blocks
}
return skeleton, nil
}
// DeList deserializes a list of ArtistSkeleton from JSON data.
func (as *ArtistSkeleton) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
skeleton := &ArtistSkeleton{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, skeleton); err != nil {
continue
}
result = append(result, skeleton)
}
return result, nil
}
// ArtistTrailerStatus represents trailer availability for artist.
type ArtistTrailerStatus struct {
BaseModel
Available bool `json:"available"`
}
// DeJSON deserializes ArtistTrailerStatus from JSON data.
func (ats *ArtistTrailerStatus) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
status := &ArtistTrailerStatus{BaseModel: *NewBaseModel(c)}
if v, ok := raw["available"]; ok {
json.Unmarshal(v, &status.Available)
}
return status, nil
}
// DeList deserializes a list of ArtistTrailerStatus from JSON data.
func (ats *ArtistTrailerStatus) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
status := &ArtistTrailerStatus{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, status); err != nil {
continue
}
result = append(result, status)
}
return result, nil
}
// ArtistLink represents a link for artist about page.
type ArtistLink struct {
BaseModel
Title string `json:"title"`
Subtitle string `json:"subtitle,omitempty"`
URL string `json:"url,omitempty"`
ImgURL string `json:"img_url,omitempty"`
}
// Stats represents artist stats.
type Stats struct {
BaseModel
Rating int `json:"rating"`
Votes int `json:"votes"`
Month int `json:"month"`
Week int `json:"week"`
Day int `json:"day"`
}
// SkeletonBlock represents a skeleton block.
type SkeletonBlock struct {
BaseModel
ID string `json:"id"`
Type string `json:"type"`
Data json.RawMessage `json:"data"`
}
+96
View File
@@ -4,6 +4,8 @@ package models
import (
"encoding/json"
"fmt"
"strconv"
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/utils"
)
@@ -81,3 +83,97 @@ func (b *BaseModel) SetClient(c ClientInterface) {
func (b *BaseModel) GetClient() ClientInterface {
return b.Client
}
// JSONID is an ID that can be represented as either a JSON string or a JSON number.
// The Yandex Music API sometimes returns IDs as numbers (e.g., 12345) and sometimes
// as strings (e.g., "12345"). JSONID accepts both and stores the value as a string.
type JSONID string
// UnmarshalJSON implements json.Unmarshaler, accepting both JSON strings and numbers.
func (id *JSONID) UnmarshalJSON(data []byte) error {
// Try string first
var s string
if err := json.Unmarshal(data, &s); err == nil {
*id = JSONID(s)
return nil
}
// Try number (float64 is the default JSON number decoding)
var f float64
if err := json.Unmarshal(data, &f); err != nil {
return fmt.Errorf("cannot unmarshal %v into JSONID: %w", string(data), err)
}
*id = JSONID(strconv.FormatFloat(f, 'f', -1, 64))
return nil
}
// String returns the ID as a Go string.
func (id JSONID) String() string {
return string(id)
}
// R128 represents EBU R 128 loudness normalization parameters.
// The Yandex Music API returns this either as a number (e.g., -23.5) or as an object
// with fields i, tp, important_secs.
type R128 struct {
BaseModel
I float64 `json:"i"`
Tp float64 `json:"tp"`
ImportantSecs int `json:"important_secs"`
}
// UnmarshalJSON implements json.Unmarshaler, accepting both JSON numbers and objects.
func (r *R128) UnmarshalJSON(data []byte) error {
// Try float64 first (API sometimes returns just a number)
var f float64
if err := json.Unmarshal(data, &f); err == nil {
r.I = f
r.Tp = 0
r.ImportantSecs = 0
return nil
}
// Try object with fields i, tp, important_secs
var obj struct {
I float64 `json:"i"`
Tp float64 `json:"tp"`
ImportantSecs int `json:"important_secs"`
}
if err := json.Unmarshal(data, &obj); err != nil {
return err
}
r.I = obj.I
r.Tp = obj.Tp
r.ImportantSecs = obj.ImportantSecs
return nil
}
// Float64 returns the integrated loudness (i) value as a float64.
func (r R128) Float64() float64 {
return r.I
}
// DeJSON deserializes an R128 from JSON data.
func (r *R128) DeJSON(data []byte, c ClientInterface) (Model, error) {
r128 := &R128{BaseModel: *NewBaseModel(c)}
if err := r128.UnmarshalJSON(data); err != nil {
return nil, err
}
return r128, nil
}
// DeList deserializes a list of R128 from JSON data.
func (r *R128) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
r128 := &R128{BaseModel: *NewBaseModel(c)}
if err := r128.UnmarshalJSON(item); err != nil {
continue
}
result = append(result, r128)
}
return result, nil
}
+48 -40
View File
@@ -8,18 +8,21 @@ import (
// Clip represents a clip (short video) from the Yandex Music API.
type Clip struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
Version string `json:"version"`
PlayerID string `json:"player_id"`
UUID string `json:"uuid"`
Thumbnail string `json:"thumbnail"`
PreviewURL string `json:"preview_url"`
Duration int `json:"duration"`
CoverURI string `json:"cover_uri"`
PreviewURI string `json:"preview_uri"`
Available bool `json:"available"`
Views int `json:"views"`
Likes int `json:"likes"`
CreatedAt string `json:"created_at"`
Artist *Artist `json:"artist"`
Track *Track `json:"track"`
Playlist *Playlist `json:"playlist"`
TrackIDs []int `json:"track_ids"`
Artists []Artist `json:"artists"`
Disclaimers []string `json:"disclaimers"`
Explicit bool `json:"explicit"`
Cover *Cover `json:"cover"`
ContentRestrictions *ContentRestrictions `json:"content_restrictions"`
Client *ClientInterface `json:"-"`
ClipType string `json:"clip_type"`
IsVertical bool `json:"is_vertical"`
AspectRatio string `json:"aspect_ratio"`
@@ -40,26 +43,47 @@ func (c *Clip) DeJSON(data []byte, client ClientInterface) (Model, error) {
if v, ok := raw["title"]; ok {
json.Unmarshal(v, &clip.Title)
}
if v, ok := raw["version"]; ok {
json.Unmarshal(v, &clip.Version)
}
if v, ok := raw["player_id"]; ok {
json.Unmarshal(v, &clip.PlayerID)
}
if v, ok := raw["uuid"]; ok {
json.Unmarshal(v, &clip.UUID)
}
if v, ok := raw["thumbnail"]; ok {
json.Unmarshal(v, &clip.Thumbnail)
}
if v, ok := raw["preview_url"]; ok {
json.Unmarshal(v, &clip.PreviewURL)
}
if v, ok := raw["duration"]; ok {
json.Unmarshal(v, &clip.Duration)
}
if v, ok := raw["cover_uri"]; ok {
json.Unmarshal(v, &clip.CoverURI)
if v, ok := raw["track_ids"]; ok {
json.Unmarshal(v, &clip.TrackIDs)
}
if v, ok := raw["preview_uri"]; ok {
json.Unmarshal(v, &clip.PreviewURI)
if v, ok := raw["artists"]; ok {
var artists []Artist
json.Unmarshal(v, &artists)
clip.Artists = artists
}
if v, ok := raw["available"]; ok {
json.Unmarshal(v, &clip.Available)
if v, ok := raw["disclaimers"]; ok {
json.Unmarshal(v, &clip.Disclaimers)
}
if v, ok := raw["views"]; ok {
json.Unmarshal(v, &clip.Views)
if v, ok := raw["explicit"]; ok {
json.Unmarshal(v, &clip.Explicit)
}
if v, ok := raw["likes"]; ok {
json.Unmarshal(v, &clip.Likes)
if v, ok := raw["cover"]; ok {
var cover Cover
json.Unmarshal(v, &cover)
clip.Cover = &cover
}
if v, ok := raw["created_at"]; ok {
json.Unmarshal(v, &clip.CreatedAt)
if v, ok := raw["content_restrictions"]; ok {
var cr ContentRestrictions
json.Unmarshal(v, &cr)
clip.ContentRestrictions = &cr
}
if v, ok := raw["clip_type"]; ok {
json.Unmarshal(v, &clip.ClipType)
@@ -71,22 +95,6 @@ func (c *Clip) DeJSON(data []byte, client ClientInterface) (Model, error) {
json.Unmarshal(v, &clip.AspectRatio)
}
if v, ok := raw["artist"]; ok {
var artist Artist
json.Unmarshal(v, &artist)
clip.Artist = &artist
}
if v, ok := raw["track"]; ok {
var track Track
json.Unmarshal(v, &track)
clip.Track = &track
}
if v, ok := raw["playlist"]; ok {
var playlist Playlist
json.Unmarshal(v, &playlist)
clip.Playlist = &playlist
}
return clip, nil
}
@@ -132,7 +140,7 @@ type ArtistClipItem struct {
// Shot represents a shot (short video).
type Shot struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Duration int `json:"duration"`
@@ -258,7 +266,7 @@ type ShotEvent struct {
// ShotType represents a shot type.
type ShotType struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Icon string `json:"icon"`
+83 -5
View File
@@ -8,11 +8,12 @@ import (
// Concert represents a concert from the Yandex Music API.
type Concert struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Date string `json:"date"`
Time string `json:"time"`
Datetime string `json:"datetime"`
Venue *ConcertVenue `json:"venue"`
Location *ConcertLocation `json:"location"`
MinPrice int `json:"min_price"`
@@ -27,6 +28,13 @@ type Concert struct {
Cashback *ConcertCashback `json:"cashback"`
Tabs []ConcertTabConfig `json:"tabs"`
Skeleton *ConcertSkeleton `json:"skeleton"`
AfishaURL string `json:"afisha_url"`
City string `json:"city"`
Place string `json:"place"`
Address string `json:"address"`
ContentRating string `json:"content_rating"`
DataSessionID string `json:"data_session_id"`
EventInfo *ConcertEventInfo `json:"event_info"`
}
// DeJSON deserializes a Concert from JSON data.
@@ -77,6 +85,30 @@ func (c *Concert) DeJSON(data []byte, client ClientInterface) (Model, error) {
if v, ok := raw["genres"]; ok {
json.Unmarshal(v, &concert.Genres)
}
if v, ok := raw["datetime"]; ok {
json.Unmarshal(v, &concert.Datetime)
}
if v, ok := raw["afisha_url"]; ok {
json.Unmarshal(v, &concert.AfishaURL)
}
if v, ok := raw["city"]; ok {
json.Unmarshal(v, &concert.City)
}
if v, ok := raw["place"]; ok {
json.Unmarshal(v, &concert.Place)
}
if v, ok := raw["address"]; ok {
json.Unmarshal(v, &concert.Address)
}
if v, ok := raw["content_rating"]; ok {
json.Unmarshal(v, &concert.ContentRating)
}
if v, ok := raw["data_session_id"]; ok {
json.Unmarshal(v, &concert.DataSessionID)
}
if v, ok := raw["event_info"]; ok {
json.Unmarshal(v, &concert.EventInfo)
}
if v, ok := raw["venue"]; ok {
var venue ConcertVenue
@@ -133,7 +165,7 @@ func (c *Concert) DeList(data []byte, client ClientInterface) ([]Model, error) {
// ConcertVenue represents a concert venue.
type ConcertVenue struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Name string `json:"name"`
Address string `json:"address"`
City string `json:"city"`
@@ -145,7 +177,7 @@ type ConcertVenue struct {
// ConcertLocation represents a concert location.
type ConcertLocation struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Name string `json:"name"`
City string `json:"city"`
Country string `json:"country"`
@@ -214,7 +246,7 @@ type ConcertMinPrice struct {
// ConcertTabConfig represents concert tab config.
type ConcertTabConfig struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
Type string `json:"type"`
Active bool `json:"active"`
@@ -237,8 +269,54 @@ type ConcertSkeleton struct {
// ConcertSkeletonBlock represents a concert skeleton block.
type ConcertSkeletonBlock struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Type string `json:"type"`
Title string `json:"title"`
Data json.RawMessage `json:"data"`
}
// ArtistConcerts represents concerts by an artist.
type ArtistConcerts struct {
BaseModel
ArtistTitle string `json:"artist_title"`
Concerts []Concert `json:"concerts"`
}
// DeJSON deserializes ArtistConcerts from JSON data.
func (ac *ArtistConcerts) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
acObj := &ArtistConcerts{BaseModel: *NewBaseModel(c)}
if v, ok := raw["artist_title"]; ok {
json.Unmarshal(v, &acObj.ArtistTitle)
}
if v, ok := raw["concerts"]; ok {
var concerts []Concert
json.Unmarshal(v, &concerts)
acObj.Concerts = concerts
}
return acObj, nil
}
// DeList deserializes a list of ArtistConcerts from JSON data.
func (ac *ArtistConcerts) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
ac := &ArtistConcerts{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, ac); err != nil {
continue
}
result = append(result, ac)
}
return result, nil
}
+82
View File
@@ -170,3 +170,85 @@ func (ltp *LicenceTextPart) DeList(data []byte, client ClientInterface) ([]Model
}
return result, nil
}
// Disclaimer represents a disclaimer.
type Disclaimer struct {
BaseModel
ForeignAgent *ForeignAgent `json:"foreign_agent"`
}
// DeJSON deserializes Disclaimer from JSON data.
func (d *Disclaimer) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
disclaimer := &Disclaimer{BaseModel: *NewBaseModel(c)}
if v, ok := raw["foreign_agent"]; ok {
var fa ForeignAgent
json.Unmarshal(v, &fa)
disclaimer.ForeignAgent = &fa
}
return disclaimer, nil
}
// DeList deserializes a list of Disclaimers from JSON data.
func (d *Disclaimer) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
disclaimer := &Disclaimer{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, disclaimer); err != nil {
continue
}
result = append(result, disclaimer)
}
return result, nil
}
// ForeignAgent represents a foreign agent.
type ForeignAgent struct {
BaseModel
Text string `json:"text"`
}
// DeJSON deserializes ForeignAgent from JSON data.
func (fa *ForeignAgent) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
agent := &ForeignAgent{BaseModel: *NewBaseModel(c)}
if v, ok := raw["text"]; ok {
json.Unmarshal(v, &agent.Text)
}
return agent, nil
}
// DeList deserializes a list of ForeignAgent from JSON data.
func (fa *ForeignAgent) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
agent := &ForeignAgent{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, agent); err != nil {
continue
}
result = append(result, agent)
}
return result, nil
}
+27 -10
View File
@@ -8,9 +8,15 @@ import (
// Feed represents the main feed from the Yandex Music API.
type Feed struct {
BaseModel
CanGetMoreEvents bool `json:"can_get_more_events"`
Pumpkin bool `json:"pumpkin"`
IsWizardPassed bool `json:"is_wizard_passed"`
Headlines []string `json:"headlines"`
Today string `json:"today"`
Days []Day `json:"days"`
GeneratedPlaylists []GeneratedPlaylist `json:"generated_playlists"`
PlayContexts []PlayContext `json:"play_contexts"`
NextRevision string `json:"next_revision"`
}
// DeJSON deserializes a Feed from JSON data.
@@ -22,6 +28,21 @@ func (f *Feed) DeJSON(data []byte, c ClientInterface) (Model, error) {
feed := &Feed{BaseModel: *NewBaseModel(c)}
if v, ok := raw["can_get_more_events"]; ok {
json.Unmarshal(v, &feed.CanGetMoreEvents)
}
if v, ok := raw["pumpkin"]; ok {
json.Unmarshal(v, &feed.Pumpkin)
}
if v, ok := raw["is_wizard_passed"]; ok {
json.Unmarshal(v, &feed.IsWizardPassed)
}
if v, ok := raw["headlines"]; ok {
json.Unmarshal(v, &feed.Headlines)
}
if v, ok := raw["today"]; ok {
json.Unmarshal(v, &feed.Today)
}
if v, ok := raw["days"]; ok {
var days []Day
json.Unmarshal(v, &days)
@@ -37,6 +58,9 @@ func (f *Feed) DeJSON(data []byte, c ClientInterface) (Model, error) {
json.Unmarshal(v, &contexts)
feed.PlayContexts = contexts
}
if v, ok := raw["next_revision"]; ok {
json.Unmarshal(v, &feed.NextRevision)
}
return feed, nil
}
@@ -122,7 +146,7 @@ func (d *Day) DeList(data []byte, c ClientInterface) ([]Model, error) {
// DayBlock represents a block in a day.
type DayBlock struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Type string `json:"type"`
Title string `json:"title"`
Description string `json:"description"`
@@ -141,7 +165,7 @@ type DayBlockData struct {
// GeneratedPlaylist represents a generated playlist.
type GeneratedPlaylist struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
CoverURI string `json:"cover_uri"`
@@ -261,7 +285,7 @@ type TrackWithAds struct {
// Ad represents an ad.
type Ad struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Type string `json:"type"`
Title string `json:"title"`
Description string `json:"description"`
@@ -270,10 +294,3 @@ type Ad struct {
Duration int `json:"duration"`
}
// AdParams represents ad parameters.
type AdParams struct {
BaseModel
Frequency int `json:"frequency"`
MaxPerDay int `json:"max_per_day"`
Targeting string `json:"targeting"`
}
+17 -5
View File
@@ -8,12 +8,15 @@ import (
// Label represents a label from the Yandex Music API.
type Label struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Name string `json:"name"`
CoverURI string `json:"cover_uri"`
Description string `json:"description"`
DescriptionFormatted string `json:"description_formatted"`
Image string `json:"image"`
Website string `json:"website"`
SocialLinks []SocialLink `json:"social_links"`
Links []Link `json:"links"`
Type string `json:"type"`
Available bool `json:"available"`
Owner *User `json:"owner"`
}
@@ -45,10 +48,19 @@ func (l *Label) DeJSON(data []byte, client ClientInterface) (Model, error) {
if v, ok := raw["available"]; ok {
json.Unmarshal(v, &label.Available)
}
if v, ok := raw["social_links"]; ok {
var links []SocialLink
if v, ok := raw["description_formatted"]; ok {
json.Unmarshal(v, &label.DescriptionFormatted)
}
if v, ok := raw["image"]; ok {
json.Unmarshal(v, &label.Image)
}
if v, ok := raw["links"]; ok {
var links []Link
json.Unmarshal(v, &links)
label.SocialLinks = links
label.Links = links
}
if v, ok := raw["type"]; ok {
json.Unmarshal(v, &label.Type)
}
if v, ok := raw["owner"]; ok {
var user User
+61 -2
View File
@@ -8,13 +8,15 @@ import (
// Landing represents a landing page from the Yandex Music API.
type Landing struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
CoverURI string `json:"cover_uri"`
Type string `json:"type"`
Blocks []LandingBlock `json:"blocks"`
Available bool `json:"available"`
Pumpkin bool `json:"pumpkin"`
ContentID string `json:"content_id"`
}
// DeJSON deserializes a Landing from JSON data.
@@ -44,6 +46,12 @@ func (l *Landing) DeJSON(data []byte, c ClientInterface) (Model, error) {
if v, ok := raw["available"]; ok {
json.Unmarshal(v, &landing.Available)
}
if v, ok := raw["pumpkin"]; ok {
json.Unmarshal(v, &landing.Pumpkin)
}
if v, ok := raw["content_id"]; ok {
json.Unmarshal(v, &landing.ContentID)
}
if v, ok := raw["blocks"]; ok {
var blocks []LandingBlock
@@ -75,7 +83,7 @@ func (l *Landing) DeList(data []byte, c ClientInterface) ([]Model, error) {
// LandingBlock represents a block in a landing.
type LandingBlock struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Type string `json:"type"`
Title string `json:"title"`
Description string `json:"description"`
@@ -94,12 +102,63 @@ type LandingBlockData struct {
// LandingList represents a list of landings.
type LandingList struct {
BaseModel
Type string `json:"type"`
TypeForFrom string `json:"type_for_from"`
Title string `json:"title"`
ID string `json:"id"`
Landings []Landing `json:"landings"`
NewReleases []int `json:"new_releases"`
NewPlaylists []PlaylistID `json:"new_playlists"`
Podcasts []int `json:"podcasts"`
Total int `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
}
// PlaylistID represents a playlist ID reference.
type PlaylistID struct {
BaseModel
UID int `json:"uid"`
Kind int `json:"kind"`
}
// DeJSON deserializes a PlaylistID from JSON data.
func (pid *PlaylistID) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
playlistID := &PlaylistID{BaseModel: *NewBaseModel(c)}
if v, ok := raw["uid"]; ok {
json.Unmarshal(v, &playlistID.UID)
}
if v, ok := raw["kind"]; ok {
json.Unmarshal(v, &playlistID.Kind)
}
return playlistID, nil
}
// DeList deserializes a list of PlaylistID from JSON data.
func (pid *PlaylistID) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
pid := &PlaylistID{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, pid); err != nil {
continue
}
result = append(result, pid)
}
return result, nil
}
// DeJSON deserializes a LandingList from JSON data.
func (ll *LandingList) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
+7 -7
View File
@@ -8,7 +8,7 @@ import (
// Metatag represents a metatag from the Yandex Music API.
type Metatag struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Image string `json:"image"`
@@ -81,7 +81,7 @@ type MetatagTree struct {
// MetatagLeaf represents a metatag leaf.
type MetatagLeaf struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Image string `json:"image"`
@@ -133,13 +133,13 @@ type MetatagSortByValue struct {
type MetatagArtistEntry struct {
BaseModel
Artist *Artist `json:"artist"`
Score float64 `json:"score"`
PopularTracks []Track `json:"popular_tracks"`
}
// Genre represents a genre from the Yandex Music API.
type Genre struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Image string `json:"image"`
@@ -205,7 +205,7 @@ func (g *Genre) DeList(data []byte, client ClientInterface) ([]Model, error) {
// Chart represents a chart from the Yandex Music API.
type Chart struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
CoverURI string `json:"cover_uri"`
@@ -286,7 +286,7 @@ type ChartItem struct {
// ChartInfoMenu represents a chart info menu.
type ChartInfoMenu struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Items []ChartInfoMenuItem `json:"items"`
@@ -295,7 +295,7 @@ type ChartInfoMenu struct {
// ChartInfoMenuItem represents a chart info menu item.
type ChartInfoMenuItem struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Action string `json:"action"`
+5 -6
View File
@@ -68,7 +68,7 @@ func (mh *MusicHistory) DeList(data []byte, client ClientInterface) ([]Model, er
// MusicHistoryTab represents a music history tab.
type MusicHistoryTab struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Icon string `json:"icon"`
@@ -79,7 +79,7 @@ type MusicHistoryTab struct {
// MusicHistoryGroup represents a music history group.
type MusicHistoryGroup struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Items []MusicHistoryItem `json:"items"`
@@ -89,7 +89,7 @@ type MusicHistoryGroup struct {
// MusicHistoryItem represents a music history item.
type MusicHistoryItem struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Type string `json:"type"` // "track", "album", "artist", "playlist"
Track *Track `json:"track"`
Album *Album `json:"album"`
@@ -110,13 +110,12 @@ type MusicHistoryItemData struct {
PlayContext *PlayContext `json:"play_context"`
Position int `json:"position"`
Duration int `json:"duration"`
Progress float64 `json:"progress"`
}
}
// MusicHistoryItemID represents music history item ID.
type MusicHistoryItemID struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Type string `json:"type"`
}
+47 -2
View File
@@ -8,7 +8,7 @@ import (
// Pin represents a pin from the Yandex Music API.
type Pin struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Image string `json:"image"`
@@ -21,6 +21,7 @@ type Pin struct {
CreatedAt string `json:"created_at"`
ExpiresAt string `json:"expires_at"`
Active bool `json:"active"`
ContentRestrictions *ContentRestrictions `json:"content_restrictions"`
}
// DeJSON deserializes a Pin from JSON data.
@@ -59,6 +60,11 @@ func (p *Pin) DeJSON(data []byte, client ClientInterface) (Model, error) {
if v, ok := raw["active"]; ok {
json.Unmarshal(v, &pin.Active)
}
if v, ok := raw["content_restrictions"]; ok {
var cr ContentRestrictions
json.Unmarshal(v, &cr)
pin.ContentRestrictions = &cr
}
if v, ok := raw["track"]; ok {
var track Track
@@ -121,6 +127,8 @@ type PinsList struct {
// Presaves represents presaves.
type Presaves struct {
BaseModel
UpcomingAlbums []Album `json:"upcoming_albums"`
ReleasedAlbums []Album `json:"released_albums"`
Presaves []Presave `json:"presaves"`
Total int `json:"total"`
Page int `json:"page"`
@@ -130,7 +138,7 @@ type Presaves struct {
// Presave represents a presave.
type Presave struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Image string `json:"image"`
@@ -213,3 +221,40 @@ func (p *Presave) DeList(data []byte, client ClientInterface) ([]Model, error) {
}
return result, nil
}
// DeJSON deserializes Presaves from JSON data.
func (p *Presaves) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
presaves := &Presaves{BaseModel: *NewBaseModel(client)}
if v, ok := raw["upcoming_albums"]; ok {
var albums []Album
json.Unmarshal(v, &albums)
presaves.UpcomingAlbums = albums
}
if v, ok := raw["released_albums"]; ok {
var albums []Album
json.Unmarshal(v, &albums)
presaves.ReleasedAlbums = albums
}
if v, ok := raw["presaves"]; ok {
var presaveList []Presave
json.Unmarshal(v, &presaveList)
presaves.Presaves = presaveList
}
if v, ok := raw["total"]; ok {
json.Unmarshal(v, &presaves.Total)
}
if v, ok := raw["page"]; ok {
json.Unmarshal(v, &presaves.Page)
}
if v, ok := raw["page_size"]; ok {
json.Unmarshal(v, &presaves.PageSize)
}
return presaves, nil
}
+7 -5
View File
@@ -8,7 +8,7 @@ import (
// Queue represents a playback queue from the Yandex Music API.
type Queue struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
TrackIDs []string `json:"track_ids"`
CurrentIndex int `json:"current_index"`
QueueType string `json:"queue_type"`
@@ -93,13 +93,15 @@ func (q *Queue) DeList(data []byte, c ClientInterface) ([]Model, error) {
// QueueItem represents a queue item.
type QueueItem struct {
BaseModel
ID JSONID `json:"id"`
TrackID string `json:"track_id"`
Track *Track `json:"track"`
Position int `json:"position"`
AddedAt string `json:"added_at"`
AddedBy string `json:"added_by"`
Playable bool `json:"playable"`
PlayContext *PlayContext `json:"play_context"`
Modified string `json:"modified"`
Context *PlayContext `json:"context"`
}
// DeJSON deserializes a QueueItem from JSON data.
@@ -132,10 +134,10 @@ func (qi *QueueItem) DeJSON(data []byte, c ClientInterface) (Model, error) {
json.Unmarshal(v, &track)
item.Track = &track
}
if v, ok := raw["play_context"]; ok {
if v, ok := raw["context"]; ok {
var ctx PlayContext
json.Unmarshal(v, &ctx)
item.PlayContext = &ctx
item.Context = &ctx
}
return item, nil
@@ -162,7 +164,7 @@ func (qi *QueueItem) DeList(data []byte, c ClientInterface) ([]Model, error) {
// PlayContext represents a play context.
type PlayContext struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Type string `json:"type"` // "playlist", "album", "artist", "radio", "search"
SourceID string `json:"source_id"`
SourceKind string `json:"source_kind"`
+395 -4
View File
@@ -8,7 +8,7 @@ import (
// RadioStation represents a radio station from the Yandex Music API.
type RadioStation struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
CoverURI string `json:"cover_uri"`
@@ -122,7 +122,7 @@ func (s *StationTracksResult) DeList(data []byte, client ClientInterface) ([]Mod
// RotorStation represents a rotor (generative) station.
type RotorStation struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
CoverURI string `json:"cover_uri"`
@@ -192,8 +192,11 @@ func (r *RotorStation) DeList(data []byte, client ClientInterface) ([]Model, err
// RotorSettings represents rotor station settings.
type RotorSettings struct {
BaseModel
Diversity float64 `json:"diversity"`
Energy float64 `json:"energy"`
Language string `json:"language"`
Diversity string `json:"diversity"`
Mood int `json:"mood"`
Energy int `json:"energy"`
MoodEnergy string `json:"mood_energy"`
Acousticness float64 `json:"acousticness"`
Instrumentalness float64 `json:"instrumentalness"`
Live float64 `json:"live"`
@@ -314,6 +317,70 @@ func (r *RotorSession) DeList(data []byte, client ClientInterface) ([]Model, err
return result, nil
}
// AdParams represents ad parameters.
type AdParams struct {
BaseModel
PartnerID string `json:"partner_id"`
CategoryID string `json:"category_id"`
PageRef string `json:"page_ref"`
TargetRef string `json:"target_ref"`
OtherParams string `json:"other_params"`
GenreID string `json:"genre_id"`
GenreName string `json:"genre_name"`
}
// DeJSON deserializes AdParams from JSON data.
func (ap *AdParams) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
params := &AdParams{BaseModel: *NewBaseModel(c)}
if v, ok := raw["partner_id"]; ok {
json.Unmarshal(v, &params.PartnerID)
}
if v, ok := raw["category_id"]; ok {
json.Unmarshal(v, &params.CategoryID)
}
if v, ok := raw["page_ref"]; ok {
json.Unmarshal(v, &params.PageRef)
}
if v, ok := raw["target_ref"]; ok {
json.Unmarshal(v, &params.TargetRef)
}
if v, ok := raw["other_params"]; ok {
json.Unmarshal(v, &params.OtherParams)
}
if v, ok := raw["genre_id"]; ok {
json.Unmarshal(v, &params.GenreID)
}
if v, ok := raw["genre_name"]; ok {
json.Unmarshal(v, &params.GenreName)
}
return params, nil
}
// DeList deserializes a list of AdParams from JSON data.
func (ap *AdParams) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
params := &AdParams{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, params); err != nil {
continue
}
result = append(result, params)
}
return result, nil
}
// SessionFeedback represents session feedback.
type SessionFeedback struct {
BaseModel
@@ -327,3 +394,327 @@ type SessionFeedbacks struct {
BaseModel
Feedbacks []SessionFeedback `json:"feedbacks"`
}
// Dashboard represents rotor stations dashboard.
type Dashboard struct {
BaseModel
DashboardID string `json:"dashboard_id"`
Stations []StationResult `json:"stations"`
Pumpkin bool `json:"pumpkin"`
}
// DeJSON deserializes Dashboard from JSON data.
func (d *Dashboard) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
dashboard := &Dashboard{BaseModel: *NewBaseModel(c)}
if v, ok := raw["dashboard_id"]; ok {
json.Unmarshal(v, &dashboard.DashboardID)
}
if v, ok := raw["stations"]; ok {
var stations []StationResult
json.Unmarshal(v, &stations)
dashboard.Stations = stations
}
if v, ok := raw["pumpkin"]; ok {
json.Unmarshal(v, &dashboard.Pumpkin)
}
return dashboard, nil
}
// DeList deserializes a list of Dashboards from JSON data.
func (d *Dashboard) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
dashboard := &Dashboard{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, dashboard); err != nil {
continue
}
result = append(result, dashboard)
}
return result, nil
}
// StationResult represents a station result.
type StationResult struct {
BaseModel
Station *Station `json:"station"`
Settings *RotorSettings `json:"settings"`
Settings2 *RotorSettings `json:"settings2"`
AdParams *AdParams `json:"ad_params"`
}
// DeJSON deserializes StationResult from JSON data.
func (sr *StationResult) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
result := &StationResult{BaseModel: *NewBaseModel(c)}
if v, ok := raw["station"]; ok {
var station Station
json.Unmarshal(v, &station)
result.Station = &station
}
if v, ok := raw["settings"]; ok {
var settings RotorSettings
json.Unmarshal(v, &settings)
result.Settings = &settings
}
if v, ok := raw["settings2"]; ok {
var settings2 RotorSettings
json.Unmarshal(v, &settings2)
result.Settings2 = &settings2
}
if v, ok := raw["ad_params"]; ok {
var adParams AdParams
json.Unmarshal(v, &adParams)
result.AdParams = &adParams
}
return result, nil
}
// DeList deserializes a list of StationResults from JSON data.
func (sr *StationResult) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
sr := &StationResult{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, sr); err != nil {
continue
}
result = append(result, sr)
}
return result, nil
}
// GenerativeStream represents a generative stream.
type GenerativeStream struct {
BaseModel
Data *GenerativeStreamData `json:"data"`
Version string `json:"version"`
Stream *GenerativeStreamInfo `json:"stream"`
}
// DeJSON deserializes GenerativeStream from JSON data.
func (gs *GenerativeStream) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
stream := &GenerativeStream{BaseModel: *NewBaseModel(c)}
if v, ok := raw["data"]; ok {
var data GenerativeStreamData
json.Unmarshal(v, &data)
stream.Data = &data
}
if v, ok := raw["version"]; ok {
json.Unmarshal(v, &stream.Version)
}
if v, ok := raw["stream"]; ok {
var streamInfo GenerativeStreamInfo
json.Unmarshal(v, &streamInfo)
stream.Stream = &streamInfo
}
return stream, nil
}
// DeList deserializes a list of GenerativeStreams from JSON data.
func (gs *GenerativeStream) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
stream := &GenerativeStream{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, stream); err != nil {
continue
}
result = append(result, stream)
}
return result, nil
}
// Station represents a radio station (used in StationResult).
type Station struct {
BaseModel
ID JSONID `json:"id"`
Name string `json:"name"`
Icon string `json:"icon"`
IDForFrom string `json:"id_for_from"`
}
// DeJSON deserializes Station from JSON data.
func (s *Station) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
station := &Station{BaseModel: *NewBaseModel(c)}
if v, ok := raw["id"]; ok {
json.Unmarshal(v, &station.ID)
}
if v, ok := raw["name"]; ok {
json.Unmarshal(v, &station.Name)
}
if v, ok := raw["icon"]; ok {
json.Unmarshal(v, &station.Icon)
}
if v, ok := raw["id_for_from"]; ok {
json.Unmarshal(v, &station.IDForFrom)
}
return station, nil
}
// DeList deserializes a list of Stations from JSON data.
func (s *Station) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
station := &Station{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, station); err != nil {
continue
}
result = append(result, station)
}
return result, nil
}
// GenerativeStreamData represents generative stream data.
type GenerativeStreamData struct {
BaseModel
Title string `json:"title"`
Subtitle string `json:"subtitle"`
BackgroundColor string `json:"background_color"`
DerivedColors *CoverDerivedColors `json:"derived_colors"`
ImageURL string `json:"image_url"`
VideoCoverURI string `json:"video_cover_uri"`
Explanations string `json:"explanations"`
}
// DeJSON deserializes GenerativeStreamData from JSON data.
func (gsd *GenerativeStreamData) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
gsdData := &GenerativeStreamData{BaseModel: *NewBaseModel(c)}
if v, ok := raw["title"]; ok {
json.Unmarshal(v, &gsdData.Title)
}
if v, ok := raw["subtitle"]; ok {
json.Unmarshal(v, &gsdData.Subtitle)
}
if v, ok := raw["background_color"]; ok {
json.Unmarshal(v, &gsdData.BackgroundColor)
}
if v, ok := raw["derived_colors"]; ok {
var dc CoverDerivedColors
json.Unmarshal(v, &dc)
gsdData.DerivedColors = &dc
}
if v, ok := raw["image_url"]; ok {
json.Unmarshal(v, &gsdData.ImageURL)
}
if v, ok := raw["video_cover_uri"]; ok {
json.Unmarshal(v, &gsdData.VideoCoverURI)
}
if v, ok := raw["explanations"]; ok {
json.Unmarshal(v, &gsdData.Explanations)
}
return gsdData, nil
}
// DeList deserializes a list of GenerativeStreamData from JSON data.
func (gsd *GenerativeStreamData) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
data := &GenerativeStreamData{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, data); err != nil {
continue
}
result = append(result, data)
}
return result, nil
}
// GenerativeStreamInfo represents generative stream info.
type GenerativeStreamInfo struct {
BaseModel
ID string `json:"id"`
URL string `json:"url"`
}
// DeJSON deserializes GenerativeStreamInfo from JSON data.
func (gsi *GenerativeStreamInfo) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
info := &GenerativeStreamInfo{BaseModel: *NewBaseModel(c)}
if v, ok := raw["id"]; ok {
json.Unmarshal(v, &info.ID)
}
if v, ok := raw["url"]; ok {
json.Unmarshal(v, &info.URL)
}
return info, nil
}
// DeList deserializes a list of GenerativeStreamInfo from JSON data.
func (gsi *GenerativeStreamInfo) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
info := &GenerativeStreamInfo{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, info); err != nil {
continue
}
result = append(result, info)
}
return result, nil
}
+16 -3
View File
@@ -3,12 +3,25 @@ package models
// SearchResult represents search results from the Yandex Music API.
type SearchResult struct {
SearchRequestID string `json:"search_request_id"`
Text string `json:"text"`
Best *Best `json:"best"`
Tracks *SearchTracksResult `json:"tracks"`
Albums *SearchAlbumsResult `json:"albums"`
Artists *SearchArtistsResult `json:"artists"`
Playlists *SearchPlaylistsResult `json:"playlists"`
Videos *SearchVideosResult `json:"videos"`
Users *SearchUsersResult `json:"users"`
Podcasts *SearchAlbumsResult `json:"podcasts"`
PodcastEpisodes *SearchTracksResult `json:"podcast_episodes"`
Clips *SearchVideosResult `json:"clips"`
Banner *SearchBanner `json:"banner"`
Page int `json:"page"`
PerPage int `json:"per_page"`
MisspellResult bool `json:"misspell_result"`
MisspellOriginal string `json:"misspell_original"`
MisspellCorrected bool `json:"misspell_corrected"`
Nocorrect bool `json:"nocorrect"`
}
// SearchTracksResult represents track search results.
@@ -49,7 +62,7 @@ type SearchUsersResult struct {
// SearchBanner represents a search banner.
type SearchBanner struct {
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Image string `json:"image"`
@@ -60,7 +73,7 @@ type SearchBanner struct {
// Tag represents a search tag.
type Tag struct {
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Count int `json:"count"`
@@ -70,7 +83,7 @@ type Tag struct {
// Video represents a video (minimal).
type Video struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
Duration int `json:"duration"`
CoverURI string `json:"cover_uri"`
+2 -2
View File
@@ -54,7 +54,7 @@ func (e *Experiments) DeList(data []byte, client ClientInterface) ([]Model, erro
// ExperimentDetail represents experiment details.
type ExperimentDetail struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Status string `json:"status"`
@@ -240,7 +240,7 @@ func (p *PermissionAlerts) DeList(data []byte, client ClientInterface) ([]Model,
// PermissionAlert represents a permission alert.
type PermissionAlert struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Type string `json:"type"`
Title string `json:"title"`
Description string `json:"description"`
+1 -1
View File
@@ -333,7 +333,7 @@ func (vs *VideoSupplement) DeList(data []byte, c ClientInterface) ([]Model, erro
// Supplement represents a track supplement.
type Supplement struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Lyrics *LyricsInfo `json:"lyrics"`
Videos []VideoSupplement `json:"videos"`
RadioIsAvailable bool `json:"radio_is_available"`
+506 -25
View File
@@ -8,23 +8,67 @@ import (
// Track represents a full track from the Yandex Music API.
type Track struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
DurationMs int `json:"duration_ms"`
StorageID string `json:"storage_id"`
CoverURI string `json:"cover_uri"`
Available bool `json:"available"`
Artists []Artist `json:"artists"`
Albums []Album `json:"albums"`
Genre string `json:"genre"`
Version string `json:"version"`
Explicit bool `json:"explicit"`
Available bool `json:"available"`
AvailableForPremiumUsers bool `json:"available_for_premium_users"`
R128 float64 `json:"r128"`
TrackSharingFlag string `json:"track_sharing_flag"`
LyricsAvailable bool `json:"lyrics_available"`
Best bool `json:"best"`
RealID JSONID `json:"real_id"`
OgImage string `json:"og_image"`
Type string `json:"type"`
CoverURI string `json:"cover_uri"`
Major *Major `json:"major"`
Lyrics *TrackLyricsInfo `json:"lyrics"`
DurationMs int `json:"duration_ms"`
StorageDir string `json:"storage_dir"`
FileSize int64 `json:"file_size"`
Genre string `json:"genre"`
AvailableAsRingtone bool `json:"available_as_ringtone"`
RingtoneID string `json:"ringtone_id"`
SimilarTracks []Track `json:"similar_tracks"`
Position *TrackPosition `json:"position"`
Substituted *Track `json:"substituted"`
MatchedTrack *Track `json:"matched_track"`
Normalization *Normalization `json:"normalization"`
Error string `json:"error"`
CanPublish bool `json:"can_publish"`
State string `json:"state"`
DesiredVisibility string `json:"desired_visibility"`
Filename string `json:"filename"`
UserInfo *User `json:"user_info"`
MetaData *MetaData `json:"meta_data"`
Regions []string `json:"regions"`
AvailableAsRbt bool `json:"available_as_rbt"`
ContentWarning string `json:"content_warning"`
Explicit bool `json:"explicit"`
PreviewDurationMs int `json:"preview_duration_ms"`
AvailableFullWithoutPermission bool `json:"available_full_without_permission"`
Version string `json:"version"`
RememberPosition bool `json:"remember_position"`
BackgroundVideoURI string `json:"background_video_uri"`
ShortDescription string `json:"short_description"`
IsSuitableForChildren bool `json:"is_suitable_for_children"`
TrackSource string `json:"track_source"`
AvailableForOptions []string `json:"available_for_options"`
R128 *R128 `json:"r128"`
LyricsInfo *TrackLyricsInfo `json:"lyrics_info"`
TrackSharingFlag string `json:"track_sharing_flag"`
DerivedColors *CoverDerivedColors `json:"derived_colors"`
Fade *Fade `json:"fade"`
SmartPreviewParams *SmartPreviewParams `json:"smart_preview_params"`
SpecialAudioResources []string `json:"special_audio_resources"`
Disclaimers []string `json:"disclaimers"`
BackgroundVideoID string `json:"background_video_id"`
PlayerID string `json:"player_id"`
MixFade *Fade `json:"mix_fade"`
Chart *Chart `json:"chart"`
ClipIDs []int `json:"clip_ids"`
Isrc string `json:"isrc"`
UgcArtistName string `json:"ugc_artist_name"`
PodcastEpisodeType string `json:"podcast_episode_type"`
PubDate string `json:"pub_date"`
DownloadInfo []DownloadInfo `json:"download_info"`
}
@@ -47,8 +91,8 @@ func (t *Track) DeJSON(data []byte, c ClientInterface) (Model, error) {
if v, ok := raw["duration_ms"]; ok {
json.Unmarshal(v, &track.DurationMs)
}
if v, ok := raw["storage_id"]; ok {
json.Unmarshal(v, &track.StorageID)
if v, ok := raw["storage_dir"]; ok {
json.Unmarshal(v, &track.StorageDir)
}
if v, ok := raw["cover_uri"]; ok {
json.Unmarshal(v, &track.CoverURI)
@@ -74,6 +118,12 @@ func (t *Track) DeJSON(data []byte, c ClientInterface) (Model, error) {
if v, ok := raw["track_sharing_flag"]; ok {
json.Unmarshal(v, &track.TrackSharingFlag)
}
if v, ok := raw["available_as_ringtone"]; ok {
json.Unmarshal(v, &track.AvailableAsRingtone)
}
if v, ok := raw["ringtone_id"]; ok {
json.Unmarshal(v, &track.RingtoneID)
}
// Nested objects
if v, ok := raw["artists"]; ok {
@@ -94,10 +144,10 @@ func (t *Track) DeJSON(data []byte, c ClientInterface) (Model, error) {
track.Major = &major
}
}
if v, ok := raw["lyrics"]; ok {
if v, ok := raw["lyrics_info"]; ok {
var lyrics TrackLyricsInfo
if err := json.Unmarshal(v, &lyrics); err == nil {
track.Lyrics = &lyrics
track.LyricsInfo = &lyrics
}
}
if v, ok := raw["similar_tracks"]; ok {
@@ -112,6 +162,12 @@ func (t *Track) DeJSON(data []byte, c ClientInterface) (Model, error) {
track.DownloadInfo = downloadInfo
}
}
if v, ok := raw["position"]; ok {
var pos TrackPosition
if err := json.Unmarshal(v, &pos); err == nil {
track.Position = &pos
}
}
return track, nil
}
@@ -144,23 +200,55 @@ func (t *Track) GetDownloadInfo(quality string) *DownloadInfo {
return nil
}
// Note: Artist and Album types are defined in their respective files
// These are imported automatically since they're in the same package
// Major represents major label info for a track.
type Major struct {
ID string `json:"id"`
ID JSONID `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
}
// TrackLyricsInfo represents minimal lyrics info for a track.
// TrackLyricsInfo represents lyrics availability info for a track.
type TrackLyricsInfo struct {
ID string `json:"id"`
Text string `json:"text"`
Major *Major `json:"major"`
Available bool `json:"available"`
HasText bool `json:"has_text"`
BaseModel
HasAvailableSyncLyrics bool `json:"has_available_sync_lyrics"`
HasAvailableTextLyrics bool `json:"has_available_text_lyrics"`
}
// DeJSON deserializes TrackLyricsInfo from JSON data.
func (tli *TrackLyricsInfo) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
info := &TrackLyricsInfo{BaseModel: *NewBaseModel(c)}
if v, ok := raw["has_available_sync_lyrics"]; ok {
json.Unmarshal(v, &info.HasAvailableSyncLyrics)
}
if v, ok := raw["has_available_text_lyrics"]; ok {
json.Unmarshal(v, &info.HasAvailableTextLyrics)
}
return info, nil
}
// DeList deserializes a list of TrackLyricsInfo from JSON data.
func (tli *TrackLyricsInfo) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
info := &TrackLyricsInfo{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, info); err != nil {
continue
}
result = append(result, info)
}
return result, nil
}
// DownloadInfo represents file download info for a track.
@@ -171,4 +259,397 @@ type DownloadInfo struct {
Size int64 `json:"size"`
URL string `json:"url"`
DirectLink bool `json:"direct_link"`
DownloadInfoURL string `json:"download_info_url"`
DirectLinkURL string `json:"-"` // Computed signed download URL
}
// Normalization represents track normalization values.
type Normalization struct {
BaseModel
Gain float64 `json:"gain"`
Peak int `json:"peak"`
}
// MetaData represents track metadata.
type MetaData struct {
BaseModel
Album string `json:"album"`
Volume int `json:"volume"`
Year int `json:"year"`
Number int `json:"number"`
Genre string `json:"genre"`
Lyricist string `json:"lyricist"`
Version string `json:"version"`
Composer string `json:"composer"`
}
// Fade represents fade parameters for a track.
type Fade struct {
BaseModel
InStart float64 `json:"in_start"`
InStop float64 `json:"in_stop"`
OutStart float64 `json:"out_start"`
OutStop float64 `json:"out_stop"`
}
// SmartPreviewParams represents smart preview parameters.
type SmartPreviewParams struct {
BaseModel
DurationMs int `json:"duration_ms"`
Fade *Fade `json:"fade"`
}
// CoverDerivedColors represents derived colors from cover art.
type CoverDerivedColors struct {
BaseModel
Average string `json:"average"`
WaveText string `json:"wave_text"`
MiniPlayer string `json:"mini_player"`
Accent string `json:"accent"`
}
// CustomWave represents a custom wave for playlists.
type CustomWave struct {
BaseModel
Title string `json:"title"`
AnimationURL string `json:"animation_url"`
Position string `json:"position"`
Header string `json:"header"`
BackgroundImageURL string `json:"background_image_url"`
SquareAgentAnimation string `json:"square_agent_animation"`
}
// TrackId represents a track ID reference.
type TrackId struct {
BaseModel
ID JSONID `json:"id"`
TrackID JSONID `json:"track_id"`
AlbumID JSONID `json:"album_id"`
From string `json:"from"`
}
// DeJSON deserializes a TrackId from JSON data.
func (ti *TrackId) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
tid := &TrackId{BaseModel: *NewBaseModel(c)}
if v, ok := raw["id"]; ok {
json.Unmarshal(v, &tid.ID)
}
if v, ok := raw["track_id"]; ok {
json.Unmarshal(v, &tid.TrackID)
}
if v, ok := raw["album_id"]; ok {
json.Unmarshal(v, &tid.AlbumID)
}
if v, ok := raw["from"]; ok {
json.Unmarshal(v, &tid.From)
}
return tid, nil
}
// DeList deserializes a list of TrackId from JSON data.
func (ti *TrackId) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
tid := &TrackId{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, tid); err != nil {
continue
}
result = append(result, tid)
}
return result, nil
}
// TrackFullInfo represents full track info.
type TrackFullInfo struct {
BaseModel
Track *Track `json:"track"`
SimilarTracks []Track `json:"similar_tracks"`
AlsoInAlbums []Track `json:"also_in_albums"`
Aliases []string `json:"aliases"`
Artists []Artist `json:"artists"`
}
// DeJSON deserializes a TrackFullInfo from JSON data.
func (tfi *TrackFullInfo) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
info := &TrackFullInfo{BaseModel: *NewBaseModel(c)}
if v, ok := raw["track"]; ok {
var track Track
if err := json.Unmarshal(v, &track); err == nil {
info.Track = &track
}
}
if v, ok := raw["similar_tracks"]; ok {
var tracks []Track
if err := json.Unmarshal(v, &tracks); err == nil {
info.SimilarTracks = tracks
}
}
if v, ok := raw["also_in_albums"]; ok {
var tracks []Track
if err := json.Unmarshal(v, &tracks); err == nil {
info.AlsoInAlbums = tracks
}
}
if v, ok := raw["aliases"]; ok {
json.Unmarshal(v, &info.Aliases)
}
if v, ok := raw["artists"]; ok {
var artists []Artist
if err := json.Unmarshal(v, &artists); err == nil {
info.Artists = artists
}
}
return info, nil
}
// DeList deserializes a list of TrackFullInfo from JSON data.
func (tfi *TrackFullInfo) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
info := &TrackFullInfo{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, info); err != nil {
continue
}
result = append(result, info)
}
return result, nil
}
// SimilarTracks represents similar tracks.
type SimilarTracks struct {
BaseModel
Track *Track `json:"track"`
SimilarTracks []Track `json:"similar_tracks"`
}
// DeJSON deserializes SimilarTracks from JSON data.
func (st *SimilarTracks) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
similar := &SimilarTracks{BaseModel: *NewBaseModel(c)}
if v, ok := raw["track"]; ok {
var track Track
if err := json.Unmarshal(v, &track); err == nil {
similar.Track = &track
}
}
if v, ok := raw["similar_tracks"]; ok {
var tracks []Track
if err := json.Unmarshal(v, &tracks); err == nil {
similar.SimilarTracks = tracks
}
}
return similar, nil
}
// DeList deserializes a list of SimilarTracks from JSON data.
func (st *SimilarTracks) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
similar := &SimilarTracks{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, similar); err != nil {
continue
}
result = append(result, similar)
}
return result, nil
}
// TrackTrailer represents track trailer info.
type TrackTrailer struct {
BaseModel
Title string `json:"title"`
Track *Track `json:"track"`
}
// DeJSON deserializes a TrackTrailer from JSON data.
func (tt *TrackTrailer) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
trailer := &TrackTrailer{BaseModel: *NewBaseModel(c)}
if v, ok := raw["title"]; ok {
json.Unmarshal(v, &trailer.Title)
}
if v, ok := raw["track"]; ok {
var track Track
if err := json.Unmarshal(v, &track); err == nil {
trailer.Track = &track
}
}
return trailer, nil
}
// DeList deserializes a list of TrackTrailer from JSON data.
func (tt *TrackTrailer) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
trailer := &TrackTrailer{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, trailer); err != nil {
continue
}
result = append(result, trailer)
}
return result, nil
}
// FileInfo represents file info for a track.
type FileInfo struct {
BaseModel
DownloadInfo *FileDownloadInfo `json:"download_info"`
}
// DeJSON deserializes FileInfo from JSON data.
func (fi *FileInfo) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
info := &FileInfo{BaseModel: *NewBaseModel(c)}
if v, ok := raw["download_info"]; ok {
var di FileDownloadInfo
json.Unmarshal(v, &di)
info.DownloadInfo = &di
}
return info, nil
}
// DeList deserializes a list of FileInfo from JSON data.
func (fi *FileInfo) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
info := &FileInfo{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, info); err != nil {
continue
}
result = append(result, info)
}
return result, nil
}
// FileDownloadInfo represents file download info.
type FileDownloadInfo struct {
BaseModel
TrackID string `json:"track_id"`
Quality string `json:"quality"`
Codec string `json:"codec"`
Bitrate int `json:"bitrate"`
Transport string `json:"transport"`
URL string `json:"url"`
URLs []string `json:"urls"`
RealID string `json:"real_id"`
Gain bool `json:"gain"`
Key string `json:"key"`
}
// DeJSON deserializes FileDownloadInfo from JSON data.
func (fdi *FileDownloadInfo) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
info := &FileDownloadInfo{BaseModel: *NewBaseModel(c)}
if v, ok := raw["track_id"]; ok {
json.Unmarshal(v, &info.TrackID)
}
if v, ok := raw["quality"]; ok {
json.Unmarshal(v, &info.Quality)
}
if v, ok := raw["codec"]; ok {
json.Unmarshal(v, &info.Codec)
}
if v, ok := raw["bitrate"]; ok {
json.Unmarshal(v, &info.Bitrate)
}
if v, ok := raw["transport"]; ok {
json.Unmarshal(v, &info.Transport)
}
if v, ok := raw["url"]; ok {
json.Unmarshal(v, &info.URL)
}
if v, ok := raw["urls"]; ok {
json.Unmarshal(v, &info.URLs)
}
if v, ok := raw["real_id"]; ok {
json.Unmarshal(v, &info.RealID)
}
if v, ok := raw["gain"]; ok {
json.Unmarshal(v, &info.Gain)
}
if v, ok := raw["key"]; ok {
json.Unmarshal(v, &info.Key)
}
return info, nil
}
// DeList deserializes a list of FileDownloadInfo from JSON data.
func (fdi *FileDownloadInfo) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
return nil, err
}
result := make([]Model, 0, len(rawList))
for _, item := range rawList {
info := &FileDownloadInfo{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, info); err != nil {
continue
}
result = append(result, info)
}
return result, nil
}
+7 -7
View File
@@ -8,10 +8,10 @@ import (
// TrackShort represents a lightweight track used in lists.
type TrackShort struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Title string `json:"title"`
DurationMs int `json:"duration_ms"`
StorageID string `json:"storage_id"`
StorageDir string `json:"storage_dir"`
CoverURI string `json:"cover_uri"`
Artists []Artist `json:"artists"`
Albums []Album `json:"albums"`
@@ -20,10 +20,10 @@ type TrackShort struct {
Explicit bool `json:"explicit"`
Available bool `json:"available"`
AvailableForPremiumUsers bool `json:"available_for_premium_users"`
R128 float64 `json:"r128"`
R128 *R128 `json:"r128"`
TrackSharingFlag string `json:"track_sharing_flag"`
Major *Major `json:"major"`
Lyrics *SupplementLyrics `json:"lyrics"`
Lyrics *TrackLyricsInfo `json:"lyrics"`
SimilarTracks []TrackShort `json:"similar_tracks"`
DownloadInfo []DownloadInfo `json:"download_info"`
// Additional fields from track_short.py
@@ -51,8 +51,8 @@ func (t *TrackShort) DeJSON(data []byte, c ClientInterface) (Model, error) {
if v, ok := raw["duration_ms"]; ok {
json.Unmarshal(v, &track.DurationMs)
}
if v, ok := raw["storage_id"]; ok {
json.Unmarshal(v, &track.StorageID)
if v, ok := raw["storage_dir"]; ok {
json.Unmarshal(v, &track.StorageDir)
}
if v, ok := raw["cover_uri"]; ok {
json.Unmarshal(v, &track.CoverURI)
@@ -105,7 +105,7 @@ func (t *TrackShort) DeJSON(data []byte, c ClientInterface) (Model, error) {
}
}
if v, ok := raw["lyrics"]; ok {
var lyrics SupplementLyrics
var lyrics TrackLyricsInfo
if err := json.Unmarshal(v, &lyrics); err == nil {
track.Lyrics = &lyrics
}
+4 -4
View File
@@ -43,7 +43,7 @@ func TestTrack_DeJSON(t *testing.T) {
"id": "123",
"title": "Test Track",
"duration_ms": 180000,
"storage_id": "456",
"storage_dir": "456",
"cover_uri": "https://example.com/cover.jpg",
"genre": "rock",
"version": "album",
@@ -55,7 +55,7 @@ func TestTrack_DeJSON(t *testing.T) {
"artists": [{"id": "1", "name": "Artist 1", "cover_uri": "https://example.com/artist.jpg"}],
"albums": [{"id": "789", "title": "Test Album", "cover_uri": "https://example.com/album.jpg", "year": 2023}],
"major": {"id": "major1", "name": "Major Label", "description": "Big label"},
"lyrics": {"id": "lyr1", "text": "Test lyrics", "available": true, "has_text": true}
"lyrics_info": {"has_available_sync_lyrics": true, "has_available_text_lyrics": true}
}`)
track := &Track{BaseModel: *NewBaseModel(client)}
@@ -80,8 +80,8 @@ func TestTrack_DeJSON(t *testing.T) {
if resultTrack.Major == nil || resultTrack.Major.Name != "Major Label" {
t.Errorf("Expected Major 'Major Label'")
}
if resultTrack.Lyrics == nil || resultTrack.Lyrics.Text != "Test lyrics" {
t.Errorf("Expected Lyrics 'Test lyrics'")
if resultTrack.LyricsInfo == nil || resultTrack.LyricsInfo.HasAvailableSyncLyrics != true {
t.Errorf("Expected LyricsInfo.HasAvailableSyncLyrics true")
}
}