Compare commits

...
18 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
MrixsandClaude Code 0456ca0665 Add comprehensive godoc documentation to all exported symbols
- Document all exported types, functions, methods, and constants across 52 files
- Add package-level doc comments to client, models, utils, and ynison packages
- Document HTTP method and endpoint path for each API client method
- Document model struct fields and DeJSON/DeList/ToJSON/ToMap methods
- Fix duplicate package doc comment in internal/models/base.go

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-18 08:57:43 +03:00
MrixsandClaude Code 7a68ad86cb Add package-level godoc documentation
- models: Package doc comment describing data models with DeJSON/DeList
- utils: Package doc comment for utility functions (JSON, signing, schema mismatch, errors)
- ynison: Package doc comment for Ynison WebSocket client protocol

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-18 08:39:50 +03:00
MrixsandClaude Code 5786f0df1e Add godoc documentation to client package
- Add package-level doc comment
- Add doc comments to ClientConfig, Client, Status, Account, Track types
- Document all exported methods and fields

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-18 08:37:39 +03:00
MrixsandClaude Code 70c2a5cd01 Fix API endpoints: resource IDs in URL path, not query params
- artists/{artist_id}/tracks and /direct-albums: artistID in path, return ArtistTracks/ArtistAlbums with pager
- likes: /users/me/likes/{type}s/{add-multiple|remove}, IDs as body params
- queue: /queues/{queue_id}/... instead of /queue/...?queue_id=...
- radio: /rotor/station/{station_id}/info and /tracks
- concerts: /concerts/{concert_id}/info, /concerts/tab-config
- playlists: /users/{user_id}/playlists
- labels: /labels/{label_id}, /albums, /artists
- clips: /clips/will/like, /artists/{artist_id}/blocks/artist-clips
- feed: /landing3/* endpoints

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-18 08:32:29 +03:00
58 changed files with 3959 additions and 1144 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
.serena/
+182
View File
@@ -0,0 +1,182 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is a Go port of the [yandex-music-api](https://github.com/MarshalX/yandex-music-api) Python library. The original Python library is in `yandex-music-api-python/` and serves as the reference implementation.
The Python library provides:
- **Sync and async HTTP clients** for Yandex Music API
- **Ynison protocol support** for player synchronization across devices
- **Radio sessions** (My Wave, generative stations)
- **High-level wrapper objects** (Track, Album, Artist, Playlist, etc.)
- **Schema mismatch handling** for API evolution
- **Multiple JSON backend support** (orjson, pydantic-core, ujson, stdlib json)
## Python Library Architecture (Reference)
### Core Structure
```
yandex_music/
├── client.py # Sync Client (main entry point)
├── client_async.py # Async ClientAsync
├── _client_base.py # Shared base class with common attributes
├── base.py # YandexMusicObject base model with de_json/de_list
├── exceptions.py # Custom exceptions
├── utils/
│ ├── request.py # Sync HTTP request handling
│ ├── request_async.py # Async HTTP request handling
│ ├── json_backend.py # Pluggable JSON backends
│ └── schema_mismatch.py # Schema mismatch reporting
├── _client/ # Generated sync method mixins (20+ files)
├── _client_async/ # Generated async method mixins
├── track/, album/, artist/, playlist/, etc. # Model packages
├── ynison/ # Ynison protocol (WebSocket)
│ ├── client.py # Sync Ynison client
│ ├── client_async.py # Async Ynison client
│ └── models/ # Protobuf-generated models
└── supplement/ # Lyrics, video supplements
```
### Key Patterns
- **Mixin-based Client**: `Client` inherits from 20+ mixins (AccountMixin, AlbumsMixin, TracksMixin, etc.)
- **Model deserialization**: Each model has `de_json(data, client)` and `de_list(list_data, client)` classmethods
- **Request signing**: Requests to `api.music.yandex.net` are signed with a custom algorithm
- **Device auth**: OAuth Device Flow for token acquisition
- **Generated code**: Sync/async clients and models are partially code-generated from OpenAPI spec
## Go Project Structure (Target)
```
yandex-music-api-go/
├── go.mod
├── go.sum
├── cmd/
│ └── examples/ # Example applications
├── internal/
│ ├── client/ # Core HTTP client
│ │ ├── client.go # Main Client struct
│ │ ├── request.go # Request building/signing
│ │ └── auth.go # Device auth, token handling
│ ├── models/ # Data models (Track, Album, Artist, etc.)
│ │ ├── base.go # BaseModel interface
│ │ ├── track.go
│ │ ├── album.go
│ │ └── ...
│ ├── ynison/ # Ynison WebSocket client
│ │ ├── client.go
│ │ └── proto/ # Protobuf-generated Go code
│ └── utils/
│ ├── json.go # JSON handling
│ └── schema.go # Schema mismatch handling
├── yandexmusic/ # Public API package (re-exports)
│ ├── client.go # Public Client interface
│ └── models.go # Public model types
└── tests/ # Integration tests
```
## Development Commands
### Go Toolchain
```bash
# Initialize module (run once)
go mod init github.com/your-org/yandex-music-api-go
# Download dependencies
go mod tidy
# Build
go build ./...
# Run tests
go test ./...
# Run tests with coverage
go test -cover ./...
# Run single test
go test -v -run TestName ./path/to/package
# Lint (requires golangci-lint)
golangci-lint run
# Format
go fmt ./...
# Vet
go vet ./...
# Generate code (if using code generation)
go generate ./...
```
### Protobuf (for Ynison)
```bash
# Install protoc and plugins
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
go install github.com/bufbuild/buf/cmd/buf@latest # optional, for buf.yaml workflow
# Generate from .proto files
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
internal/ynison/proto/*.proto
# Or with buf
buf generate
```
## Key Implementation Notes
### Request Signing
The Python library signs requests with a custom algorithm. Port this exactly from `yandex_music/utils/request.py`:
- Request parameters are sorted and concatenated
- Signed with a secret key
- Signature added as `sign` parameter
### JSON Handling
Go's standard `encoding/json` is fast enough. No need for multiple backends. Use `json.RawMessage` for flexible fields.
### Schema Mismatch Handling
Port the Python `SchemaMismatch` logic:
- Track unknown fields in API responses
- Configurable: ignore, warn, or strict (error)
- Callback support for custom handling (e.g., Sentry)
### Ynison Protocol
- Uses WebSocket with protobuf messages
- Python uses `betterproto` for protobuf; Go uses standard `protobuf` module
- Port the message types from `yandex_music/ynison/models/`
### Model Structure
Follow Python patterns:
```go
// Base interface for all models
type Model interface {
// DeJSON deserializes from JSON
DeJSON(data []byte, client *Client) (Model, error)
// DeList deserializes a list
DeList(data []byte, client *Client) ([]Model, error)
}
```
### Client Design
- Single `Client` struct with methods (not mixins - Go doesn't have them)
- Group methods by feature using separate files: `client_tracks.go`, `client_albums.go`, etc.
- Support both sync and async (context-based) patterns
## Testing Strategy
- Unit tests for models (de/serialization)
- Integration tests against mock server (use `httptest`)
- Test schema mismatch handling
- Test request signing
## License
LGPL-3.0-only (same as Python library)
## References
- Python library: `yandex-music-api-python/`
- OpenAPI spec: https://ym.marshal.dev/api/openapi.json
- Documentation: https://ym.marshal.dev/
- Telegram: https://t.me/yandex_music_api
+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 {
+10 -8
View File
@@ -1,3 +1,4 @@
// Package client provides methods for interacting with Yandex Music API endpoints.
package client
import (
@@ -6,7 +7,7 @@ import (
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/models"
)
// GetAccountSettings gets account settings (current user's settings from /account/settings)
// GetAccountSettings gets account settings (current user's settings from /account/settings).
func (c *Client) GetAccountSettings(ctx context.Context) (*models.UserSettings, error) {
var result models.UserSettings
if err := c.request.Get(ctx, "/account/settings", nil, &result); err != nil {
@@ -15,7 +16,7 @@ func (c *Client) GetAccountSettings(ctx context.Context) (*models.UserSettings,
return &result, nil
}
// UpdateAccountSettings updates account settings
// UpdateAccountSettings updates account settings (PUT /account/settings).
func (c *Client) UpdateAccountSettings(ctx context.Context, settings *models.AccountSettings) (*models.AccountSettings, error) {
var result models.AccountSettings
if err := c.request.Put(ctx, "/account/settings", settings, &result); err != nil {
@@ -24,7 +25,7 @@ func (c *Client) UpdateAccountSettings(ctx context.Context, settings *models.Acc
return &result, nil
}
// GetSubscription gets user subscription info
// GetSubscription gets user subscription info (GET /account/subscription).
func (c *Client) GetSubscription(ctx context.Context) (*models.Subscription, error) {
var result models.Subscription
if err := c.request.Get(ctx, "/account/subscription", nil, &result); err != nil {
@@ -33,7 +34,7 @@ func (c *Client) GetSubscription(ctx context.Context) (*models.Subscription, err
return &result, nil
}
// GetPlusInfo gets Plus subscription info
// GetPlusInfo gets Plus subscription info (GET /account/plus).
func (c *Client) GetPlusInfo(ctx context.Context) (*models.PlusInfo, error) {
var result models.PlusInfo
if err := c.request.Get(ctx, "/account/plus", nil, &result); err != nil {
@@ -42,7 +43,7 @@ func (c *Client) GetPlusInfo(ctx context.Context) (*models.PlusInfo, error) {
return &result, nil
}
// GetPromoCodeStatus gets promo code status
// GetPromoCodeStatus gets promo code status (GET /promo-code-status).
func (c *Client) GetPromoCodeStatus(ctx context.Context, code string) (*models.PromoCodeStatus, error) {
params := map[string]string{
"code": code,
@@ -54,7 +55,7 @@ func (c *Client) GetPromoCodeStatus(ctx context.Context, code string) (*models.P
return &result, nil
}
// ActivatePromoCode activates a promo code
// ActivatePromoCode activates a promo code (POST /promo-code/activate).
func (c *Client) ActivatePromoCode(ctx context.Context, code string) (*models.PromoCodeStatus, error) {
params := map[string]string{
"code": code,
@@ -66,7 +67,7 @@ func (c *Client) ActivatePromoCode(ctx context.Context, code string) (*models.Pr
return &result, nil
}
// GetUserSettings gets user settings
// GetUserSettings gets user settings for a specific user (GET /user/settings).
func (c *Client) GetUserSettings(ctx context.Context, userID string) (*models.UserSettings, error) {
params := map[string]string{
"user_id": userID,
@@ -78,7 +79,8 @@ func (c *Client) GetUserSettings(ctx context.Context, userID string) (*models.Us
return &result, nil
}
// GetAccountStatus fetches account status
// GetAccountStatus fetches account status (GET /account/status).
// It returns the account status including account info and Plus subscription status.
func (c *Client) GetAccountStatus(ctx context.Context) (*Status, error) {
var result Status
if err := c.request.Get(ctx, "/account/status", nil, &result); err != nil {
+28 -3
View File
@@ -1,13 +1,16 @@
// Package client provides methods for interacting with Yandex Music API endpoints.
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"
)
// GetAlbums gets albums by IDs
// GetAlbums gets albums by their IDs (GET /tracks?album-ids=...).
// Returns a slice of Album pointers, or nil if no IDs are provided.
func (c *Client) GetAlbums(ctx context.Context, albumIDs []string) ([]*models.Album, error) {
if len(albumIDs) == 0 {
return nil, nil
@@ -30,7 +33,8 @@ func (c *Client) GetAlbums(ctx context.Context, albumIDs []string) ([]*models.Al
return albums, nil
}
// GetAlbum gets a single album by ID
// GetAlbum gets a single album by ID using GetAlbums.
// Returns an error if no album is found with the given ID.
func (c *Client) GetAlbum(ctx context.Context, albumID string) (*models.Album, error) {
albums, err := c.GetAlbums(ctx, []string{albumID})
if err != nil {
@@ -42,7 +46,8 @@ func (c *Client) GetAlbum(ctx context.Context, albumID string) (*models.Album, e
return albums[0], nil
}
// GetAlbumsWithTracks gets albums with tracks expanded
// GetAlbumsWithTracks gets albums with tracks expanded (GET /tracks?with-tracks=true).
// Returns a slice of Album pointers with their tracks populated.
func (c *Client) GetAlbumsWithTracks(ctx context.Context, albumIDs []string) ([]*models.Album, error) {
if len(albumIDs) == 0 {
return nil, nil
@@ -65,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
}
+156 -28
View File
@@ -1,3 +1,4 @@
// Package client provides methods for interacting with Yandex Music API endpoints.
package client
import (
@@ -8,7 +9,8 @@ import (
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/utils"
)
// GetArtists gets artists by IDs
// GetArtists gets artists by their IDs (GET /tracks?artist-ids=...).
// Returns a slice of Artist pointers, or nil if no IDs are provided.
func (c *Client) GetArtists(ctx context.Context, artistIDs []string) ([]*models.Artist, error) {
if len(artistIDs) == 0 {
return nil, nil
@@ -31,7 +33,8 @@ func (c *Client) GetArtists(ctx context.Context, artistIDs []string) ([]*models.
return artists, nil
}
// GetArtist gets a single artist by ID
// GetArtist gets a single artist by ID using GetArtists.
// Returns an error if no artist is found with the given ID.
func (c *Client) GetArtist(ctx context.Context, artistID string) (*models.Artist, error) {
artists, err := c.GetArtists(ctx, []string{artistID})
if err != nil {
@@ -43,47 +46,172 @@ func (c *Client) GetArtist(ctx context.Context, artistID string) (*models.Artist
return artists[0], nil
}
// GetArtistAlbums gets albums by an artist
func (c *Client) GetArtistAlbums(ctx context.Context, artistID string, page, pageSize int) ([]*models.Album, error) {
// GetArtistAlbums gets albums by an artist (GET /artists/{artist_id}/direct-albums).
// The page and pageSize parameters control pagination.
func (c *Client) GetArtistAlbums(ctx context.Context, artistID string, page, pageSize int) (*models.ArtistAlbums, error) {
params := map[string]string{
"artist-id": artistID,
"page": fmt.Sprintf("%d", page),
"page-size": fmt.Sprintf("%d", pageSize),
}
var result []models.Album
if err := c.request.Get(ctx, "/artists/albums", params, &result); err != nil {
var result models.ArtistAlbums
if err := c.request.Get(ctx, fmt.Sprintf("/artists/%s/direct-albums", artistID), params, &result); err != nil {
return nil, err
}
albums := make([]*models.Album, len(result))
for i := range result {
result[i].SetClient(c)
albums[i] = &result[i]
}
return albums, nil
result.SetClient(c)
return &result, nil
}
// GetArtistTracks gets tracks by an artist
func (c *Client) GetArtistTracks(ctx context.Context, artistID string, page, pageSize int) ([]*models.Track, error) {
// GetArtistTracks gets tracks by an artist (GET /artists/{artist_id}/tracks).
// The page and pageSize parameters control pagination.
func (c *Client) GetArtistTracks(ctx context.Context, artistID string, page, pageSize int) (*models.ArtistTracks, error) {
params := map[string]string{
"artist-id": artistID,
"page": fmt.Sprintf("%d", page),
"page-size": fmt.Sprintf("%d", pageSize),
}
var result []models.Track
if err := c.request.Get(ctx, "/artists/tracks", params, &result); err != nil {
var result models.ArtistTracks
if err := c.request.Get(ctx, fmt.Sprintf("/artists/%s/tracks", artistID), params, &result); err != nil {
return nil, err
}
tracks := make([]*models.Track, len(result))
for i := range result {
result[i].SetClient(c)
tracks[i] = &result[i]
}
return tracks, nil
result.SetClient(c)
return &result, nil
}
// Need to add fmt import
// "fmt"
// 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 -6
View File
@@ -13,7 +13,7 @@ import (
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/utils"
)
// DeviceCode represents the device authorization response
// DeviceCode represents the device authorization response from the Yandex OAuth device flow.
type DeviceCode struct {
DeviceCode string `json:"device_code"`
UserCode string `json:"user_code"`
@@ -22,7 +22,7 @@ type DeviceCode struct {
Interval int `json:"interval"`
}
// OAuthToken represents an OAuth token response
// OAuthToken represents an OAuth token response from the Yandex OAuth token endpoint.
type OAuthToken struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
@@ -30,7 +30,9 @@ type OAuthToken struct {
ExpiresIn int `json:"expires_in"`
}
// DeviceAuth performs OAuth Device Flow authentication
// DeviceAuth performs OAuth Device Flow authentication.
// It requests a device code, invokes the onCode callback with it, then polls for the token.
// The onCode callback receives the DeviceCode which contains the user_code and verification URL.
func (r *Request) DeviceAuth(ctx context.Context, onCode func(*DeviceCode)) (*OAuthToken, error) {
// Step 1: Request device code
deviceCode, err := r.requestDeviceCode(ctx)
@@ -47,7 +49,7 @@ func (r *Request) DeviceAuth(ctx context.Context, onCode func(*DeviceCode)) (*OA
return r.pollForToken(ctx, deviceCode)
}
// requestDeviceCode requests a device code from Yandex OAuth
// requestDeviceCode requests a device code from the Yandex OAuth device code endpoint.
func (r *Request) requestDeviceCode(ctx context.Context) (*DeviceCode, error) {
// Use Yandex OAuth endpoint for device auth
data := url.Values{}
@@ -85,7 +87,8 @@ func (r *Request) requestDeviceCode(ctx context.Context) (*DeviceCode, error) {
return &deviceCode, nil
}
// pollForToken polls for the OAuth token
// pollForToken polls the Yandex OAuth token endpoint until the user authorizes the device code.
// It returns the OAuthToken once authorization is complete, or an error if the code expires or is denied.
func (r *Request) pollForToken(ctx context.Context, deviceCode *DeviceCode) (*OAuthToken, error) {
interval := deviceCode.Interval
if interval == 0 {
@@ -155,7 +158,8 @@ func (r *Request) pollForToken(ctx context.Context, deviceCode *DeviceCode) (*OA
}
}
// RefreshToken refreshes an access token using a refresh token
// RefreshToken refreshes an access token using the provided refresh token.
// It makes a POST request to the Yandex OAuth token endpoint with grant_type=refresh_token.
func (r *Request) RefreshToken(ctx context.Context, refreshToken string) (*OAuthToken, error) {
data := url.Values{}
data.Set("grant_type", "refresh_token")
+28 -20
View File
@@ -1,3 +1,4 @@
// Package client provides the core Yandex Music API client and related types.
package client
import (
@@ -9,7 +10,7 @@ import (
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/utils"
)
// ClientConfig holds configuration for the Client
// ClientConfig holds configuration for the Client.
type ClientConfig struct {
Token string
BaseURL string
@@ -22,7 +23,9 @@ type ClientConfig struct {
JSONBackend utils.JSONBackend
}
// Client is the main Yandex Music API client
// Client is the main Yandex Music API client.
// It provides methods for interacting with the Yandex Music API,
// including tracks, albums, artists, playlists, radio, and more.
type Client struct {
config ClientConfig
request *Request
@@ -31,7 +34,8 @@ type Client struct {
accountUID *int64
}
// NewClient creates a new Yandex Music client
// NewClient creates a new Yandex Music client with the given configuration.
// If BaseURL, Language, or Timeout are not set in the config, sensible defaults are used.
func NewClient(config ClientConfig) *Client {
if config.BaseURL == "" {
config.BaseURL = DefaultBaseURL
@@ -66,7 +70,8 @@ func NewClient(config ClientConfig) *Client {
}
}
// Init initializes the client by fetching account info
// Init initializes the client by fetching account info from the /account/status endpoint.
// It populates the client's internal account state (GetMe and GetAccountUID).
func (c *Client) Init(ctx context.Context) error {
status, err := c.GetAccountStatus(ctx)
if err != nil {
@@ -79,76 +84,79 @@ func (c *Client) Init(ctx context.Context) error {
return nil
}
// GetRequest returns the underlying request handler
// GetRequest returns the underlying request handler used by this client.
// The returned RequestInterface can be used to make raw API requests.
func (c *Client) GetRequest() models.RequestInterface {
return c.request
}
// GetToken returns the current token
// GetToken returns the current OAuth token used for authentication.
func (c *Client) GetToken() string {
return c.config.Token
}
// SetToken updates the token
// SetToken updates the OAuth token used for authentication.
// It also updates the underlying request handler's authorization header.
func (c *Client) SetToken(token string) {
c.config.Token = token
c.request.SetToken(token)
}
// GetDevice returns the device string
// GetDevice returns the device identifier string used in API requests.
func (c *Client) GetDevice() string {
return c.device
}
// GetMe returns the account status
// GetMe returns the account status fetched during Init, or nil if Init has not been called.
func (c *Client) GetMe() *Status {
return c.me
}
// GetAccountUID returns the account UID
// GetAccountUID returns the authenticated user's account UID, or nil if not available.
func (c *Client) GetAccountUID() *int64 {
return c.accountUID
}
// Context returns a context for requests (can be overridden for timeouts)
// Context returns a context for requests (can be overridden for timeouts).
// By default it returns context.Background().
func (c *Client) Context() context.Context {
return context.Background()
}
// WithContext returns a new client with a custom context
// WithContext returns a new client with a custom context.
// In Go, context is typically passed to each method rather than stored, so this returns the same client.
func (c *Client) WithContext(ctx context.Context) *Client {
// In Go, we typically pass context to each method rather than storing it
return c
}
// DeviceAuth performs OAuth Device Flow authentication
// DeviceAuth performs OAuth Device Flow authentication.
// The onCode callback is invoked with the device code so the user can authorize on a separate device.
func (c *Client) DeviceAuth(ctx context.Context, onCode func(*DeviceCode)) (*OAuthToken, error) {
return c.request.DeviceAuth(ctx, onCode)
}
// RefreshToken refreshes the access token
// RefreshToken refreshes the access token using the provided refresh token.
// It delegates to the underlying request handler.
func (c *Client) RefreshToken(ctx context.Context, refreshToken string) (*OAuthToken, error) {
return c.request.RefreshToken(ctx, refreshToken)
}
// Status represents account status
// Status represents account status returned by the /account/status endpoint.
type Status struct {
Account *Account `json:"account"`
// Other fields...
}
// Account represents a user account
// Account represents a user account.
type Account struct {
UID int64 `json:"uid"`
Login string `json:"login"`
DisplayName string `json:"display_name"`
// Other fields...
}
// Track represents a music track (minimal for now)
// Track represents a music track (minimal subset of fields).
type Track struct {
ID string `json:"id"`
Title string `json:"title"`
Duration int `json:"duration_ms"`
// Other fields...
}
+17 -10
View File
@@ -1,3 +1,4 @@
// Package client provides methods for interacting with Yandex Music API endpoints.
package client
import (
@@ -7,7 +8,7 @@ import (
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/models"
)
// GetClips gets clips
// GetClips gets clips with pagination (GET /clips).
func (c *Client) GetClips(ctx context.Context, page, pageSize int) ([]*models.Clip, error) {
params := map[string]string{
"page": "1",
@@ -33,29 +34,35 @@ func (c *Client) GetClips(ctx context.Context, page, pageSize int) ([]*models.Cl
return clips, nil
}
// GetClipsWillLike gets clips the user might like
// GetClipsWillLike gets clips the user might like (GET /clips/will/like).
func (c *Client) GetClipsWillLike(ctx context.Context) (*models.ClipsWillLike, error) {
var result models.ClipsWillLike
if err := c.request.Get(ctx, "/clips/will-like", nil, &result); err != nil {
if err := c.request.Get(ctx, "/clips/will/like", nil, &result); err != nil {
return nil, err
}
return &result, nil
}
// GetArtistClips gets clips by an artist
// GetArtistClips gets clips by an artist (GET /artists/{artistID}/blocks/artist-clips).
func (c *Client) GetArtistClips(ctx context.Context, artistID string) (*models.ArtistClipData, error) {
params := map[string]string{
"artist_id": artistID,
}
var result models.ArtistClipData
if err := c.request.Get(ctx, "/artists/clips", params, &result); err != nil {
if err := c.request.Get(ctx, fmt.Sprintf("/artists/%s/blocks/artist-clips", artistID), nil, &result); err != nil {
return nil, err
}
return &result, nil
}
// GetShots gets shots
// 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{
"page": "1",
+31 -24
View File
@@ -1,3 +1,4 @@
// Package client provides methods for interacting with Yandex Music API endpoints.
package client
import (
@@ -7,17 +8,17 @@ import (
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/models"
)
// GetConcerts gets concerts
// GetConcerts gets concerts with pagination (GET /concerts).
func (c *Client) GetConcerts(ctx context.Context, page, pageSize int) (*models.ConcertFeed, error) {
params := map[string]string{
"page": "1",
"page_size": "20",
}
if page > 0 {
params["page"] = fmt.Sprintf("%d", page)
params["page"] = fmt.Sprint(page)
}
if pageSize > 0 {
params["page_size"] = fmt.Sprintf("%d", pageSize)
params["page_size"] = fmt.Sprint(pageSize)
}
var result models.ConcertFeed
@@ -27,7 +28,7 @@ func (c *Client) GetConcerts(ctx context.Context, page, pageSize int) (*models.C
return &result, nil
}
// GetConcertFeed gets concert feed
// GetConcertFeed gets the concert feed (GET /concerts/feed).
func (c *Client) GetConcertFeed(ctx context.Context) (*models.ConcertFeed, error) {
var result models.ConcertFeed
if err := c.request.Get(ctx, "/concerts/feed", nil, &result); err != nil {
@@ -36,20 +37,16 @@ func (c *Client) GetConcertFeed(ctx context.Context) (*models.ConcertFeed, error
return &result, nil
}
// GetConcertInfo gets concert info
// GetConcertInfo gets concert info by ID (GET /concerts/{concertID}/info).
func (c *Client) GetConcertInfo(ctx context.Context, concertID string) (*models.ConcertInfo, error) {
params := map[string]string{
"concert_id": concertID,
}
var result models.ConcertInfo
if err := c.request.Get(ctx, "/concerts/info", params, &result); err != nil {
if err := c.request.Get(ctx, fmt.Sprintf("/concerts/%s/info", concertID), nil, &result); err != nil {
return nil, err
}
return &result, nil
}
// GetConcertLocations gets concert locations
// GetConcertLocations gets concert locations (GET /concerts/locations).
func (c *Client) GetConcertLocations(ctx context.Context) (*models.ConcertLocations, error) {
var result models.ConcertLocations
if err := c.request.Get(ctx, "/concerts/locations", nil, &result); err != nil {
@@ -58,21 +55,31 @@ func (c *Client) GetConcertLocations(ctx context.Context) (*models.ConcertLocati
return &result, nil
}
// GetConcertTabs gets concert tabs
func (c *Client) GetConcertTabs(ctx context.Context, concertID string) ([]*models.ConcertTabConfig, error) {
params := map[string]string{
"concert_id": concertID,
}
var result []models.ConcertTabConfig
if err := c.request.Get(ctx, "/concerts/tabs", params, &result); err != nil {
// GetConcertTabs gets concert tab configuration (GET /concerts/tab-config).
func (c *Client) GetConcertTabs(ctx context.Context) (*models.ConcertTabConfig, error) {
var result models.ConcertTabConfig
if err := c.request.Get(ctx, "/concerts/tab-config", nil, &result); err != nil {
return nil, err
}
return &result, nil
}
tabs := make([]*models.ConcertTabConfig, len(result))
for i := range result {
result[i].SetClient(c)
tabs[i] = &result[i]
// 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
}
return tabs, nil
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
}
+13 -11
View File
@@ -1,12 +1,14 @@
// Package client provides methods for interacting with Yandex Music API endpoints.
package client
import (
"context"
"fmt"
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/models"
)
// GetFeed gets the main feed
// GetFeed gets the main feed (GET /feed).
func (c *Client) GetFeed(ctx context.Context) (*models.Feed, error) {
var result models.Feed
if err := c.request.Get(ctx, "/feed", nil, &result); err != nil {
@@ -15,7 +17,7 @@ func (c *Client) GetFeed(ctx context.Context) (*models.Feed, error) {
return &result, nil
}
// GetFeedBlocks gets feed blocks
// GetFeedBlocks gets feed blocks (GET /feed/blocks).
func (c *Client) GetFeedBlocks(ctx context.Context) ([]*models.DayBlock, error) {
var result []models.DayBlock
if err := c.request.Get(ctx, "/feed/blocks", nil, &result); err != nil {
@@ -30,7 +32,7 @@ func (c *Client) GetFeedBlocks(ctx context.Context) ([]*models.DayBlock, error)
return blocks, nil
}
// GetFeedGeneratedPlaylist gets a generated playlist by ID from feed
// GetFeedGeneratedPlaylist gets a generated playlist by ID from the feed (GET /feed/generated-playlist).
func (c *Client) GetFeedGeneratedPlaylist(ctx context.Context, playlistID string) (*models.GeneratedPlaylist, error) {
params := map[string]string{
"playlist_id": playlistID,
@@ -44,7 +46,7 @@ func (c *Client) GetFeedGeneratedPlaylist(ctx context.Context, playlistID string
return &result, nil
}
// GetFeedDay gets a specific day from the feed
// GetFeedDay gets a specific day from the feed by date (GET /feed/day).
func (c *Client) GetFeedDay(ctx context.Context, date string) (*models.Day, error) {
params := map[string]string{
"date": date,
@@ -57,7 +59,7 @@ func (c *Client) GetFeedDay(ctx context.Context, date string) (*models.Day, erro
return &result, nil
}
// GetGeneratedPlaylists gets generated playlists
// GetGeneratedPlaylists gets generated playlists (GET /generated-playlists).
func (c *Client) GetGeneratedPlaylists(ctx context.Context) ([]*models.GeneratedPlaylist, error) {
var result []models.GeneratedPlaylist
if err := c.request.Get(ctx, "/generated-playlists", nil, &result); err != nil {
@@ -72,36 +74,36 @@ func (c *Client) GetGeneratedPlaylists(ctx context.Context) ([]*models.Generated
return playlists, nil
}
// GetLanding gets a landing page
// GetLanding gets a landing page by ID (GET /landing3/{landingID}).
func (c *Client) GetLanding(ctx context.Context, landingID string) (*models.Landing, error) {
params := map[string]string{
"landing_id": landingID,
}
var result models.Landing
if err := c.request.Get(ctx, "/landing", params, &result); err != nil {
if err := c.request.Get(ctx, fmt.Sprintf("/landing3/%s", landingID), params, &result); err != nil {
return nil, err
}
return &result, nil
}
// GetLandingList gets a list of landings
// GetLandingList gets a list of landings (GET /landing3/list).
func (c *Client) GetLandingList(ctx context.Context) (*models.LandingList, error) {
var result models.LandingList
if err := c.request.Get(ctx, "/landing/list", nil, &result); err != nil {
if err := c.request.Get(ctx, "/landing3/list", nil, &result); err != nil {
return nil, err
}
return &result, nil
}
// GetLandingBlocks gets landing blocks by block IDs
// GetLandingBlocks gets landing blocks by block IDs (GET /landing3/blocks).
func (c *Client) GetLandingBlocks(ctx context.Context, blockIDs []string) ([]*models.LandingBlock, error) {
params := map[string]string{
"blocks": joinStrings(blockIDs, ","),
}
var result []models.LandingBlock
if err := c.request.Get(ctx, "/landing/blocks", params, &result); err != nil {
if err := c.request.Get(ctx, "/landing3/blocks", params, &result); err != nil {
return nil, err
}
+4 -3
View File
@@ -1,3 +1,4 @@
// Package client provides methods for interacting with Yandex Music API endpoints.
package client
import (
@@ -7,7 +8,7 @@ import (
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/models"
)
// GetMusicHistory gets music history
// GetMusicHistory gets the user's music history (GET /music-history).
func (c *Client) GetMusicHistory(ctx context.Context) (*models.MusicHistory, error) {
var result models.MusicHistory
if err := c.request.Get(ctx, "/music-history", nil, &result); err != nil {
@@ -16,7 +17,7 @@ func (c *Client) GetMusicHistory(ctx context.Context) (*models.MusicHistory, err
return &result, nil
}
// GetMusicHistoryTabs gets music history tabs
// GetMusicHistoryTabs gets music history tabs (GET /music-history/tabs).
func (c *Client) GetMusicHistoryTabs(ctx context.Context) ([]*models.MusicHistoryTab, error) {
var result []models.MusicHistoryTab
if err := c.request.Get(ctx, "/music-history/tabs", nil, &result); err != nil {
@@ -31,7 +32,7 @@ func (c *Client) GetMusicHistoryTabs(ctx context.Context) ([]*models.MusicHistor
return tabs, nil
}
// GetMusicHistoryItems gets music history items
// GetMusicHistoryItems gets music history items for a tab (GET /music-history/items).
func (c *Client) GetMusicHistoryItems(ctx context.Context, tabID string, page, pageSize int) (*models.MusicHistoryItems, error) {
params := map[string]string{}
if tabID != "" {
+10 -17
View File
@@ -1,3 +1,4 @@
// Package client provides methods for interacting with Yandex Music API endpoints.
package client
import (
@@ -7,7 +8,7 @@ import (
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/models"
)
// GetLabels gets all labels
// GetLabels gets all labels (GET /labels).
func (c *Client) GetLabels(ctx context.Context) ([]*models.Label, error) {
var result []models.Label
if err := c.request.Get(ctx, "/labels", nil, &result); err != nil {
@@ -22,25 +23,19 @@ func (c *Client) GetLabels(ctx context.Context) ([]*models.Label, error) {
return labels, nil
}
// GetLabel gets a label by ID
// GetLabel gets a label by ID (GET /labels/{labelID}).
func (c *Client) GetLabel(ctx context.Context, labelID string) (*models.Label, error) {
params := map[string]string{
"label_id": labelID,
}
var result models.Label
if err := c.request.Get(ctx, "/labels", params, &result); err != nil {
if err := c.request.Get(ctx, fmt.Sprintf("/labels/%s", labelID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetLabelAlbums gets albums for a label
// GetLabelAlbums gets albums for a label (GET /labels/{labelID}/albums).
func (c *Client) GetLabelAlbums(ctx context.Context, labelID string, page, pageSize int) (*models.LabelAlbums, error) {
params := map[string]string{
"label_id": labelID,
}
params := map[string]string{}
if page > 0 {
params["page"] = fmt.Sprintf("%d", page)
}
@@ -49,17 +44,15 @@ func (c *Client) GetLabelAlbums(ctx context.Context, labelID string, page, pageS
}
var result models.LabelAlbums
if err := c.request.Get(ctx, "/labels/albums", params, &result); err != nil {
if err := c.request.Get(ctx, fmt.Sprintf("/labels/%s/albums", labelID), params, &result); err != nil {
return nil, err
}
return &result, nil
}
// GetLabelArtists gets artists for a label
// GetLabelArtists gets artists for a label (GET /labels/{labelID}/artists).
func (c *Client) GetLabelArtists(ctx context.Context, labelID string, page, pageSize int) (*models.LabelArtists, error) {
params := map[string]string{
"label_id": labelID,
}
params := map[string]string{}
if page > 0 {
params["page"] = fmt.Sprintf("%d", page)
}
@@ -68,7 +61,7 @@ func (c *Client) GetLabelArtists(ctx context.Context, labelID string, page, page
}
var result models.LabelArtists
if err := c.request.Get(ctx, "/labels/artists", params, &result); err != nil {
if err := c.request.Get(ctx, fmt.Sprintf("/labels/%s/artists", labelID), params, &result); err != nil {
return nil, err
}
return &result, nil
+33 -32
View File
@@ -1,3 +1,4 @@
// Package client provides methods for interacting with Yandex Music API endpoints.
package client
import (
@@ -7,82 +8,82 @@ import (
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/models"
)
// LikeTrack likes a track
// LikeTrack likes a track (POST /users/me/likes/tracks/add-multiple).
func (c *Client) LikeTrack(ctx context.Context, trackID string) error {
params := map[string]string{
"track_id": trackID,
"track-ids": trackID,
}
var result map[string]any
return c.request.Post(ctx, "/users/likes/tracks/add", params, &result)
return c.request.Post(ctx, "/users/me/likes/tracks/add-multiple", params, &result)
}
// UnlikeTrack unlikes a track
// UnlikeTrack unlikes a track (POST /users/me/likes/tracks/remove).
func (c *Client) UnlikeTrack(ctx context.Context, trackID string) error {
params := map[string]string{
"track_id": trackID,
"track-ids": trackID,
}
var result map[string]any
return c.request.Post(ctx, "/users/likes/tracks/remove", params, &result)
return c.request.Post(ctx, "/users/me/likes/tracks/remove", params, &result)
}
// LikeAlbum likes an album
// LikeAlbum likes an album (POST /users/me/likes/albums/add-multiple).
func (c *Client) LikeAlbum(ctx context.Context, albumID string) error {
params := map[string]string{
"album_id": albumID,
"album-ids": albumID,
}
var result map[string]any
return c.request.Post(ctx, "/users/likes/albums/add", params, &result)
return c.request.Post(ctx, "/users/me/likes/albums/add-multiple", params, &result)
}
// UnlikeAlbum unlikes an album
// UnlikeAlbum unlikes an album (POST /users/me/likes/albums/remove).
func (c *Client) UnlikeAlbum(ctx context.Context, albumID string) error {
params := map[string]string{
"album_id": albumID,
"album-ids": albumID,
}
var result map[string]any
return c.request.Post(ctx, "/users/likes/albums/remove", params, &result)
return c.request.Post(ctx, "/users/me/likes/albums/remove", params, &result)
}
// LikeArtist likes an artist
// LikeArtist likes an artist (POST /users/me/likes/artists/add-multiple).
func (c *Client) LikeArtist(ctx context.Context, artistID string) error {
params := map[string]string{
"artist_id": artistID,
"artist-ids": artistID,
}
var result map[string]any
return c.request.Post(ctx, "/users/likes/artists/add", params, &result)
return c.request.Post(ctx, "/users/me/likes/artists/add-multiple", params, &result)
}
// UnlikeArtist unlikes an artist
// UnlikeArtist unlikes an artist (POST /users/me/likes/artists/remove).
func (c *Client) UnlikeArtist(ctx context.Context, artistID string) error {
params := map[string]string{
"artist_id": artistID,
"artist-ids": artistID,
}
var result map[string]any
return c.request.Post(ctx, "/users/likes/artists/remove", params, &result)
return c.request.Post(ctx, "/users/me/likes/artists/remove", params, &result)
}
// LikePlaylist likes a playlist
// LikePlaylist likes a playlist (POST /users/me/likes/playlists/add-multiple).
func (c *Client) LikePlaylist(ctx context.Context, kind int) error {
params := map[string]string{
"kind": fmt.Sprintf("%d", kind),
"playlist-ids": fmt.Sprintf("%d", kind),
}
var result map[string]any
return c.request.Post(ctx, "/users/likes/playlists/add", params, &result)
return c.request.Post(ctx, "/users/me/likes/playlists/add-multiple", params, &result)
}
// UnlikePlaylist unlikes a playlist
// UnlikePlaylist unlikes a playlist (POST /users/me/likes/playlists/remove).
func (c *Client) UnlikePlaylist(ctx context.Context, kind int) error {
params := map[string]string{
"kind": fmt.Sprintf("%d", kind),
"playlist-ids": fmt.Sprintf("%d", kind),
}
var result map[string]any
return c.request.Post(ctx, "/users/likes/playlists/remove", params, &result)
return c.request.Post(ctx, "/users/me/likes/playlists/remove", params, &result)
}
// GetLikedTracks gets user's liked tracks
// GetLikedTracks gets the user's liked tracks (GET /users/me/likes/tracks).
func (c *Client) GetLikedTracks(ctx context.Context) ([]*models.Track, error) {
var result []models.Track
if err := c.request.Get(ctx, "/users/likes/tracks", nil, &result); err != nil {
if err := c.request.Get(ctx, "/users/me/likes/tracks", nil, &result); err != nil {
return nil, err
}
@@ -94,10 +95,10 @@ func (c *Client) GetLikedTracks(ctx context.Context) ([]*models.Track, error) {
return tracks, nil
}
// GetLikedAlbums gets user's liked albums
// GetLikedAlbums gets the user's liked albums (GET /users/me/likes/albums).
func (c *Client) GetLikedAlbums(ctx context.Context) ([]*models.Album, error) {
var result []models.Album
if err := c.request.Get(ctx, "/users/likes/albums", nil, &result); err != nil {
if err := c.request.Get(ctx, "/users/me/likes/albums", nil, &result); err != nil {
return nil, err
}
@@ -109,10 +110,10 @@ func (c *Client) GetLikedAlbums(ctx context.Context) ([]*models.Album, error) {
return albums, nil
}
// GetLikedArtists gets user's liked artists
// GetLikedArtists gets the user's liked artists (GET /users/me/likes/artists).
func (c *Client) GetLikedArtists(ctx context.Context) ([]*models.Artist, error) {
var result []models.Artist
if err := c.request.Get(ctx, "/users/likes/artists", nil, &result); err != nil {
if err := c.request.Get(ctx, "/users/me/likes/artists", nil, &result); err != nil {
return nil, err
}
@@ -124,10 +125,10 @@ func (c *Client) GetLikedArtists(ctx context.Context) ([]*models.Artist, error)
return artists, nil
}
// GetLikedPlaylists gets user's liked playlists
// GetLikedPlaylists gets the user's liked playlists (GET /users/me/likes/playlists).
func (c *Client) GetLikedPlaylists(ctx context.Context) ([]*models.Playlist, error) {
var result []models.Playlist
if err := c.request.Get(ctx, "/users/likes/playlists", nil, &result); err != nil {
if err := c.request.Get(ctx, "/users/me/likes/playlists", nil, &result); err != nil {
return nil, err
}
+11 -10
View File
@@ -1,3 +1,4 @@
// Package client provides methods for interacting with Yandex Music API endpoints.
package client
import (
@@ -7,7 +8,7 @@ import (
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/models"
)
// GetMetatags gets all metatags
// GetMetatags gets all metatags (GET /metatags).
func (c *Client) GetMetatags(ctx context.Context) ([]*models.Metatag, error) {
var result []models.Metatag
if err := c.request.Get(ctx, "/metatags", nil, &result); err != nil {
@@ -22,7 +23,7 @@ func (c *Client) GetMetatags(ctx context.Context) ([]*models.Metatag, error) {
return metatags, nil
}
// GetMetatagTree gets metatag tree
// GetMetatagTree gets the metatag tree (GET /metatags/tree).
func (c *Client) GetMetatagTree(ctx context.Context) (*models.MetatagTree, error) {
var result models.MetatagTree
if err := c.request.Get(ctx, "/metatags/tree", nil, &result); err != nil {
@@ -31,7 +32,7 @@ func (c *Client) GetMetatagTree(ctx context.Context) (*models.MetatagTree, error
return &result, nil
}
// GetMetatagArtists gets artists for a metatag
// GetMetatagArtists gets artists for a metatag (GET /metatags/artists).
func (c *Client) GetMetatagArtists(ctx context.Context, metatagID string, page, pageSize int) (*models.MetatagArtists, error) {
params := map[string]string{
"metatag_id": metatagID,
@@ -50,7 +51,7 @@ func (c *Client) GetMetatagArtists(ctx context.Context, metatagID string, page,
return &result, nil
}
// GetMetatagAlbums gets albums for a metatag
// GetMetatagAlbums gets albums for a metatag (GET /metatags/albums).
func (c *Client) GetMetatagAlbums(ctx context.Context, metatagID string, page, pageSize int) (*models.MetatagAlbums, error) {
params := map[string]string{
"metatag_id": metatagID,
@@ -69,7 +70,7 @@ func (c *Client) GetMetatagAlbums(ctx context.Context, metatagID string, page, p
return &result, nil
}
// GetMetatagPlaylists gets playlists for a metatag
// GetMetatagPlaylists gets playlists for a metatag (GET /metatags/playlists).
func (c *Client) GetMetatagPlaylists(ctx context.Context, metatagID string, page, pageSize int) (*models.MetatagPlaylists, error) {
params := map[string]string{
"metatag_id": metatagID,
@@ -88,7 +89,7 @@ func (c *Client) GetMetatagPlaylists(ctx context.Context, metatagID string, page
return &result, nil
}
// GetGenres gets all genres
// GetGenres gets all genres (GET /genres).
func (c *Client) GetGenres(ctx context.Context) ([]*models.Genre, error) {
var result []models.Genre
if err := c.request.Get(ctx, "/genres", nil, &result); err != nil {
@@ -103,7 +104,7 @@ func (c *Client) GetGenres(ctx context.Context) ([]*models.Genre, error) {
return genres, nil
}
// GetCharts gets charts
// GetCharts gets all charts (GET /charts).
func (c *Client) GetCharts(ctx context.Context) ([]*models.Chart, error) {
var result []models.Chart
if err := c.request.Get(ctx, "/charts", nil, &result); err != nil {
@@ -118,7 +119,7 @@ func (c *Client) GetCharts(ctx context.Context) ([]*models.Chart, error) {
return charts, nil
}
// GetChartInfo gets chart info
// GetChartInfo gets chart info for a chart ID (GET /charts/info).
func (c *Client) GetChartInfo(ctx context.Context, chartID string) (*models.ChartInfo, error) {
params := map[string]string{
"chart_id": chartID,
@@ -131,7 +132,7 @@ func (c *Client) GetChartInfo(ctx context.Context, chartID string) (*models.Char
return &result, nil
}
// GetChartItems gets chart items
// GetChartItems gets chart items for a chart ID (GET /charts/items).
func (c *Client) GetChartItems(ctx context.Context, chartID string, page, pageSize int) (*models.ChartItems, error) {
params := map[string]string{
"chart_id": chartID,
@@ -150,7 +151,7 @@ func (c *Client) GetChartItems(ctx context.Context, chartID string, page, pageSi
return &result, nil
}
// GetChartMenu gets chart menu
// GetChartMenu gets chart menu for a chart ID (GET /charts/menu).
func (c *Client) GetChartMenu(ctx context.Context, chartID string) (*models.ChartInfoMenu, error) {
params := map[string]string{
"chart_id": chartID,
+5 -4
View File
@@ -1,3 +1,4 @@
// Package client provides methods for interacting with Yandex Music API endpoints.
package client
import (
@@ -6,7 +7,7 @@ import (
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/models"
)
// GetPins gets user's pins
// GetPins gets user's pins (GET /pins).
func (c *Client) GetPins(ctx context.Context) (*models.PinsList, error) {
var result models.PinsList
if err := c.request.Get(ctx, "/pins", nil, &result); err != nil {
@@ -15,7 +16,7 @@ func (c *Client) GetPins(ctx context.Context) (*models.PinsList, error) {
return &result, nil
}
// AddPin adds a pin
// AddPin adds a pin (POST /pins).
func (c *Client) AddPin(ctx context.Context, pinType, entityID string) (*models.Pin, error) {
params := map[string]string{
"type": pinType,
@@ -29,7 +30,7 @@ func (c *Client) AddPin(ctx context.Context, pinType, entityID string) (*models.
return &result, nil
}
// RemovePin removes a pin
// RemovePin removes a pin by ID (DELETE /pins).
func (c *Client) RemovePin(ctx context.Context, pinID string) error {
params := map[string]string{
"pin_id": pinID,
@@ -38,7 +39,7 @@ func (c *Client) RemovePin(ctx context.Context, pinID string) error {
return c.request.Delete(ctx, "/pins", params, &result)
}
// GetPresaves gets user's presaves
// GetPresaves gets user's presaves (GET /presaves).
func (c *Client) GetPresaves(ctx context.Context) (*models.Presaves, error) {
var result models.Presaves
if err := c.request.Get(ctx, "/presaves", nil, &result); err != nil {
+19 -15
View File
@@ -1,3 +1,4 @@
// Package client provides methods for interacting with Yandex Music API endpoints.
package client
import (
@@ -8,7 +9,8 @@ import (
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/utils"
)
// GetPlaylists gets playlists by IDs (kinds)
// GetPlaylists gets playlists by their kind IDs (GET /playlists).
// Returns a slice of Playlist pointers, or nil if no kinds are provided.
func (c *Client) GetPlaylists(ctx context.Context, kinds []int) ([]*models.Playlist, error) {
if len(kinds) == 0 {
return nil, nil
@@ -40,7 +42,8 @@ func (c *Client) GetPlaylists(ctx context.Context, kinds []int) ([]*models.Playl
return playlists, nil
}
// GetPlaylist gets a single playlist by kind
// GetPlaylist gets a single playlist by kind using GetPlaylists.
// Returns an error if no playlist is found with the given kind.
func (c *Client) GetPlaylist(ctx context.Context, kind int) (*models.Playlist, error) {
playlists, err := c.GetPlaylists(ctx, []int{kind})
if err != nil {
@@ -52,14 +55,10 @@ func (c *Client) GetPlaylist(ctx context.Context, kind int) (*models.Playlist, e
return playlists[0], nil
}
// GetUserPlaylists gets playlists for a user
// GetUserPlaylists gets playlists for a user (GET /users/{userID}/playlists).
func (c *Client) GetUserPlaylists(ctx context.Context, userID string) ([]*models.Playlist, error) {
params := map[string]string{
"user-id": userID,
}
var result []models.Playlist
if err := c.request.Get(ctx, "/users/playlists", params, &result); err != nil {
if err := c.request.Get(ctx, fmt.Sprintf("/users/%s/playlists", userID), nil, &result); err != nil {
return nil, err
}
@@ -71,7 +70,8 @@ func (c *Client) GetUserPlaylists(ctx context.Context, userID string) ([]*models
return playlists, nil
}
// CreatePlaylist creates a new playlist
// CreatePlaylist creates a new playlist (POST /users/me/playlists).
// The visibility parameter should be "public" or "private".
func (c *Client) CreatePlaylist(ctx context.Context, title string, visibility string) (*models.Playlist, error) {
params := map[string]string{
"title": title,
@@ -86,7 +86,8 @@ func (c *Client) CreatePlaylist(ctx context.Context, title string, visibility st
return &result, nil
}
// EditPlaylist edits an existing playlist
// EditPlaylist edits an existing playlist (PUT /users/me/playlists).
// Only non-empty parameters are updated.
func (c *Client) EditPlaylist(ctx context.Context, kind int, title, description, visibility string) (*models.Playlist, error) {
params := map[string]string{
"kind": fmt.Sprintf("%d", kind),
@@ -109,7 +110,7 @@ func (c *Client) EditPlaylist(ctx context.Context, kind int, title, description,
return &result, nil
}
// DeletePlaylist deletes a playlist
// DeletePlaylist deletes a playlist (DELETE /users/me/playlists).
func (c *Client) DeletePlaylist(ctx context.Context, kind int) error {
params := map[string]string{
"kind": fmt.Sprintf("%d", kind),
@@ -118,7 +119,8 @@ func (c *Client) DeletePlaylist(ctx context.Context, kind int) error {
return c.request.Delete(ctx, "/users/me/playlists", params, &result)
}
// AddTracksToPlaylist adds tracks to a playlist
// AddTracksToPlaylist adds tracks to a playlist (POST /users/me/playlists/tracks).
// The revision parameter is the current playlist revision for optimistic concurrency.
func (c *Client) AddTracksToPlaylist(ctx context.Context, kind int, trackIDs []string, revision int) (*models.Playlist, error) {
if len(trackIDs) == 0 {
return nil, utils.NewYandexMusicError("no tracks provided", utils.ErrBadRequest)
@@ -146,7 +148,8 @@ func (c *Client) AddTracksToPlaylist(ctx context.Context, kind int, trackIDs []s
return &result, nil
}
// RemoveTracksFromPlaylist removes tracks from a playlist
// RemoveTracksFromPlaylist removes tracks from a playlist (DELETE /users/me/playlists/tracks).
// The revision parameter is the current playlist revision for optimistic concurrency.
func (c *Client) RemoveTracksFromPlaylist(ctx context.Context, kind int, trackIDs []string, revision int) (*models.Playlist, error) {
if len(trackIDs) == 0 {
return nil, utils.NewYandexMusicError("no tracks provided", utils.ErrBadRequest)
@@ -174,7 +177,8 @@ func (c *Client) RemoveTracksFromPlaylist(ctx context.Context, kind int, trackID
return &result, nil
}
// ReorderPlaylistTracks reorders tracks in a playlist
// ReorderPlaylistTracks reorders tracks in a playlist (PUT /users/me/playlists/tracks/reorder).
// The from and to parameters specify the position range to move.
func (c *Client) ReorderPlaylistTracks(ctx context.Context, kind int, from, to, revision int) (*models.Playlist, error) {
params := map[string]string{
"kind": fmt.Sprintf("%d", kind),
@@ -191,7 +195,7 @@ func (c *Client) ReorderPlaylistTracks(ctx context.Context, kind int, from, to,
return &result, nil
}
// GetPlaylistRecommendations gets playlist recommendations
// GetPlaylistRecommendations gets playlist recommendations based on a playlist kind (GET /playlists/recommendations).
func (c *Client) GetPlaylistRecommendations(ctx context.Context, kind int) ([]*models.Playlist, error) {
params := map[string]string{
"kind": fmt.Sprintf("%d", kind),
+199 -208
View File
@@ -7,213 +7,10 @@ import (
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/models"
)
// GetQueue gets the current queue
func (c *Client) GetQueue(ctx context.Context) (*models.Queue, error) {
var result models.Queue
if err := c.request.Get(ctx, "/queue", nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// CreateQueue creates a new queue
func (c *Client) CreateQueue(ctx context.Context, trackID string, queueType string) (*models.Queue, error) {
params := map[string]string{
"track_id": trackID,
"queue_type": queueType, // "radio", "album", "playlist", etc.
}
var result models.Queue
if err := c.request.Post(ctx, "/queue", params, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetQueueItem gets a queue item
func (c *Client) GetQueueItem(ctx context.Context, queueID string) (*models.QueueItem, error) {
params := map[string]string{
"queue_id": queueID,
}
var result models.QueueItem
if err := c.request.Get(ctx, "/queue/item", params, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// AddToQueue adds a track to the queue
func (c *Client) AddToQueue(ctx context.Context, queueID, trackID string, position int) (*models.Queue, error) {
params := map[string]string{
"queue_id": queueID,
"track_id": trackID,
"position": fmt.Sprintf("%d", position),
}
var result models.Queue
if err := c.request.Post(ctx, "/queue/add", params, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// RemoveFromQueue removes a track from the queue
func (c *Client) RemoveFromQueue(ctx context.Context, queueID, trackID string) (*models.Queue, error) {
params := map[string]string{
"queue_id": queueID,
"track_id": trackID,
}
var result models.Queue
if err := c.request.Delete(ctx, "/queue/remove", params, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// ReorderQueue reorders the queue
func (c *Client) ReorderQueue(ctx context.Context, queueID string, from, to int) (*models.Queue, error) {
params := map[string]string{
"queue_id": queueID,
"from": fmt.Sprintf("%d", from),
"to": fmt.Sprintf("%d", to),
}
var result models.Queue
if err := c.request.Put(ctx, "/queue/reorder", params, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// ClearQueue clears the queue
func (c *Client) ClearQueue(ctx context.Context, queueID string) error {
params := map[string]string{
"queue_id": queueID,
}
var result map[string]any
return c.request.Delete(ctx, "/queue/clear", params, &result)
}
// NextTrack moves to the next track in the queue
func (c *Client) NextTrack(ctx context.Context, queueID string) (*models.Queue, error) {
params := map[string]string{
"queue_id": queueID,
}
var result models.Queue
if err := c.request.Post(ctx, "/queue/next", params, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// PreviousTrack moves to the previous track in the queue
func (c *Client) PreviousTrack(ctx context.Context, queueID string) (*models.Queue, error) {
params := map[string]string{
"queue_id": queueID,
}
var result models.Queue
if err := c.request.Post(ctx, "/queue/prev", params, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// Seek seeks to a specific position in the current track (in milliseconds)
func (c *Client) Seek(ctx context.Context, queueID string, positionMs int) (*models.Queue, error) {
params := map[string]string{
"queue_id": queueID,
"position": fmt.Sprintf("%d", positionMs),
}
var result models.Queue
if err := c.request.Post(ctx, "/queue/seek", params, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// SetRepeatMode sets the repeat mode for the queue
// mode can be "off", "one", or "all"
func (c *Client) SetRepeatMode(ctx context.Context, queueID, mode string) (*models.Queue, error) {
params := map[string]string{
"queue_id": queueID,
"mode": mode,
}
var result models.Queue
if err := c.request.Post(ctx, "/queue/repeat", params, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// SetShuffle enables or disables shuffle for the queue
func (c *Client) SetShuffle(ctx context.Context, queueID string, enabled bool) (*models.Queue, error) {
params := map[string]string{
"queue_id": queueID,
"enabled": fmt.Sprintf("%t", enabled),
}
var result models.Queue
if err := c.request.Post(ctx, "/queue/shuffle", params, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// SetVolume sets the volume for the queue (0-100)
func (c *Client) SetVolume(ctx context.Context, queueID string, volume int) (*models.Queue, error) {
params := map[string]string{
"queue_id": queueID,
"volume": fmt.Sprintf("%d", volume),
}
var result models.Queue
if err := c.request.Post(ctx, "/queue/volume", params, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetCurrentTrack gets the currently playing track from the queue
func (c *Client) GetCurrentTrack(ctx context.Context, queueID string) (*models.QueueItem, error) {
params := map[string]string{
"queue_id": queueID,
}
var result models.QueueItem
if err := c.request.Get(ctx, "/queue/current", params, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetQueueHistory gets the queue history (recently played tracks)
func (c *Client) GetQueueHistory(ctx context.Context, queueID string, limit int) ([]*models.QueueItem, error) {
params := map[string]string{
"queue_id": queueID,
"limit": fmt.Sprintf("%d", limit),
}
// 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, "/queue/history", params, &result); err != nil {
if err := c.request.Get(ctx, "/queues", nil, &result); err != nil {
return nil, err
}
@@ -225,7 +22,201 @@ func (c *Client) GetQueueHistory(ctx context.Context, queueID string, limit int)
return items, nil
}
// GetPlayContexts gets play contexts
// GetQueue gets the current queue
func (c *Client) GetQueue(ctx context.Context) (*models.Queue, error) {
var result models.Queue
if err := c.request.Get(ctx, "/queues", nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// CreateQueue creates a new playback queue (POST /queues).
// The queueType parameter specifies the type of queue (e.g., "radio", "album", "playlist").
func (c *Client) CreateQueue(ctx context.Context, trackID string, queueType string) (*models.Queue, error) {
params := map[string]string{
"track_id": trackID,
"queue_type": queueType, // "radio", "album", "playlist", etc.
}
var result models.Queue
if err := c.request.Post(ctx, "/queues", params, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetQueueItem gets a queue item by queue ID (GET /queues/{queue_id}).
func (c *Client) GetQueueItem(ctx context.Context, queueID string) (*models.QueueItem, error) {
var result models.QueueItem
if err := c.request.Get(ctx, fmt.Sprintf("/queues/%s", queueID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// AddToQueue adds a track to the queue at the given position (POST /queues/{queue_id}/add).
func (c *Client) AddToQueue(ctx context.Context, queueID, trackID string, position int) (*models.Queue, error) {
params := map[string]string{
"track_id": trackID,
"position": fmt.Sprintf("%d", position),
}
var result models.Queue
if err := c.request.Post(ctx, fmt.Sprintf("/queues/%s/add", queueID), params, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// RemoveFromQueue removes a track from the queue (DELETE /queues/{queue_id}/remove).
func (c *Client) RemoveFromQueue(ctx context.Context, queueID, trackID string) (*models.Queue, error) {
params := map[string]string{
"track_id": trackID,
}
var result models.Queue
if err := c.request.Delete(ctx, fmt.Sprintf("/queues/%s/remove", queueID), params, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// ReorderQueue reorders items in the queue from one position to another (PUT /queues/{queue_id}/reorder).
func (c *Client) ReorderQueue(ctx context.Context, queueID string, from, to int) (*models.Queue, error) {
params := map[string]string{
"from": fmt.Sprintf("%d", from),
"to": fmt.Sprintf("%d", to),
}
var result models.Queue
if err := c.request.Put(ctx, fmt.Sprintf("/queues/%s/reorder", queueID), params, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// ClearQueue clears the queue (DELETE /queues/{queue_id}/clear).
func (c *Client) ClearQueue(ctx context.Context, queueID string) error {
var result map[string]any
return c.request.Delete(ctx, fmt.Sprintf("/queues/%s/clear", queueID), nil, &result)
}
// NextTrack moves to the next track in the queue (POST /queues/{queue_id}/next).
func (c *Client) NextTrack(ctx context.Context, queueID string) (*models.Queue, error) {
var result models.Queue
if err := c.request.Post(ctx, fmt.Sprintf("/queues/%s/next", queueID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// PreviousTrack moves to the previous track in the queue (POST /queues/{queue_id}/prev).
func (c *Client) PreviousTrack(ctx context.Context, queueID string) (*models.Queue, error) {
var result models.Queue
if err := c.request.Post(ctx, fmt.Sprintf("/queues/%s/prev", queueID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// Seek seeks to a specific position in the current track (POST /queues/{queue_id}/seek).
// The positionMs parameter is the position in milliseconds.
func (c *Client) Seek(ctx context.Context, queueID string, positionMs int) (*models.Queue, error) {
params := map[string]string{
"position": fmt.Sprintf("%d", positionMs),
}
var result models.Queue
if err := c.request.Post(ctx, fmt.Sprintf("/queues/%s/seek", queueID), params, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// SetRepeatMode sets the repeat mode for the queue (POST /queues/{queue_id}/repeat).
// The mode parameter can be "off", "one", or "all".
func (c *Client) SetRepeatMode(ctx context.Context, queueID, mode string) (*models.Queue, error) {
params := map[string]string{
"mode": mode,
}
var result models.Queue
if err := c.request.Post(ctx, fmt.Sprintf("/queues/%s/repeat", queueID), params, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// SetShuffle enables or disables shuffle for the queue (POST /queues/{queue_id}/shuffle).
func (c *Client) SetShuffle(ctx context.Context, queueID string, enabled bool) (*models.Queue, error) {
params := map[string]string{
"enabled": fmt.Sprintf("%t", enabled),
}
var result models.Queue
if err := c.request.Post(ctx, fmt.Sprintf("/queues/%s/shuffle", queueID), params, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// SetVolume sets the volume for the queue (POST /queues/{queue_id}/volume).
// The volume parameter should be between 0 and 100.
func (c *Client) SetVolume(ctx context.Context, queueID string, volume int) (*models.Queue, error) {
params := map[string]string{
"volume": fmt.Sprintf("%d", volume),
}
var result models.Queue
if err := c.request.Post(ctx, fmt.Sprintf("/queues/%s/volume", queueID), params, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetCurrentTrack gets the currently playing track from the queue (GET /queues/{queue_id}/current).
func (c *Client) GetCurrentTrack(ctx context.Context, queueID string) (*models.QueueItem, error) {
var result models.QueueItem
if err := c.request.Get(ctx, fmt.Sprintf("/queues/%s/current", queueID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetQueueHistory gets the queue history (recently played tracks) (GET /queues/{queue_id}/history).
func (c *Client) GetQueueHistory(ctx context.Context, queueID string, limit int) ([]*models.QueueItem, error) {
params := map[string]string{
"limit": fmt.Sprintf("%d", limit),
}
var result []models.QueueItem
if err := c.request.Get(ctx, fmt.Sprintf("/queues/%s/history", queueID), params, &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
}
// GetPlayContexts gets play contexts (GET /play-contexts).
func (c *Client) GetPlayContexts(ctx context.Context) ([]*models.PlayContext, error) {
var result []models.PlayContext
if err := c.request.Get(ctx, "/play-contexts", nil, &result); err != nil {
@@ -240,7 +231,7 @@ func (c *Client) GetPlayContexts(ctx context.Context) ([]*models.PlayContext, er
return contexts, nil
}
// GetPlayContextsData gets play contexts data
// GetPlayContextsData gets play contexts data (GET /play-contexts/data).
func (c *Client) GetPlayContextsData(ctx context.Context) (*models.PlayContextsData, error) {
var result models.PlayContextsData
if err := c.request.Get(ctx, "/play-contexts/data", nil, &result); err != nil {
+77 -34
View File
@@ -1,3 +1,4 @@
// Package client provides methods for interacting with Yandex Music API endpoints.
package client
import (
@@ -7,7 +8,7 @@ import (
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/models"
)
// GetRadioStations gets radio stations
// GetRadioStations gets radio stations (GET /radio/stations).
func (c *Client) GetRadioStations(ctx context.Context) ([]*models.RadioStation, error) {
var result []models.RadioStation
if err := c.request.Get(ctx, "/radio/stations", nil, &result); err != nil {
@@ -22,37 +23,31 @@ func (c *Client) GetRadioStations(ctx context.Context) ([]*models.RadioStation,
return stations, nil
}
// GetRadioStation gets a radio station by ID
// GetRadioStation gets a radio station by ID (GET /rotor/station/{stationID}/info).
func (c *Client) GetRadioStation(ctx context.Context, stationID string) (*models.RadioStation, error) {
params := map[string]string{
"station_id": stationID,
}
var result models.RadioStation
if err := c.request.Get(ctx, "/radio/station", params, &result); err != nil {
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
}
// GetStationTracks gets tracks for a radio station
// GetStationTracks gets tracks for a radio station (GET /rotor/station/{stationID}/tracks).
func (c *Client) GetStationTracks(ctx context.Context, stationID string, limit int) (*models.StationTracksResult, error) {
params := map[string]string{
"station_id": stationID,
}
params := map[string]string{}
if limit > 0 {
params["limit"] = fmt.Sprintf("%d", limit)
}
var result models.StationTracksResult
if err := c.request.Get(ctx, "/radio/station/tracks", params, &result); err != nil {
if err := c.request.Get(ctx, fmt.Sprintf("/rotor/station/%s/tracks", stationID), params, &result); err != nil {
return nil, err
}
return &result, nil
}
// GetRotorStations gets rotor stations (generative)
// GetRotorStations gets rotor stations (generative stations) (GET /rotor/stations).
func (c *Client) GetRotorStations(ctx context.Context) ([]*models.RotorStation, error) {
var result []models.RotorStation
if err := c.request.Get(ctx, "/rotor/stations", nil, &result); err != nil {
@@ -67,51 +62,41 @@ func (c *Client) GetRotorStations(ctx context.Context) ([]*models.RotorStation,
return stations, nil
}
// GetRotorStation gets a rotor station by ID
// GetRotorStation gets a rotor station by ID (GET /rotor/station/{stationID}/info).
func (c *Client) GetRotorStation(ctx context.Context, stationID string) (*models.RotorStation, error) {
params := map[string]string{
"station_id": stationID,
}
var result models.RotorStation
if err := c.request.Get(ctx, "/rotor/station", params, &result); err != nil {
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
}
// GetRotorStationTracks gets tracks for a rotor station
// GetRotorStationTracks gets tracks for a rotor station (GET /rotor/station/{stationID}/tracks).
func (c *Client) GetRotorStationTracks(ctx context.Context, stationID string, limit int) (*models.RotorStationTracksResult, error) {
params := map[string]string{
"station_id": stationID,
}
params := map[string]string{}
if limit > 0 {
params["limit"] = fmt.Sprintf("%d", limit)
}
var result models.RotorStationTracksResult
if err := c.request.Get(ctx, "/rotor/station/tracks", params, &result); err != nil {
if err := c.request.Get(ctx, fmt.Sprintf("/rotor/station/%s/tracks", stationID), params, &result); err != nil {
return nil, err
}
return &result, nil
}
// StartRotorSession starts a rotor session
// StartRotorSession starts a rotor session (POST /rotor/station/{stationID}/session/start).
func (c *Client) StartRotorSession(ctx context.Context, stationID string) (*models.RotorSession, error) {
params := map[string]string{
"station_id": stationID,
}
var result models.RotorSession
if err := c.request.Post(ctx, "/rotor/session/start", params, &result); err != nil {
if err := c.request.Post(ctx, fmt.Sprintf("/rotor/station/%s/session/start", stationID), nil, &result); err != nil {
return nil, err
}
result.SetClient(c)
return &result, nil
}
// GetRotorSession gets rotor session info
// GetRotorSession gets rotor session info by session ID (GET /rotor/session).
func (c *Client) GetRotorSession(ctx context.Context, sessionID string) (*models.RotorSession, error) {
params := map[string]string{
"session_id": sessionID,
@@ -125,14 +110,72 @@ func (c *Client) GetRotorSession(ctx context.Context, sessionID string) (*models
return &result, nil
}
// RotorSessionFeedback sends feedback for a rotor session track
// RotorSessionFeedback sends feedback for a rotor session track (POST /rotor/station/{stationID}/feedback).
// The feedbackType parameter can be "like", "dislike", or "skip".
func (c *Client) RotorSessionFeedback(ctx context.Context, sessionID, trackID, feedbackType string) error {
params := map[string]string{
"session_id": sessionID,
"track_id": trackID,
"feedback": feedbackType, // "like", "dislike", "skip"
}
var result map[string]any
return c.request.Post(ctx, "/rotor/session/feedback", params, &result)
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
}
+22 -13
View File
@@ -13,7 +13,7 @@ import (
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/utils"
)
// Default values
// Default values for the Yandex Music API client.
const (
DefaultBaseURL = "https://api.music.yandex.net"
DefaultTimeout = 5 * time.Second
@@ -21,7 +21,7 @@ const (
DefaultClientHeader = "YandexMusicAndroid/24023621"
)
// RequestConfig holds configuration for the request client
// RequestConfig holds configuration for the request client.
type RequestConfig struct {
BaseURL string
Token string
@@ -36,14 +36,16 @@ type RequestConfig struct {
OnSchemaMismatch utils.SchemaMismatchHandler
}
// Request handles HTTP requests to Yandex Music API
// Request handles HTTP requests to the Yandex Music API.
// It manages the HTTP client, authentication headers, and request signing.
type Request struct {
config RequestConfig
client *http.Client
schemaTracker *utils.SchemaMismatchTracker
}
// NewRequest creates a new Request instance
// NewRequest creates a new Request instance with the given configuration.
// It sets up the HTTP client, default headers, and authentication if a token is provided.
func NewRequest(config RequestConfig) *Request {
if config.BaseURL == "" {
config.BaseURL = DefaultBaseURL
@@ -92,7 +94,8 @@ func NewRequest(config RequestConfig) *Request {
return r
}
// SetToken updates the authorization token
// SetToken updates the authorization token used for API requests.
// When a token is provided, it sets the Authorization header to "OAuth <token>".
func (r *Request) SetToken(token string) {
r.config.Token = token
if token != "" {
@@ -102,33 +105,38 @@ func (r *Request) SetToken(token string) {
}
}
// SetLanguage sets the Accept-Language header
// SetLanguage sets the Accept-Language header for API requests.
func (r *Request) SetLanguage(lang string) {
r.config.Language = lang
r.config.Headers["Accept-Language"] = lang
}
// Get performs a GET request
// Get performs a GET request to the given path with query parameters.
// The response is unmarshaled into the result pointer.
func (r *Request) Get(ctx context.Context, path string, params map[string]string, result any) error {
return r.doRequest(ctx, "GET", path, params, nil, result)
}
// Post performs a POST request
// Post performs a POST request to the given path with a JSON body.
// The response is unmarshaled into the result pointer.
func (r *Request) Post(ctx context.Context, path string, body any, result any) error {
return r.doRequest(ctx, "POST", path, nil, body, result)
}
// Put performs a PUT request
// Put performs a PUT request to the given path with a JSON body.
// The response is unmarshaled into the result pointer.
func (r *Request) Put(ctx context.Context, path string, body any, result any) error {
return r.doRequest(ctx, "PUT", path, nil, body, result)
}
// Delete performs a DELETE request
// Delete performs a DELETE request to the given path with an optional JSON body.
// The response is unmarshaled into the result pointer.
func (r *Request) Delete(ctx context.Context, path string, body any, result any) error {
return r.doRequest(ctx, "DELETE", path, nil, body, result)
}
// doRequest performs the actual HTTP request
// doRequest performs the actual HTTP request to the Yandex Music API.
// It handles URL building, request signing, response parsing, and error handling.
func (r *Request) doRequest(ctx context.Context, method, path string, params map[string]string, body any, result any) error {
// Build URL
fullURL := r.config.BaseURL + path
@@ -209,7 +217,7 @@ func (r *Request) doRequest(ctx context.Context, method, path string, params map
return nil
}
// handleErrorResponse handles HTTP error responses
// handleErrorResponse handles HTTP error responses by mapping status codes to typed errors.
func (r *Request) handleErrorResponse(statusCode int, body []byte) error {
var msg string
apiResp, err := utils.DeJSON(body)
@@ -235,7 +243,8 @@ func (r *Request) handleErrorResponse(statusCode int, body []byte) error {
}
}
// Download downloads a file from a URL
// Download downloads a file from the given URL and returns the raw response body.
// It is used for downloading track files and lyrics text.
func (r *Request) Download(ctx context.Context, urlStr string) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, "GET", urlStr, nil)
if err != nil {
+12 -10
View File
@@ -1,3 +1,4 @@
// Package client provides methods for interacting with Yandex Music API endpoints.
package client
import (
@@ -7,7 +8,8 @@ import (
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/models"
)
// Search performs a multi-type search
// Search performs a multi-type search (GET /search).
// The searchType parameter specifies what to search for (e.g., "track", "album", "artist").
func (c *Client) Search(ctx context.Context, query string, searchType string, page, pageSize int) (*models.SearchResult, error) {
params := map[string]string{
"text": query,
@@ -46,7 +48,7 @@ func (c *Client) Search(ctx context.Context, query string, searchType string, pa
return &result, nil
}
// SearchTracks searches for tracks
// SearchTracks searches for tracks using Search with type "track".
func (c *Client) SearchTracks(ctx context.Context, query string, page, pageSize int) (*models.SearchTracksResult, error) {
result, err := c.Search(ctx, query, "track", page, pageSize)
if err != nil {
@@ -55,7 +57,7 @@ func (c *Client) SearchTracks(ctx context.Context, query string, page, pageSize
return result.Tracks, nil
}
// SearchAlbums searches for albums
// SearchAlbums searches for albums using Search with type "album".
func (c *Client) SearchAlbums(ctx context.Context, query string, page, pageSize int) (*models.SearchAlbumsResult, error) {
result, err := c.Search(ctx, query, "album", page, pageSize)
if err != nil {
@@ -64,7 +66,7 @@ func (c *Client) SearchAlbums(ctx context.Context, query string, page, pageSize
return result.Albums, nil
}
// SearchArtists searches for artists
// SearchArtists searches for artists using Search with type "artist".
func (c *Client) SearchArtists(ctx context.Context, query string, page, pageSize int) (*models.SearchArtistsResult, error) {
result, err := c.Search(ctx, query, "artist", page, pageSize)
if err != nil {
@@ -73,7 +75,7 @@ func (c *Client) SearchArtists(ctx context.Context, query string, page, pageSize
return result.Artists, nil
}
// SearchPlaylists searches for playlists
// SearchPlaylists searches for playlists using Search with type "playlist".
func (c *Client) SearchPlaylists(ctx context.Context, query string, page, pageSize int) (*models.SearchPlaylistsResult, error) {
result, err := c.Search(ctx, query, "playlist", page, pageSize)
if err != nil {
@@ -82,7 +84,7 @@ func (c *Client) SearchPlaylists(ctx context.Context, query string, page, pageSi
return result.Playlists, nil
}
// SearchVideos searches for videos
// SearchVideos searches for videos using Search with type "video".
func (c *Client) SearchVideos(ctx context.Context, query string, page, pageSize int) (*models.SearchVideosResult, error) {
result, err := c.Search(ctx, query, "video", page, pageSize)
if err != nil {
@@ -91,7 +93,7 @@ func (c *Client) SearchVideos(ctx context.Context, query string, page, pageSize
return result.Videos, nil
}
// SearchUsers searches for users
// SearchUsers searches for users using Search with type "user".
func (c *Client) SearchUsers(ctx context.Context, query string, page, pageSize int) (*models.SearchUsersResult, error) {
result, err := c.Search(ctx, query, "user", page, pageSize)
if err != nil {
@@ -100,7 +102,7 @@ func (c *Client) SearchUsers(ctx context.Context, query string, page, pageSize i
return result.Users, nil
}
// SearchSuggestions gets search suggestions
// SearchSuggestions gets search suggestions for a query (GET /search/suggest?type=suggest).
func (c *Client) SearchSuggestions(ctx context.Context, query string) ([]string, error) {
params := map[string]string{
"text": query,
@@ -116,7 +118,7 @@ func (c *Client) SearchSuggestions(ctx context.Context, query string) ([]string,
return result.Suggestions, nil
}
// SearchBanners gets search banners
// SearchBanners gets search banners for a query (GET /search/banner?type=banner).
func (c *Client) SearchBanners(ctx context.Context, query string) ([]models.SearchBanner, error) {
params := map[string]string{
"text": query,
@@ -132,7 +134,7 @@ func (c *Client) SearchBanners(ctx context.Context, query string) ([]models.Sear
return result.Banners, nil
}
// SearchTags gets search tags
// SearchTags gets search tags for a query (GET /search/tag?type=tag).
func (c *Client) SearchTags(ctx context.Context, query string) ([]models.Tag, error) {
params := map[string]string{
"text": query,
+8 -7
View File
@@ -1,3 +1,4 @@
// Package client provides methods for interacting with Yandex Music API endpoints.
package client
import (
@@ -6,7 +7,7 @@ import (
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/models"
)
// GetSettings gets user settings
// GetSettings gets user settings (GET /account/settings).
func (c *Client) GetSettings(ctx context.Context) (*models.AccountSettings, error) {
var result models.AccountSettings
if err := c.request.Get(ctx, "/account/settings", nil, &result); err != nil {
@@ -15,7 +16,7 @@ func (c *Client) GetSettings(ctx context.Context) (*models.AccountSettings, erro
return &result, nil
}
// UpdateSettings updates user settings
// UpdateSettings updates user settings (PUT /account/settings).
func (c *Client) UpdateSettings(ctx context.Context, settings *models.AccountSettings) (*models.AccountSettings, error) {
var result models.AccountSettings
if err := c.request.Put(ctx, "/account/settings", settings, &result); err != nil {
@@ -24,7 +25,7 @@ func (c *Client) UpdateSettings(ctx context.Context, settings *models.AccountSet
return &result, nil
}
// GetExperiments gets experiments
// GetExperiments gets experiments (GET /experiments).
func (c *Client) GetExperiments(ctx context.Context) (*models.Experiments, error) {
var result models.Experiments
if err := c.request.Get(ctx, "/experiments", nil, &result); err != nil {
@@ -33,7 +34,7 @@ func (c *Client) GetExperiments(ctx context.Context) (*models.Experiments, error
return &result, nil
}
// GetExperimentDetails gets experiment details
// GetExperimentDetails gets experiment details for the given experiment ID (GET /experiments/details).
func (c *Client) GetExperimentDetails(ctx context.Context, experimentID string) (*models.ExperimentDetail, error) {
params := map[string]string{
"experiment_id": experimentID,
@@ -45,7 +46,7 @@ func (c *Client) GetExperimentDetails(ctx context.Context, experimentID string)
return &result, nil
}
// GetPermissions gets user permissions
// GetPermissions gets user permissions (GET /permissions).
func (c *Client) GetPermissions(ctx context.Context) (*models.Permissions, error) {
var result models.Permissions
if err := c.request.Get(ctx, "/permissions", nil, &result); err != nil {
@@ -54,7 +55,7 @@ func (c *Client) GetPermissions(ctx context.Context) (*models.Permissions, error
return &result, nil
}
// GetPermissionAlerts gets permission alerts
// GetPermissionAlerts gets permission alerts (GET /permission-alerts).
func (c *Client) GetPermissionAlerts(ctx context.Context) (*models.PermissionAlerts, error) {
var result models.PermissionAlerts
if err := c.request.Get(ctx, "/permission-alerts", nil, &result); err != nil {
@@ -63,7 +64,7 @@ func (c *Client) GetPermissionAlerts(ctx context.Context) (*models.PermissionAle
return &result, nil
}
// GetContentRestrictions gets content restrictions
// GetContentRestrictions gets content restrictions (GET /content-restrictions).
func (c *Client) GetContentRestrictions(ctx context.Context) (*models.ContentRestrictions, error) {
var result models.ContentRestrictions
if err := c.request.Get(ctx, "/content-restrictions", nil, &result); err != nil {
+40 -21
View File
@@ -1,3 +1,4 @@
// Package client provides methods for interacting with Yandex Music API endpoints.
package client
import (
@@ -9,8 +10,7 @@ import (
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/utils"
)
// GetTrackSupplement gets supplement for a track
// GET /tracks/{track_id}/supplement
// GetTrackSupplement gets supplement for a track (GET /tracks/{track_id}/supplement).
func (c *Client) GetTrackSupplement(ctx context.Context, trackID string) (*models.Supplement, error) {
path := fmt.Sprintf("/tracks/%s/supplement", trackID)
@@ -21,8 +21,9 @@ func (c *Client) GetTrackSupplement(ctx context.Context, trackID string) (*model
return &result, nil
}
// GetTrackLyrics gets lyrics for a track (new endpoint with signing)
// GET /tracks/{track_id}/lyrics
// GetTrackLyrics gets lyrics for a track (GET /tracks/{track_id}/lyrics).
// The format parameter specifies the lyrics format (e.g., "TEXT" or "LRC").
// The request is signed using the default signing key.
func (c *Client) GetTrackLyrics(ctx context.Context, trackID string, format string) (*models.TrackLyrics, error) {
path := fmt.Sprintf("/tracks/%s/lyrics", trackID)
@@ -42,7 +43,7 @@ func (c *Client) GetTrackLyrics(ctx context.Context, trackID string, format stri
return &result, nil
}
// GetTrackLyricsText fetches the full lyrics text from the download URL
// GetTrackLyricsText fetches the full lyrics text from the download URL provided in TrackLyrics.
func (c *Client) GetTrackLyricsText(ctx context.Context, trackLyrics *models.TrackLyrics) (string, error) {
data, err := c.request.Download(ctx, trackLyrics.DownloadURL)
if err != nil {
@@ -51,8 +52,7 @@ func (c *Client) GetTrackLyricsText(ctx context.Context, trackLyrics *models.Tra
return string(data), nil
}
// GetLyrics gets lyrics info for a track (legacy endpoint)
// GET /tracks/lyrics?track_id={track_id}
// GetLyrics gets lyrics info for a track using the legacy endpoint (GET /tracks/lyrics).
func (c *Client) GetLyrics(ctx context.Context, trackID string) (*models.LyricsInfo, error) {
params := map[string]string{
"track_id": trackID,
@@ -65,8 +65,7 @@ func (c *Client) GetLyrics(ctx context.Context, trackID string) (*models.LyricsI
return &result, nil
}
// GetVideoSupplement gets video supplement for a track (legacy endpoint)
// GET /tracks/video-supplement?track_id={track_id}
// GetVideoSupplement gets video supplement for a track using the legacy endpoint (GET /tracks/video-supplement).
func (c *Client) GetVideoSupplement(ctx context.Context, trackID string) (*models.VideoSupplement, error) {
params := map[string]string{
"track_id": trackID,
@@ -79,44 +78,43 @@ func (c *Client) GetVideoSupplement(ctx context.Context, trackID string) (*model
return &result, nil
}
// GetLyricsByTrackID is an alias for GetTrackLyrics for backward compatibility
// GET /tracks/{track_id}/lyrics
// GetLyricsByTrackID is an alias for GetTrackLyrics for backward compatibility (GET /tracks/{track_id}/lyrics).
func (c *Client) GetLyricsByTrackID(ctx context.Context, trackID string, format string) (*models.TrackLyrics, error) {
return c.GetTrackLyrics(ctx, trackID, format)
}
// LyricsFormat represents the lyrics format
// LyricsFormat represents the lyrics format.
type LyricsFormat string
const (
// LyricsFormatText is plain text format
// LyricsFormatText is plain text lyrics format.
LyricsFormatText LyricsFormat = "TEXT"
// LyricsFormatLRC is LRC format with timestamps
// LyricsFormatLRC is LRC format lyrics with timestamps.
LyricsFormatLRC LyricsFormat = "LRC"
)
// GetTrackLyricsWithOptions gets lyrics with options
// GetTrackLyricsWithOptions gets lyrics with options using a LyricsFormat value.
func (c *Client) GetTrackLyricsWithOptions(ctx context.Context, trackID string, format LyricsFormat) (*models.TrackLyrics, error) {
return c.GetTrackLyrics(ctx, trackID, string(format))
}
// SupplementOptions holds options for supplement requests
// SupplementOptions holds options for supplement requests.
type SupplementOptions struct {
TrackID string
}
// GetSupplement gets track supplement with options
// GetSupplement gets track supplement with options.
func (c *Client) GetSupplement(ctx context.Context, opts SupplementOptions) (*models.Supplement, error) {
return c.GetTrackSupplement(ctx, opts.TrackID)
}
// LyricsOptions holds options for lyrics requests
// LyricsOptions holds options for lyrics requests.
type LyricsOptions struct {
TrackID string
Format LyricsFormat
}
// GetLyricsWithOptions gets track lyrics with options
// GetLyricsWithOptions gets track lyrics with options.
func (c *Client) GetLyricsWithOptions(ctx context.Context, opts LyricsOptions) (*models.TrackLyrics, error) {
format := string(opts.Format)
if format == "" {
@@ -125,13 +123,34 @@ func (c *Client) GetLyricsWithOptions(ctx context.Context, opts LyricsOptions) (
return c.GetTrackLyrics(ctx, opts.TrackID, format)
}
// Sign represents a request signature
// Sign represents a request signature for signed API requests.
type Sign struct {
Timestamp int64
Value string
}
// GetSignRequest creates a signature for a track request (exported for external use)
// 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 {
h := utils.GetSignRequest(trackID, utils.DefaultSignKey)
+154 -15
View File
@@ -1,14 +1,52 @@
// Package client provides methods for interacting with Yandex Music API endpoints.
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"
)
// GetTracks gets tracks by IDs
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) {
if len(trackIDs) == 0 {
return nil, nil
@@ -31,7 +69,8 @@ func (c *Client) GetTracks(ctx context.Context, trackIDs []string) ([]*models.Tr
return tracks, nil
}
// GetTrack gets a single track by ID
// GetTrack gets a single track by ID using GetTracks.
// Returns an error if no track is found with the given ID.
func (c *Client) GetTrack(ctx context.Context, trackID string) (*models.Track, error) {
tracks, err := c.GetTracks(ctx, []string{trackID})
if err != nil {
@@ -43,13 +82,15 @@ func (c *Client) GetTrack(ctx context.Context, trackID string) (*models.Track, e
return tracks[0], nil
}
// GetTrackDownloadInfo gets download info for a track
// 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 {
@@ -58,7 +99,45 @@ func (c *Client) GetTrackDownloadInfo(ctx context.Context, trackID, quality stri
return result, nil
}
// DownloadTrack downloads a track
// 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, 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 {
@@ -66,31 +145,91 @@ 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)
}
// SearchResult represents search results
// 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"`
}
// SearchTracksResult represents track search results
// SearchTracksResult represents track search results with total count and results list.
type SearchTracksResult struct {
Total int `json:"total"`
Results []models.Track `json:"results"`
+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": "",
+14 -13
View File
@@ -1,10 +1,11 @@
// Package models provides data models for the Yandex Music API.
package models
import (
"encoding/json"
)
// AccountSettings represents account settings
// AccountSettings represents account settings from the Yandex Music API.
type AccountSettings struct {
BaseModel
Language string `json:"language"`
@@ -26,7 +27,7 @@ type AccountSettings struct {
DownloadOnWifiOnly bool `json:"download_on_wifi_only"`
}
// DeJSON deserializes AccountSettings from JSON
// DeJSON deserializes AccountSettings from JSON data.
func (a *AccountSettings) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -90,7 +91,7 @@ func (a *AccountSettings) DeJSON(data []byte, c ClientInterface) (Model, error)
return settings, nil
}
// DeList deserializes a list of AccountSettings
// DeList deserializes a list of AccountSettings from JSON data.
func (a *AccountSettings) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -108,10 +109,10 @@ func (a *AccountSettings) DeList(data []byte, c ClientInterface) ([]Model, error
return result, nil
}
// Subscription represents subscription info
// 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"`
@@ -127,7 +128,7 @@ type Subscription struct {
TrialUsed bool `json:"trial_used"`
}
// DeJSON deserializes Subscription from JSON
// DeJSON deserializes Subscription from JSON data.
func (s *Subscription) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -182,7 +183,7 @@ func (s *Subscription) DeJSON(data []byte, c ClientInterface) (Model, error) {
return sub, nil
}
// DeList deserializes a list of Subscriptions
// DeList deserializes a list of Subscriptions from JSON data.
func (s *Subscription) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -200,7 +201,7 @@ func (s *Subscription) DeList(data []byte, c ClientInterface) ([]Model, error) {
return result, nil
}
// PlusInfo represents Plus subscription info (from /account/plus or account status)
// PlusInfo represents Plus subscription info.
type PlusInfo struct {
BaseModel
HasPlus bool `json:"has_plus"`
@@ -208,7 +209,7 @@ type PlusInfo struct {
Migrated *bool `json:"migrated,omitempty"`
}
// DeJSON deserializes PlusInfo from JSON
// DeJSON deserializes PlusInfo from JSON data.
func (p *PlusInfo) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -232,7 +233,7 @@ func (p *PlusInfo) DeJSON(data []byte, c ClientInterface) (Model, error) {
return plus, nil
}
// DeList deserializes a list of PlusInfo
// DeList deserializes a list of PlusInfo from JSON data.
func (p *PlusInfo) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -250,7 +251,7 @@ func (p *PlusInfo) DeList(data []byte, c ClientInterface) ([]Model, error) {
return result, nil
}
// PromoCodeStatus represents promo code status
// PromoCodeStatus represents promo code status.
type PromoCodeStatus struct {
BaseModel
Code string `json:"code"`
@@ -262,7 +263,7 @@ type PromoCodeStatus struct {
Activated bool `json:"activated"`
}
// DeJSON deserializes PromoCodeStatus from JSON
// DeJSON deserializes PromoCodeStatus from JSON data.
func (p *PromoCodeStatus) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -296,7 +297,7 @@ func (p *PromoCodeStatus) DeJSON(data []byte, c ClientInterface) (Model, error)
return promo, nil
}
// DeList deserializes a list of PromoCodeStatus
// DeList deserializes a list of PromoCodeStatus from JSON data.
func (p *PromoCodeStatus) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
+283 -29
View File
@@ -1,40 +1,128 @@
// Package models provides data models for the Yandex Music API.
package models
import (
"encoding/json"
)
// Album represents a full album
// 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"`
}
// DeJSON deserializes an Album from JSON
// 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.
func (a *Album) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -47,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)
}
@@ -100,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)
@@ -130,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
}
@@ -138,7 +230,7 @@ func (a *Album) DeJSON(data []byte, c ClientInterface) (Model, error) {
return album, nil
}
// DeList deserializes a list of Albums
// DeList deserializes a list of Albums from JSON data.
func (a *Album) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -156,14 +248,14 @@ func (a *Album) DeList(data []byte, c ClientInterface) ([]Model, error) {
return result, nil
}
// AlbumMeta represents album metadata
// AlbumMeta represents album metadata.
type AlbumMeta struct {
BaseModel
Availability *Availability `json:"availability"`
Upsell *Upsell `json:"upsell"`
}
// DeJSON deserializes an AlbumMeta from JSON
// DeJSON deserializes an AlbumMeta from JSON data.
func (am *AlbumMeta) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -186,7 +278,7 @@ func (am *AlbumMeta) DeJSON(data []byte, c ClientInterface) (Model, error) {
return meta, nil
}
// DeList deserializes a list of AlbumMeta
// DeList deserializes a list of AlbumMeta from JSON data.
func (am *AlbumMeta) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -204,33 +296,33 @@ func (am *AlbumMeta) DeList(data []byte, c ClientInterface) ([]Model, error) {
return result, nil
}
// Availability represents availability info
// Availability represents availability info for an album.
type Availability struct {
AvailableForPremiumUsers bool `json:"available_for_premium_users"`
Territories []string `json:"territories"`
}
// Upsell represents upsell info
// Upsell represents upsell info for an album.
type Upsell struct {
Type string `json:"type"`
Description string `json:"description"`
}
// Best represents best tracks info
// Best represents best tracks info for an album.
type Best struct {
Tracks []Track `json:"tracks"`
}
// TrailerInfo represents album/track trailer info
// TrailerInfo represents album/track trailer info.
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"`
}
// DeJSON deserializes a TrailerInfo from JSON
// DeJSON deserializes a TrailerInfo from JSON data.
func (ti *TrailerInfo) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -255,7 +347,7 @@ func (ti *TrailerInfo) DeJSON(data []byte, c ClientInterface) (Model, error) {
return trailer, nil
}
// DeList deserializes a list of TrailerInfo
// DeList deserializes a list of TrailerInfo from JSON data.
func (ti *TrailerInfo) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -272,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))
}
}
+561 -41
View File
@@ -1,32 +1,32 @@
// Package models provides data models for the Yandex Music API.
package models
import (
"encoding/json"
)
// Artist represents a full artist
// 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"`
@@ -34,7 +34,7 @@ type Artist struct {
TracksData []Track `json:"tracks_data"`
}
// DeJSON deserializes an Artist from JSON
// DeJSON deserializes an Artist from JSON data.
func (a *Artist) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -62,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)
}
@@ -83,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)
@@ -98,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
@@ -112,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
@@ -146,7 +142,7 @@ func (a *Artist) DeJSON(data []byte, c ClientInterface) (Model, error) {
return artist, nil
}
// DeList deserializes a list of Artists
// DeList deserializes a list of Artists from JSON data.
func (a *Artist) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -164,27 +160,29 @@ func (a *Artist) DeList(data []byte, c ClientInterface) ([]Model, error) {
return result, nil
}
// Ratings represents artist ratings
// 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
// Counts represents artist counts.
type Counts struct {
Tracks int `json:"tracks"`
Albums int `json:"albums"`
Folders int `json:"folders"`
}
// SocialLink represents a social media link
// SocialLink represents a social media link.
type SocialLink struct {
URL string `json:"url"`
Title string `json:"title"`
Type string `json:"type"`
}
// Cover represents cover image info
// Cover represents cover image info.
type Cover struct {
URI string `json:"uri"`
Type string `json:"type"`
@@ -193,23 +191,25 @@ type Cover struct {
Background string `json:"background"`
}
// ArtistBriefInfo represents brief artist info
// ArtistBriefInfo represents brief artist info.
type ArtistBriefInfo struct {
BaseModel
Description string `json:"description"`
Available bool `json:"available"`
}
// ArtistDonations represents artist donations info
// ArtistDonations represents artist donations info.
type ArtistDonations struct {
BaseModel
Items []DonationItem `json:"items"`
Total int `json:"total"`
Goal int `json:"goal"`
Raised int `json:"raised"`
}
// DonationItem represents a donation item
// 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"`
@@ -218,10 +218,182 @@ type DonationItem struct {
Currency string `json:"currency"`
}
// ConcertShort represents a concert (minimal for artist)
// 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"`
@@ -233,7 +405,7 @@ type ConcertShort struct {
Available bool `json:"available"`
}
// DeJSON deserializes a ConcertShort from JSON
// DeJSON deserializes a ConcertShort from JSON data.
func (c *ConcertShort) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -276,7 +448,7 @@ func (c *ConcertShort) DeJSON(data []byte, client ClientInterface) (Model, error
return concert, nil
}
// DeList deserializes a list of ConcertShort
// DeList deserializes a list of ConcertShort from JSON data.
func (c *ConcertShort) DeList(data []byte, client ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -294,10 +466,10 @@ func (c *ConcertShort) DeList(data []byte, client ClientInterface) ([]Model, err
return result, nil
}
// ClipShort represents a clip (minimal for artist)
// 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"`
@@ -308,7 +480,7 @@ type ClipShort struct {
CreatedAt string `json:"created_at"`
}
// DeJSON deserializes a ClipShort from JSON
// DeJSON deserializes a ClipShort from JSON data.
func (c *ClipShort) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -348,7 +520,7 @@ func (c *ClipShort) DeJSON(data []byte, client ClientInterface) (Model, error) {
return clip, nil
}
// DeList deserializes a list of ClipShort
// DeList deserializes a list of ClipShort from JSON data.
func (c *ClipShort) DeList(data []byte, client ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -365,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"`
}
+56
View File
@@ -0,0 +1,56 @@
// Package models provides data models for the Yandex Music API.
package models
import (
"encoding/json"
)
// ArtistAlbums represents a page of albums by an artist.
type ArtistAlbums struct {
BaseModel
Albums []Album `json:"albums"`
Pager *Pager `json:"pager"`
}
// DeJSON deserializes an ArtistAlbums from JSON data.
func (aa *ArtistAlbums) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
albums := &ArtistAlbums{BaseModel: *NewBaseModel(c)}
if v, ok := raw["albums"]; ok {
var albumList []Album
if err := json.Unmarshal(v, &albumList); err == nil {
albums.Albums = albumList
}
}
if v, ok := raw["pager"]; ok {
var pager Pager
if err := json.Unmarshal(v, &pager); err == nil {
albums.Pager = &pager
}
}
return albums, nil
}
// DeList deserializes a list of ArtistAlbums from JSON data.
func (aa *ArtistAlbums) 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 {
albums := &ArtistAlbums{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, albums); err != nil {
continue
}
result = append(result, albums)
}
return result, nil
}
+56
View File
@@ -0,0 +1,56 @@
// Package models provides data models for the Yandex Music API.
package models
import (
"encoding/json"
)
// ArtistTracks represents a page of tracks by an artist.
type ArtistTracks struct {
BaseModel
Tracks []Track `json:"tracks"`
Pager *Pager `json:"pager"`
}
// DeJSON deserializes an ArtistTracks from JSON data.
func (at *ArtistTracks) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
tracks := &ArtistTracks{BaseModel: *NewBaseModel(c)}
if v, ok := raw["tracks"]; ok {
var trackList []Track
if err := json.Unmarshal(v, &trackList); err == nil {
tracks.Tracks = trackList
}
}
if v, ok := raw["pager"]; ok {
var pager Pager
if err := json.Unmarshal(v, &pager); err == nil {
tracks.Pager = &pager
}
}
return tracks, nil
}
// DeList deserializes a list of ArtistTracks from JSON data.
func (at *ArtistTracks) 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 {
tracks := &ArtistTracks{BaseModel: *NewBaseModel(c)}
if err := json.Unmarshal(item, tracks); err != nil {
continue
}
result = append(result, tracks)
}
return result, nil
}
+114 -13
View File
@@ -1,40 +1,47 @@
// Package models provides data models for the Yandex Music API.
// All models implement the Model interface with DeJSON/DeList deserialization methods.
package models
import (
"encoding/json"
"fmt"
"strconv"
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/utils"
)
// Model is the base interface for all models
// Model is the base interface for all models.
// All model types implement this interface for JSON deserialization and serialization.
type Model interface {
// DeJSON deserializes from JSON
// DeJSON deserializes a model from JSON data.
DeJSON(data []byte, c ClientInterface) (Model, error)
// DeList deserializes a list of models
// DeList deserializes a list of models from JSON data.
DeList(data []byte, c ClientInterface) ([]Model, error)
// ToJSON serializes to JSON
// ToJSON serializes the model to JSON.
ToJSON() ([]byte, error)
// ToMap converts to map
// ToMap converts the model to a map.
ToMap() (map[string]any, error)
}
// BaseModel provides common functionality for models
// BaseModel provides common functionality for models, including a client reference.
type BaseModel struct {
Client ClientInterface `json:"-"`
}
// NewBaseModel creates a new BaseModel
// NewBaseModel creates a new BaseModel with the given client interface.
func NewBaseModel(c ClientInterface) *BaseModel {
return &BaseModel{Client: c}
}
// DeJSON implements Model.DeJSON for basic models
// DeJSON implements Model.DeJSON for basic models.
// This should be overridden by concrete types.
func (b *BaseModel) DeJSON(data []byte, c ClientInterface) (Model, error) {
// This should be overridden by concrete types
return nil, nil
}
// DeList implements Model.DeList for basic models
// DeList implements Model.DeList for basic models.
// This should be overridden by concrete types.
func (b *BaseModel) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -49,12 +56,12 @@ func (b *BaseModel) DeList(data []byte, c ClientInterface) ([]Model, error) {
return result, nil
}
// ToJSON serializes the model to JSON
// ToJSON serializes the model to JSON using the default JSON backend.
func (b *BaseModel) ToJSON() ([]byte, error) {
return utils.Marshal(b)
}
// ToMap converts the model to a map
// ToMap converts the model to a map by serializing to JSON and unmarshaling into a map.
func (b *BaseModel) ToMap() (map[string]any, error) {
data, err := b.ToJSON()
if err != nil {
@@ -67,12 +74,106 @@ func (b *BaseModel) ToMap() (map[string]any, error) {
return result, nil
}
// SetClient sets the client on the model
// SetClient sets the client interface on the model.
func (b *BaseModel) SetClient(c ClientInterface) {
b.Client = c
}
// GetClient returns the client
// GetClient returns the client interface associated with the model.
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
}
+11 -2
View File
@@ -1,19 +1,28 @@
// Package models provides data models for the Yandex Music API.
package models
import (
"context"
)
// ClientInterface defines the minimal client interface needed by models
// ClientInterface defines the minimal client interface needed by models.
// Models use this to access the underlying request handler.
type ClientInterface interface {
// GetRequest returns the underlying request handler.
GetRequest() RequestInterface
}
// RequestInterface defines the minimal request interface needed by models
// RequestInterface defines the minimal request interface needed by models.
// It provides methods for making HTTP requests to the Yandex Music API.
type RequestInterface interface {
// Get performs a GET request to the given path with query parameters.
Get(ctx context.Context, path string, params map[string]string, result any) error
// Post performs a POST request to the given path with a JSON body.
Post(ctx context.Context, path string, body any, result any) error
// Put performs a PUT request to the given path with a JSON body.
Put(ctx context.Context, path string, body any, result any) error
// Delete performs a DELETE request to the given path with an optional JSON body.
Delete(ctx context.Context, path string, body any, result any) error
// Download downloads a file from the given URL and returns the raw response body.
Download(ctx context.Context, url string) ([]byte, error)
}
+61 -52
View File
@@ -1,30 +1,34 @@
// Package models provides data models for the Yandex Music API.
package models
import (
"encoding/json"
)
// Clip represents a clip (short video)
// 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"`
}
// DeJSON deserializes a Clip from JSON
// DeJSON deserializes a Clip from JSON data.
func (c *Clip) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -39,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)
@@ -70,26 +95,10 @@ 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
}
// DeList deserializes a list of Clips
// DeList deserializes a list of Clips from JSON data.
func (c *Clip) DeList(data []byte, client ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -107,31 +116,31 @@ func (c *Clip) DeList(data []byte, client ClientInterface) ([]Model, error) {
return result, nil
}
// ClipsWillLike represents clips the user might like
// ClipsWillLike represents clips the user might like.
type ClipsWillLike struct {
BaseModel
Clips []Clip `json:"clips"`
Total int `json:"total"`
}
// ArtistClipData represents artist clip data
// ArtistClipData represents artist clip data.
type ArtistClipData struct {
BaseModel
Clips []Clip `json:"clips"`
Total int `json:"total"`
}
// ArtistClipItem represents an artist clip item
// ArtistClipItem represents an artist clip item.
type ArtistClipItem struct {
BaseModel
Clip *Clip `json:"clip"`
Index int `json:"index"`
}
// Shot represents a shot (short video)
// 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"`
@@ -150,7 +159,7 @@ type Shot struct {
AspectRatio string `json:"aspect_ratio"`
}
// DeJSON deserializes a Shot from JSON
// DeJSON deserializes a Shot from JSON data.
func (s *Shot) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -219,7 +228,7 @@ func (s *Shot) DeJSON(data []byte, client ClientInterface) (Model, error) {
return shot, nil
}
// DeList deserializes a list of Shots
// DeList deserializes a list of Shots from JSON data.
func (s *Shot) DeList(data []byte, client ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -237,14 +246,14 @@ func (s *Shot) DeList(data []byte, client ClientInterface) ([]Model, error) {
return result, nil
}
// ShotData represents shot data
// ShotData represents shot data.
type ShotData struct {
BaseModel
Shots []Shot `json:"shots"`
Total int `json:"total"`
}
// ShotEvent represents a shot event
// ShotEvent represents a shot event.
type ShotEvent struct {
BaseModel
EventID string `json:"event_id"`
@@ -254,10 +263,10 @@ type ShotEvent struct {
Action string `json:"action"`
}
// ShotType represents a shot type
// 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"`
+100 -21
View File
@@ -1,17 +1,19 @@
// Package models provides data models for the Yandex Music API.
package models
import (
"encoding/json"
)
// Concert represents a concert
// 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"`
@@ -26,9 +28,16 @@ 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
// DeJSON deserializes a Concert from JSON data.
func (c *Concert) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -76,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
@@ -111,7 +144,7 @@ func (c *Concert) DeJSON(data []byte, client ClientInterface) (Model, error) {
return concert, nil
}
// DeList deserializes a list of Concerts
// DeList deserializes a list of Concerts from JSON data.
func (c *Concert) DeList(data []byte, client ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -129,10 +162,10 @@ func (c *Concert) DeList(data []byte, client ClientInterface) ([]Model, error) {
return result, nil
}
// ConcertVenue represents a concert venue
// 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"`
@@ -141,10 +174,10 @@ type ConcertVenue struct {
ImageURI string `json:"image_uri"`
}
// ConcertLocation represents a concert location
// 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"`
@@ -152,7 +185,7 @@ type ConcertLocation struct {
Venues []ConcertVenue `json:"venues"`
}
// ConcertLocations represents concert locations
// ConcertLocations represents concert locations.
type ConcertLocations struct {
BaseModel
Locations []ConcertLocation `json:"locations"`
@@ -161,7 +194,7 @@ type ConcertLocations struct {
PageSize int `json:"page_size"`
}
// ConcertFeed represents a concert feed
// ConcertFeed represents a concert feed.
type ConcertFeed struct {
BaseModel
Concerts []Concert `json:"concerts"`
@@ -170,14 +203,14 @@ type ConcertFeed struct {
PageSize int `json:"page_size"`
}
// ConcertInfo represents concert info
// ConcertInfo represents concert info.
type ConcertInfo struct {
BaseModel
Concert *Concert `json:"concert"`
Related []Concert `json:"related"`
}
// ConcertEventInfo represents concert event info
// ConcertEventInfo represents concert event info.
type ConcertEventInfo struct {
BaseModel
EventID string `json:"event_id"`
@@ -186,7 +219,7 @@ type ConcertEventInfo struct {
Data json.RawMessage `json:"data"`
}
// ConcertDescription represents concert description
// ConcertDescription represents concert description.
type ConcertDescription struct {
BaseModel
Text string `json:"text"`
@@ -194,7 +227,7 @@ type ConcertDescription struct {
ShortText string `json:"short_text"`
}
// ConcertCashback represents concert cashback
// ConcertCashback represents concert cashback.
type ConcertCashback struct {
BaseModel
Amount int `json:"amount"`
@@ -203,41 +236,87 @@ type ConcertCashback struct {
Expires string `json:"expires"`
}
// ConcertMinPrice represents concert min price
// ConcertMinPrice represents concert min price.
type ConcertMinPrice struct {
BaseModel
Price int `json:"price"`
Currency string `json:"currency"`
}
// ConcertTabConfig represents concert tab config
// 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"`
Range *ConcertTabRange `json:"range"`
}
// ConcertTabRange represents concert tab range
// ConcertTabRange represents concert tab range.
type ConcertTabRange struct {
BaseModel
From string `json:"from"`
To string `json:"to"`
}
// ConcertSkeleton represents concert skeleton
// ConcertSkeleton represents concert skeleton.
type ConcertSkeleton struct {
BaseModel
Blocks []ConcertSkeletonBlock `json:"blocks"`
}
// ConcertSkeletonBlock represents a concert skeleton block
// 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
}
+91 -8
View File
@@ -1,17 +1,18 @@
// Package models provides data models for the Yandex Music API.
package models
import (
"encoding/json"
)
// Credits represents track credits
// Credits represents track credits from the Yandex Music API.
type Credits struct {
BaseModel
Items []Credit `json:"items"`
Total int `json:"total"`
}
// DeJSON deserializes Credits from JSON
// DeJSON deserializes Credits from JSON data.
func (c *Credits) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -32,7 +33,7 @@ func (c *Credits) DeJSON(data []byte, client ClientInterface) (Model, error) {
return credits, nil
}
// DeList deserializes a list of Credits
// DeList deserializes a list of Credits from JSON data. from JSON data.
func (c *Credits) DeList(data []byte, client ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -50,7 +51,7 @@ func (c *Credits) DeList(data []byte, client ClientInterface) ([]Model, error) {
return result, nil
}
// Credit represents a credit
// Credit represents a credit.
type Credit struct {
BaseModel
Role string `json:"role"`
@@ -62,7 +63,7 @@ type Credit struct {
AlbumID string `json:"album_id"`
}
// DeJSON deserializes a Credit from JSON
// DeJSON deserializes a Credit from JSON data.
func (c *Credit) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -114,7 +115,7 @@ func (c *Credit) DeList(data []byte, client ClientInterface) ([]Model, error) {
return result, nil
}
// LicenceTextPart represents a licence text part
// LicenceTextPart represents a licence text part.
type LicenceTextPart struct {
BaseModel
Text string `json:"text"`
@@ -124,7 +125,7 @@ type LicenceTextPart struct {
IsMain bool `json:"is_main"`
}
// DeJSON deserializes a LicenceTextPart from JSON
// DeJSON deserializes a LicenceTextPart from JSON data.
func (ltp *LicenceTextPart) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -152,7 +153,7 @@ func (ltp *LicenceTextPart) DeJSON(data []byte, client ClientInterface) (Model,
return part, nil
}
// DeList deserializes a list of LicenceTextPart
// DeList deserializes a list of LicenceTextPart from JSON data.
func (ltp *LicenceTextPart) DeList(data []byte, client ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -169,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
}
+44 -26
View File
@@ -1,18 +1,25 @@
// Package models provides data models for the Yandex Music API.
package models
import (
"encoding/json"
)
// Feed represents the main feed
// 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
// DeJSON deserializes a Feed from JSON data.
func (f *Feed) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -21,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)
@@ -36,11 +58,14 @@ 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
}
// DeList deserializes a list of Feeds
// DeList deserializes a list of Feeds from JSON data.
func (f *Feed) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -58,7 +83,7 @@ func (f *Feed) DeList(data []byte, c ClientInterface) ([]Model, error) {
return result, nil
}
// Day represents a day in the feed
// Day represents a day in the feed.
type Day struct {
BaseModel
Date string `json:"date"`
@@ -68,7 +93,7 @@ type Day struct {
GeneratedPlaylists []GeneratedPlaylist `json:"generated_playlists"`
}
// DeJSON deserializes a Day from JSON
// DeJSON deserializes a Day from JSON data.
func (d *Day) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -100,7 +125,7 @@ func (d *Day) DeJSON(data []byte, c ClientInterface) (Model, error) {
return day, nil
}
// DeList deserializes a list of Days
// DeList deserializes a list of Days from JSON data.
func (d *Day) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -118,17 +143,17 @@ func (d *Day) DeList(data []byte, c ClientInterface) ([]Model, error) {
return result, nil
}
// DayBlock represents a block in a day
// 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"`
Data *DayBlockData `json:"data"`
}
// DayBlockData represents data in a day block
// DayBlockData represents data in a day block.
type DayBlockData struct {
Tracks []Track `json:"tracks"`
Playlists []Playlist `json:"playlists"`
@@ -137,10 +162,10 @@ type DayBlockData struct {
GeneratedPlaylist *GeneratedPlaylist `json:"generated_playlist"`
}
// GeneratedPlaylist represents a generated playlist
// 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"`
@@ -153,7 +178,7 @@ type GeneratedPlaylist struct {
Tracks []Track `json:"tracks"`
}
// DeJSON deserializes a GeneratedPlaylist from JSON
// DeJSON deserializes a GeneratedPlaylist from JSON data.
func (gp *GeneratedPlaylist) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -201,7 +226,7 @@ func (gp *GeneratedPlaylist) DeJSON(data []byte, c ClientInterface) (Model, erro
return playlist, nil
}
// DeList deserializes a list of GeneratedPlaylists
// DeList deserializes a list of GeneratedPlaylists from JSON data.
func (gp *GeneratedPlaylist) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -219,7 +244,7 @@ func (gp *GeneratedPlaylist) DeList(data []byte, c ClientInterface) ([]Model, er
return result, nil
}
// Event represents a feed event
// Event represents a feed event.
type Event struct {
BaseModel
Timestamp string `json:"timestamp"`
@@ -231,7 +256,7 @@ type Event struct {
Action string `json:"action"`
}
// AlbumEvent represents an album event
// AlbumEvent represents an album event.
type AlbumEvent struct {
BaseModel
Album *Album `json:"album"`
@@ -240,7 +265,7 @@ type AlbumEvent struct {
Action string `json:"action"`
}
// ArtistEvent represents an artist event
// ArtistEvent represents an artist event.
type ArtistEvent struct {
BaseModel
Artist *Artist `json:"artist"`
@@ -249,7 +274,7 @@ type ArtistEvent struct {
Action string `json:"action"`
}
// TrackWithAds represents a track with ads
// TrackWithAds represents a track with ads.
type TrackWithAds struct {
BaseModel
Track *Track `json:"track"`
@@ -257,10 +282,10 @@ type TrackWithAds struct {
AdParams *AdParams `json:"ad_params"`
}
// Ad represents an ad
// 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"`
@@ -269,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"`
}
+23 -10
View File
@@ -1,23 +1,27 @@
// Package models provides data models for the Yandex Music API.
package models
import (
"encoding/json"
)
// Label represents a label
// 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"`
}
// DeJSON deserializes a Label from JSON
// DeJSON deserializes a Label from JSON data.
func (l *Label) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -44,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
@@ -58,7 +71,7 @@ func (l *Label) DeJSON(data []byte, client ClientInterface) (Model, error) {
return label, nil
}
// DeList deserializes a list of Labels
// DeList deserializes a list of Labels from JSON data.
func (l *Label) DeList(data []byte, client ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -76,7 +89,7 @@ func (l *Label) DeList(data []byte, client ClientInterface) ([]Model, error) {
return result, nil
}
// LabelAlbums represents label albums
// LabelAlbums represents label albums.
type LabelAlbums struct {
BaseModel
Albums []Album `json:"albums"`
@@ -85,7 +98,7 @@ type LabelAlbums struct {
PageSize int `json:"page_size"`
}
// LabelArtists represents label artists
// LabelArtists represents label artists.
type LabelArtists struct {
BaseModel
Artists []Artist `json:"artists"`
+70 -10
View File
@@ -1,22 +1,25 @@
// Package models provides data models for the Yandex Music API.
package models
import (
"encoding/json"
)
// Landing represents a landing page
// 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
// DeJSON deserializes a Landing from JSON data.
func (l *Landing) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -43,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
@@ -53,7 +62,7 @@ func (l *Landing) DeJSON(data []byte, c ClientInterface) (Model, error) {
return landing, nil
}
// DeList deserializes a list of Landings
// DeList deserializes a list of Landings from JSON data.
func (l *Landing) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -71,17 +80,17 @@ func (l *Landing) DeList(data []byte, c ClientInterface) ([]Model, error) {
return result, nil
}
// LandingBlock represents a block in a landing
// 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"`
Data *LandingBlockData `json:"data"`
}
// LandingBlockData represents data in a landing block
// LandingBlockData represents data in a landing block.
type LandingBlockData struct {
Tracks []Track `json:"tracks"`
Playlists []Playlist `json:"playlists"`
@@ -90,16 +99,67 @@ type LandingBlockData struct {
GeneratedPlaylist *GeneratedPlaylist `json:"generated_playlist"`
}
// LandingList represents a list of landings
// 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"`
}
// DeJSON deserializes a LandingList from JSON
// 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
if err := json.Unmarshal(data, &raw); err != nil {
@@ -126,7 +186,7 @@ func (ll *LandingList) DeJSON(data []byte, c ClientInterface) (Model, error) {
return list, nil
}
// DeList deserializes a list of LandingLists
// DeList deserializes a list of LandingLists from JSON data.
func (ll *LandingList) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
+30 -29
View File
@@ -1,13 +1,14 @@
// Package models provides data models for the Yandex Music API.
package models
import (
"encoding/json"
)
// Metatag represents a metatag
// 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"`
@@ -16,7 +17,7 @@ type Metatag struct {
Children []Metatag `json:"children"`
}
// DeJSON deserializes a Metatag from JSON
// DeJSON deserializes a Metatag from JSON data.
func (m *Metatag) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -52,7 +53,7 @@ func (m *Metatag) DeJSON(data []byte, client ClientInterface) (Model, error) {
return metatag, nil
}
// DeList deserializes a list of Metatags
// DeList deserializes a list of Metatags from JSON data.
func (m *Metatag) DeList(data []byte, client ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -70,17 +71,17 @@ func (m *Metatag) DeList(data []byte, client ClientInterface) ([]Model, error) {
return result, nil
}
// MetatagTree represents a metatag tree
// MetatagTree represents a metatag tree.
type MetatagTree struct {
BaseModel
Root *Metatag `json:"root"`
Leaves []Metatag `json:"leaves"`
}
// MetatagLeaf represents a metatag leaf
// 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"`
@@ -88,7 +89,7 @@ type MetatagLeaf struct {
Position int `json:"position"`
}
// MetatagArtists represents metatag artists
// MetatagArtists represents metatag artists.
type MetatagArtists struct {
BaseModel
Artists []Artist `json:"artists"`
@@ -97,7 +98,7 @@ type MetatagArtists struct {
PageSize int `json:"page_size"`
}
// MetatagAlbums represents metatag albums
// MetatagAlbums represents metatag albums.
type MetatagAlbums struct {
BaseModel
Albums []Album `json:"albums"`
@@ -106,7 +107,7 @@ type MetatagAlbums struct {
PageSize int `json:"page_size"`
}
// MetatagPlaylists represents metatag playlists
// MetatagPlaylists represents metatag playlists.
type MetatagPlaylists struct {
BaseModel
Playlists []Playlist `json:"playlists"`
@@ -115,30 +116,30 @@ type MetatagPlaylists struct {
PageSize int `json:"page_size"`
}
// MetatagTitle represents a metatag title
// MetatagTitle represents a metatag title.
type MetatagTitle struct {
BaseModel
Title string `json:"title"`
}
// MetatagSortByValue represents metatag sort by value
// MetatagSortByValue represents metatag sort by value.
type MetatagSortByValue struct {
BaseModel
Value string `json:"value"`
Label string `json:"label"`
}
// MetatagArtistEntry represents a metatag artist entry
// MetatagArtistEntry represents a metatag artist entry.
type MetatagArtistEntry struct {
BaseModel
Artist *Artist `json:"artist"`
Score float64 `json:"score"`
PopularTracks []Track `json:"popular_tracks"`
}
// Genre represents a genre
// 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"`
@@ -147,7 +148,7 @@ type Genre struct {
Children []Genre `json:"children"`
}
// DeJSON deserializes a Genre from JSON
// DeJSON deserializes a Genre from JSON data.
func (g *Genre) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -183,7 +184,7 @@ func (g *Genre) DeJSON(data []byte, client ClientInterface) (Model, error) {
return genre, nil
}
// DeList deserializes a list of Genres
// DeList deserializes a list of Genres from JSON data.
func (g *Genre) DeList(data []byte, client ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -201,10 +202,10 @@ func (g *Genre) DeList(data []byte, client ClientInterface) ([]Model, error) {
return result, nil
}
// Chart represents a chart
// 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"`
@@ -212,7 +213,7 @@ type Chart struct {
Items []ChartItem `json:"items"`
}
// DeJSON deserializes a Chart from JSON
// DeJSON deserializes a Chart from JSON data.
func (c *Chart) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -245,7 +246,7 @@ func (c *Chart) DeJSON(data []byte, client ClientInterface) (Model, error) {
return chart, nil
}
// DeList deserializes a list of Charts
// DeList deserializes a list of Charts from JSON data.
func (c *Chart) DeList(data []byte, client ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -263,14 +264,14 @@ func (c *Chart) DeList(data []byte, client ClientInterface) ([]Model, error) {
return result, nil
}
// ChartInfo represents chart info
// ChartInfo represents chart info.
type ChartInfo struct {
BaseModel
Chart *Chart `json:"chart"`
Menu []ChartInfoMenu `json:"menu"`
}
// ChartItem represents a chart item
// ChartItem represents a chart item.
type ChartItem struct {
BaseModel
Position int `json:"position"`
@@ -282,25 +283,25 @@ type ChartItem struct {
Trend string `json:"trend"`
}
// ChartInfoMenu represents a chart info menu
// 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"`
}
// ChartInfoMenuItem represents a chart info menu item
// 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"`
}
// ChartItems represents chart items response
// ChartItems represents chart items response.
type ChartItems struct {
BaseModel
Items []ChartItem `json:"items"`
+16 -16
View File
@@ -1,10 +1,11 @@
// Package models provides data models for the Yandex Music API.
package models
import (
"encoding/json"
)
// MusicHistory represents music history
// MusicHistory represents music history from the Yandex Music API.
type MusicHistory struct {
BaseModel
Tabs []MusicHistoryTab `json:"tabs"`
@@ -13,7 +14,7 @@ type MusicHistory struct {
Context *MusicHistoryContextFullModel `json:"context"`
}
// DeJSON deserializes a MusicHistory from JSON
// DeJSON deserializes a MusicHistory from JSON data.
func (mh *MusicHistory) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -46,7 +47,7 @@ func (mh *MusicHistory) DeJSON(data []byte, client ClientInterface) (Model, erro
return history, nil
}
// DeList deserializes a list of MusicHistory
// DeList deserializes a list of MusicHistory from JSON data.
func (mh *MusicHistory) DeList(data []byte, client ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -64,10 +65,10 @@ func (mh *MusicHistory) DeList(data []byte, client ClientInterface) ([]Model, er
return result, nil
}
// MusicHistoryTab represents a music history tab
// 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"`
@@ -75,20 +76,20 @@ type MusicHistoryTab struct {
Position int `json:"position"`
}
// MusicHistoryGroup represents a music history group
// 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"`
Total int `json:"total"`
}
// MusicHistoryItem represents a music history item
// 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"`
@@ -100,7 +101,7 @@ type MusicHistoryItem struct {
Context *MusicHistoryItemData `json:"context"`
}
// MusicHistoryItemData represents music history item data
// MusicHistoryItemData represents music history item data.
type MusicHistoryItemData struct {
BaseModel
Source string `json:"source"`
@@ -109,17 +110,16 @@ 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
// MusicHistoryItemID represents music history item ID.
type MusicHistoryItemID struct {
BaseModel
ID string `json:"id"`
ID JSONID `json:"id"`
Type string `json:"type"`
}
// MusicHistoryItems represents music history items
// MusicHistoryItems represents music history items.
type MusicHistoryItems struct {
BaseModel
Items []MusicHistoryItem `json:"items"`
@@ -128,7 +128,7 @@ type MusicHistoryItems struct {
PageSize int `json:"page_size"`
}
// MusicHistoryContextFullModel represents music history context full model
// MusicHistoryContextFullModel represents music history context full model.
type MusicHistoryContextFullModel struct {
BaseModel
DeviceID string `json:"device_id"`
+57 -11
View File
@@ -1,13 +1,14 @@
// Package models provides data models for the Yandex Music API.
package models
import (
"encoding/json"
)
// Pin represents a pin
// 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"`
@@ -20,9 +21,10 @@ 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
// DeJSON deserializes a Pin from JSON data.
func (p *Pin) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -58,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
@@ -83,7 +90,7 @@ func (p *Pin) DeJSON(data []byte, client ClientInterface) (Model, error) {
return pin, nil
}
// DeList deserializes a list of Pins
// DeList deserializes a list of Pins from JSON data.
func (p *Pin) DeList(data []byte, client ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -101,14 +108,14 @@ func (p *Pin) DeList(data []byte, client ClientInterface) ([]Model, error) {
return result, nil
}
// PinData represents pin data
// PinData represents pin data.
type PinData struct {
BaseModel
Pins []Pin `json:"pins"`
Total int `json:"total"`
}
// PinsList represents a list of pins
// PinsList represents a list of pins.
type PinsList struct {
BaseModel
Pins []Pin `json:"pins"`
@@ -117,19 +124,21 @@ type PinsList struct {
PageSize int `json:"page_size"`
}
// Presaves represents presaves
// 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"`
PageSize int `json:"page_size"`
}
// Presave represents a presave
// 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"`
@@ -142,7 +151,7 @@ type Presave struct {
UpdatedAt string `json:"updated_at"`
}
// DeJSON deserializes a Presave from JSON
// DeJSON deserializes a Presave from JSON data.
func (p *Presave) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -195,7 +204,7 @@ func (p *Presave) DeJSON(data []byte, client ClientInterface) (Model, error) {
return presave, nil
}
// DeList deserializes a list of Presaves
// DeList deserializes a list of Presaves from JSON data.
func (p *Presave) DeList(data []byte, client ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -212,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
}
+12 -11
View File
@@ -1,10 +1,11 @@
// Package models provides data models for the Yandex Music API.
package models
import (
"encoding/json"
)
// Playlist represents a playlist
// Playlist represents a playlist from the Yandex Music API.
type Playlist struct {
BaseModel
Kind int `json:"kind"`
@@ -36,7 +37,7 @@ type Playlist struct {
Trailer *TrailerInfo `json:"trailer"`
}
// DeJSON deserializes a Playlist from JSON
// DeJSON deserializes a Playlist from JSON data.
func (p *Playlist) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -142,7 +143,7 @@ func (p *Playlist) DeJSON(data []byte, c ClientInterface) (Model, error) {
return playlist, nil
}
// DeList deserializes a list of Playlists
// DeList deserializes a list of Playlists from JSON data.
func (p *Playlist) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -160,7 +161,7 @@ func (p *Playlist) DeList(data []byte, c ClientInterface) ([]Model, error) {
return result, nil
}
// User represents a user (playlist owner)
// User represents a user (playlist owner) from the Yandex Music API.
type User struct {
BaseModel
UID int64 `json:"uid"`
@@ -184,7 +185,7 @@ type User struct {
Counters *UserCounters `json:"counters"`
}
// DeJSON deserializes a User from JSON
// DeJSON deserializes a User from JSON data.
func (u *User) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -255,7 +256,7 @@ func (u *User) DeJSON(data []byte, c ClientInterface) (Model, error) {
return user, nil
}
// DeList deserializes a list of Users
// DeList deserializes a list of Users from JSON data.
func (u *User) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -273,7 +274,7 @@ func (u *User) DeList(data []byte, c ClientInterface) ([]Model, error) {
return result, nil
}
// UserSettings represents user settings (from /account/settings)
// UserSettings represents user settings from /account/settings.
type UserSettings struct {
BaseModel
UID int64 `json:"uid"`
@@ -295,7 +296,7 @@ type UserSettings struct {
ShowDiskTracksInLibrary *bool `json:"show_disk_tracks_in_library,omitempty"`
}
// DeJSON deserializes UserSettings from JSON
// DeJSON deserializes UserSettings from JSON data.
func (u *UserSettings) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -365,7 +366,7 @@ func (u *UserSettings) DeJSON(data []byte, c ClientInterface) (Model, error) {
return settings, nil
}
// DeList deserializes a list of UserSettings
// DeList deserializes a list of UserSettings from JSON data.
func (u *UserSettings) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -383,7 +384,7 @@ func (u *UserSettings) DeList(data []byte, c ClientInterface) ([]Model, error) {
return result, nil
}
// UserCounters represents user counters
// UserCounters represents user counters.
type UserCounters struct {
Tracks int `json:"tracks"`
Albums int `json:"albums"`
@@ -393,7 +394,7 @@ type UserCounters struct {
Likes int `json:"likes"`
}
// Image represents an image with multiple sizes
// Image represents an image with multiple sizes.
type Image struct {
URI string `json:"uri"`
Type string `json:"type"`
+20 -17
View File
@@ -1,13 +1,14 @@
// Package models provides data models for the Yandex Music API.
package models
import (
"encoding/json"
)
// Queue represents a playback queue
// 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"`
@@ -20,7 +21,7 @@ type Queue struct {
TotalDuration int `json:"total_duration_ms"`
}
// DeJSON deserializes a Queue from JSON
// DeJSON deserializes a Queue from JSON data.
func (q *Queue) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -71,7 +72,7 @@ func (q *Queue) DeJSON(data []byte, c ClientInterface) (Model, error) {
return queue, nil
}
// DeList deserializes a list of Queues
// DeList deserializes a list of Queues from JSON data.
func (q *Queue) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -89,19 +90,21 @@ func (q *Queue) DeList(data []byte, c ClientInterface) ([]Model, error) {
return result, nil
}
// QueueItem represents a queue item
// 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
// DeJSON deserializes a QueueItem from JSON data.
func (qi *QueueItem) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -131,16 +134,16 @@ 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
}
// DeList deserializes a list of QueueItems
// DeList deserializes a list of QueueItems from JSON data.
func (qi *QueueItem) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -158,10 +161,10 @@ func (qi *QueueItem) DeList(data []byte, c ClientInterface) ([]Model, error) {
return result, nil
}
// PlayContext represents a play context
// 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"`
@@ -173,7 +176,7 @@ type PlayContext struct {
Available bool `json:"available"`
}
// DeJSON deserializes a PlayContext from JSON
// DeJSON deserializes a PlayContext from JSON data.
func (pc *PlayContext) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -219,7 +222,7 @@ func (pc *PlayContext) DeJSON(data []byte, c ClientInterface) (Model, error) {
return ctx, nil
}
// DeList deserializes a list of PlayContexts
// DeList deserializes a list of PlayContexts from JSON data.
func (pc *PlayContext) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -237,7 +240,7 @@ func (pc *PlayContext) DeList(data []byte, c ClientInterface) ([]Model, error) {
return result, nil
}
// PlayContextsData represents play contexts data
// PlayContextsData represents play contexts data.
type PlayContextsData struct {
BaseModel
Contexts []PlayContext `json:"contexts"`
@@ -246,7 +249,7 @@ type PlayContextsData struct {
PageSize int `json:"page_size"`
}
// DeJSON deserializes PlayContextsData from JSON
// DeJSON deserializes PlayContextsData from JSON data.
func (pcd *PlayContextsData) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -273,7 +276,7 @@ func (pcd *PlayContextsData) DeJSON(data []byte, c ClientInterface) (Model, erro
return result, nil
}
// DeList deserializes a list of PlayContextsData
// DeList deserializes a list of PlayContextsData from JSON data.
func (pcd *PlayContextsData) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
+414 -22
View File
@@ -1,13 +1,14 @@
// Package models provides data models for the Yandex Music API.
package models
import (
"encoding/json"
)
// RadioStation represents a radio station
// 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"`
@@ -16,7 +17,7 @@ type RadioStation struct {
IsPersonal bool `json:"is_personal"`
}
// DeJSON deserializes a RadioStation from JSON
// DeJSON deserializes a RadioStation from JSON data.
func (r *RadioStation) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -50,7 +51,7 @@ func (r *RadioStation) DeJSON(data []byte, client ClientInterface) (Model, error
return station, nil
}
// DeList deserializes a list of RadioStations
// DeList deserializes a list of RadioStations from JSON data.
func (r *RadioStation) DeList(data []byte, client ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -68,7 +69,7 @@ func (r *RadioStation) DeList(data []byte, client ClientInterface) ([]Model, err
return result, nil
}
// StationTracksResult represents station tracks result
// StationTracksResult represents station tracks result.
type StationTracksResult struct {
BaseModel
Tracks []Track `json:"tracks"`
@@ -76,7 +77,7 @@ type StationTracksResult struct {
Sequence string `json:"sequence"`
}
// DeJSON deserializes StationTracksResult from JSON
// DeJSON deserializes StationTracksResult from JSON data.
func (s *StationTracksResult) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -100,7 +101,7 @@ func (s *StationTracksResult) DeJSON(data []byte, client ClientInterface) (Model
return result, nil
}
// DeList deserializes a list of StationTracksResult
// DeList deserializes a list of StationTracksResult from JSON data.
func (s *StationTracksResult) DeList(data []byte, client ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -118,10 +119,10 @@ func (s *StationTracksResult) DeList(data []byte, client ClientInterface) ([]Mod
return result, nil
}
// RotorStation represents a rotor (generative) station
// 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"`
@@ -131,7 +132,7 @@ type RotorStation struct {
Settings *RotorSettings `json:"settings"`
}
// DeJSON deserializes a RotorStation from JSON
// DeJSON deserializes a RotorStation from JSON data.
func (r *RotorStation) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -170,7 +171,7 @@ func (r *RotorStation) DeJSON(data []byte, client ClientInterface) (Model, error
return station, nil
}
// DeList deserializes a list of RotorStations
// DeList deserializes a list of RotorStations from JSON data.
func (r *RotorStation) DeList(data []byte, client ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -188,11 +189,14 @@ func (r *RotorStation) DeList(data []byte, client ClientInterface) ([]Model, err
return result, nil
}
// RotorSettings represents rotor station settings
// 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"`
@@ -201,7 +205,7 @@ type RotorSettings struct {
Valence float64 `json:"valence"`
}
// RotorStationTracksResult represents rotor station tracks result
// RotorStationTracksResult represents rotor station tracks result.
type RotorStationTracksResult struct {
BaseModel
Tracks []Track `json:"tracks"`
@@ -209,7 +213,7 @@ type RotorStationTracksResult struct {
Sequence string `json:"sequence"`
}
// DeJSON deserializes RotorStationTracksResult from JSON
// DeJSON deserializes RotorStationTracksResult from JSON data.
func (r *RotorStationTracksResult) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -233,7 +237,7 @@ func (r *RotorStationTracksResult) DeJSON(data []byte, client ClientInterface) (
return result, nil
}
// DeList deserializes a list of RotorStationTracksResult
// DeList deserializes a list of RotorStationTracksResult from JSON data.
func (r *RotorStationTracksResult) DeList(data []byte, client ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -251,7 +255,7 @@ func (r *RotorStationTracksResult) DeList(data []byte, client ClientInterface) (
return result, nil
}
// RotorSession represents a rotor session
// RotorSession represents a rotor session.
type RotorSession struct {
BaseModel
SessionID string `json:"session_id"`
@@ -262,7 +266,7 @@ type RotorSession struct {
CreatedAt string `json:"created_at"`
}
// DeJSON deserializes a RotorSession from JSON
// DeJSON deserializes a RotorSession from JSON data.
func (r *RotorSession) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -295,7 +299,7 @@ func (r *RotorSession) DeJSON(data []byte, client ClientInterface) (Model, error
return session, nil
}
// DeList deserializes a list of RotorSessions
// DeList deserializes a list of RotorSessions from JSON data.
func (r *RotorSession) DeList(data []byte, client ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -313,7 +317,71 @@ func (r *RotorSession) DeList(data []byte, client ClientInterface) ([]Model, err
return result, nil
}
// SessionFeedback represents session feedback
// 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
TrackID string `json:"track_id"`
@@ -321,8 +389,332 @@ type SessionFeedback struct {
Timestamp string `json:"timestamp"`
}
// SessionFeedbacks represents multiple session feedbacks
// SessionFeedbacks represents multiple session feedbacks.
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
}
+27 -13
View File
@@ -1,54 +1,68 @@
// Package models provides data models for the Yandex Music API.
package models
// SearchResult represents search results
// 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
// SearchTracksResult represents track search results.
type SearchTracksResult struct {
Total int `json:"total"`
Results []Track `json:"results"`
}
// SearchAlbumsResult represents album search results
// SearchAlbumsResult represents album search results.
type SearchAlbumsResult struct {
Total int `json:"total"`
Results []Album `json:"results"`
}
// SearchArtistsResult represents artist search results
// SearchArtistsResult represents artist search results.
type SearchArtistsResult struct {
Total int `json:"total"`
Results []Artist `json:"results"`
}
// SearchPlaylistsResult represents playlist search results
// SearchPlaylistsResult represents playlist search results.
type SearchPlaylistsResult struct {
Total int `json:"total"`
Results []Playlist `json:"results"`
}
// SearchVideosResult represents video search results
// SearchVideosResult represents video search results.
type SearchVideosResult struct {
Total int `json:"total"`
Results []Video `json:"results"`
}
// SearchUsersResult represents user search results
// SearchUsersResult represents user search results.
type SearchUsersResult struct {
Total int `json:"total"`
Results []User `json:"results"`
}
// SearchBanner represents a search banner
// 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"`
@@ -57,19 +71,19 @@ type SearchBanner struct {
Type string `json:"type"`
}
// Tag represents a search tag
// 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"`
Type string `json:"type"`
}
// Video represents a video (minimal)
// 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"`
+20 -19
View File
@@ -1,17 +1,18 @@
// Package models provides data models for the Yandex Music API.
package models
import (
"encoding/json"
)
// Experiments represents experiments
// Experiments represents experiments from the Yandex Music API.
type Experiments struct {
BaseModel
Experiments []ExperimentDetail `json:"experiments"`
Total int `json:"total"`
}
// DeJSON deserializes Experiments from JSON
// DeJSON deserializes Experiments from JSON data.
func (e *Experiments) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -32,7 +33,7 @@ func (e *Experiments) DeJSON(data []byte, client ClientInterface) (Model, error)
return experiments, nil
}
// DeList deserializes a list of Experiments
// DeList deserializes a list of Experiments from JSON data.
func (e *Experiments) DeList(data []byte, client ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -50,10 +51,10 @@ func (e *Experiments) DeList(data []byte, client ClientInterface) ([]Model, erro
return result, nil
}
// ExperimentDetail represents experiment details
// 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"`
@@ -62,7 +63,7 @@ type ExperimentDetail struct {
EndDate string `json:"end_date"`
}
// DeJSON deserializes ExperimentDetail from JSON
// DeJSON deserializes ExperimentDetail from JSON data.
func (e *ExperimentDetail) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -96,7 +97,7 @@ func (e *ExperimentDetail) DeJSON(data []byte, client ClientInterface) (Model, e
return detail, nil
}
// DeList deserializes a list of ExperimentDetails
// DeList deserializes a list of ExperimentDetails from JSON data.
func (e *ExperimentDetail) DeList(data []byte, client ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -114,7 +115,7 @@ func (e *ExperimentDetail) DeList(data []byte, client ClientInterface) ([]Model,
return result, nil
}
// Permissions represents user permissions
// Permissions represents user permissions.
type Permissions struct {
BaseModel
CanPlay bool `json:"can_play"`
@@ -129,7 +130,7 @@ type Permissions struct {
CanUseLossless bool `json:"can_use_lossless"`
}
// DeJSON deserializes Permissions from JSON
// DeJSON deserializes Permissions from JSON data.
func (p *Permissions) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -172,7 +173,7 @@ func (p *Permissions) DeJSON(data []byte, client ClientInterface) (Model, error)
return perms, nil
}
// DeList deserializes a list of Permissions
// DeList deserializes a list of Permissions from JSON data.
func (p *Permissions) DeList(data []byte, client ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -190,14 +191,14 @@ func (p *Permissions) DeList(data []byte, client ClientInterface) ([]Model, erro
return result, nil
}
// PermissionAlerts represents permission alerts
// PermissionAlerts represents permission alerts.
type PermissionAlerts struct {
BaseModel
Alerts []PermissionAlert `json:"alerts"`
Total int `json:"total"`
}
// DeJSON deserializes PermissionAlerts from JSON
// DeJSON deserializes PermissionAlerts from JSON data.
func (p *PermissionAlerts) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -218,7 +219,7 @@ func (p *PermissionAlerts) DeJSON(data []byte, client ClientInterface) (Model, e
return alerts, nil
}
// DeList deserializes a list of PermissionAlerts
// DeList deserializes a list of PermissionAlerts from JSON data. from JSON data.
func (p *PermissionAlerts) DeList(data []byte, client ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -236,10 +237,10 @@ func (p *PermissionAlerts) DeList(data []byte, client ClientInterface) ([]Model,
return result, nil
}
// PermissionAlert represents a permission alert
// 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"`
@@ -248,7 +249,7 @@ type PermissionAlert struct {
CreatedAt string `json:"created_at"`
}
// DeJSON deserializes PermissionAlert from JSON
// DeJSON deserializes PermissionAlert from JSON data.
func (p *PermissionAlert) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -300,7 +301,7 @@ func (p *PermissionAlert) DeList(data []byte, client ClientInterface) ([]Model,
return result, nil
}
// ContentRestrictions represents content restrictions
// ContentRestrictions represents content restrictions.
type ContentRestrictions struct {
BaseModel
HasExplicitContent bool `json:"has_explicit_content"`
@@ -311,7 +312,7 @@ type ContentRestrictions struct {
BlockedArtists []string `json:"blocked_artists"`
}
// DeJSON deserializes ContentRestrictions from JSON
// DeJSON deserializes ContentRestrictions from JSON data.
func (c *ContentRestrictions) DeJSON(data []byte, client ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -342,7 +343,7 @@ func (c *ContentRestrictions) DeJSON(data []byte, client ClientInterface) (Model
return restrictions, nil
}
// DeList deserializes a list of ContentRestrictions
// DeList deserializes a list of ContentRestrictions from JSON data.
func (c *ContentRestrictions) DeList(data []byte, client ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
+20 -19
View File
@@ -1,10 +1,11 @@
// Package models provides data models for the Yandex Music API.
package models
import (
"encoding/json"
)
// LyricsMajor represents a lyrics provider (e.g., Genius, Musixmatch)
// LyricsMajor represents a lyrics provider (e.g., Genius, Musixmatch).
type LyricsMajor struct {
BaseModel
ID int `json:"id"`
@@ -12,7 +13,7 @@ type LyricsMajor struct {
PrettyName string `json:"pretty_name"`
}
// DeJSON deserializes LyricsMajor from JSON
// DeJSON deserializes LyricsMajor from JSON data.
func (lm *LyricsMajor) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -34,7 +35,7 @@ func (lm *LyricsMajor) DeJSON(data []byte, c ClientInterface) (Model, error) {
return major, nil
}
// DeList deserializes a list of LyricsMajor
// DeList deserializes a list of LyricsMajor from JSON data.
func (lm *LyricsMajor) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -52,7 +53,7 @@ func (lm *LyricsMajor) DeList(data []byte, c ClientInterface) ([]Model, error) {
return result, nil
}
// TrackLyrics represents lyrics information from /tracks/{id}/lyrics endpoint
// TrackLyrics represents lyrics information from /tracks/{id}/lyrics endpoint.
type TrackLyrics struct {
BaseModel
DownloadURL string `json:"download_url"`
@@ -62,7 +63,7 @@ type TrackLyrics struct {
Major *LyricsMajor `json:"major"`
}
// DeJSON deserializes TrackLyrics from JSON
// DeJSON deserializes TrackLyrics from JSON data.
func (tl *TrackLyrics) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -92,7 +93,7 @@ func (tl *TrackLyrics) DeJSON(data []byte, c ClientInterface) (Model, error) {
return lyrics, nil
}
// DeList deserializes a list of TrackLyrics
// DeList deserializes a list of TrackLyrics from JSON data.
func (tl *TrackLyrics) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -110,7 +111,7 @@ func (tl *TrackLyrics) DeList(data []byte, c ClientInterface) ([]Model, error) {
return result, nil
}
// SupplementLyrics represents the actual lyrics content (from supplement)
// SupplementLyrics represents the actual lyrics content from supplement.
type SupplementLyrics struct {
BaseModel
ID int `json:"id"`
@@ -123,7 +124,7 @@ type SupplementLyrics struct {
Major *LyricsMajor `json:"major"`
}
// DeJSON deserializes SupplementLyrics from JSON
// DeJSON deserializes SupplementLyrics from JSON data.
func (l *SupplementLyrics) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -162,7 +163,7 @@ func (l *SupplementLyrics) DeJSON(data []byte, c ClientInterface) (Model, error)
return lyrics, nil
}
// DeList deserializes a list of SupplementLyrics
// DeList deserializes a list of SupplementLyrics from JSON data.
func (l *SupplementLyrics) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -180,7 +181,7 @@ func (l *SupplementLyrics) DeList(data []byte, c ClientInterface) ([]Model, erro
return result, nil
}
// LyricsInfo represents lyrics availability information
// LyricsInfo represents lyrics availability information.
type LyricsInfo struct {
BaseModel
TrackID string `json:"track_id"`
@@ -196,7 +197,7 @@ type LyricsInfo struct {
HasAvailableTextLyrics bool `json:"has_available_text_lyrics"`
}
// DeJSON deserializes LyricsInfo from JSON
// DeJSON deserializes LyricsInfo from JSON data.
func (li *LyricsInfo) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -247,7 +248,7 @@ func (li *LyricsInfo) DeJSON(data []byte, c ClientInterface) (Model, error) {
return info, nil
}
// DeList deserializes a list of LyricsInfo
// DeList deserializes a list of LyricsInfo from JSON data.
func (li *LyricsInfo) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -265,7 +266,7 @@ func (li *LyricsInfo) DeList(data []byte, c ClientInterface) ([]Model, error) {
return result, nil
}
// VideoSupplement represents a video supplement (from supplement endpoint)
// VideoSupplement represents a video supplement from supplement endpoint.
type VideoSupplement struct {
BaseModel
Cover string `json:"cover"`
@@ -277,7 +278,7 @@ type VideoSupplement struct {
Embed string `json:"embed"`
}
// DeJSON deserializes VideoSupplement from JSON
// DeJSON deserializes VideoSupplement from JSON data.
func (vs *VideoSupplement) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -311,7 +312,7 @@ func (vs *VideoSupplement) DeJSON(data []byte, c ClientInterface) (Model, error)
return supp, nil
}
// DeList deserializes a list of VideoSupplements
// DeList deserializes a list of VideoSupplements from JSON data.
func (vs *VideoSupplement) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -329,10 +330,10 @@ func (vs *VideoSupplement) DeList(data []byte, c ClientInterface) ([]Model, erro
return result, nil
}
// Supplement represents a track supplement
// 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"`
@@ -340,7 +341,7 @@ type Supplement struct {
Available bool `json:"available"`
}
// DeJSON deserializes Supplement from JSON
// DeJSON deserializes Supplement from JSON data.
func (s *Supplement) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -376,7 +377,7 @@ func (s *Supplement) DeJSON(data []byte, c ClientInterface) (Model, error) {
return supp, nil
}
// DeList deserializes a list of Supplements
// DeList deserializes a list of Supplements from JSON data.
func (s *Supplement) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
+513 -31
View File
@@ -1,33 +1,78 @@
// Package models provides data models for the Yandex Music API.
package models
import (
"encoding/json"
)
// Track represents a full track
// 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"`
}
// DeJSON deserializes a Track from JSON
// DeJSON deserializes a Track from JSON data.
func (t *Track) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -46,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)
@@ -73,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 {
@@ -93,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 {
@@ -111,11 +162,17 @@ 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
}
// DeList deserializes a list of Tracks
// DeList deserializes a list of Tracks from JSON data.
func (t *Track) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -133,7 +190,7 @@ func (t *Track) DeList(data []byte, c ClientInterface) ([]Model, error) {
return result, nil
}
// GetDownloadInfo returns download info for a specific quality
// GetDownloadInfo returns download info for a specific quality, or nil if not found.
func (t *Track) GetDownloadInfo(quality string) *DownloadInfo {
for _, info := range t.DownloadInfo {
if info.Quality == quality {
@@ -143,26 +200,58 @@ 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 info
// 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"`
}
// DownloadInfo represents file download info
// 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.
type DownloadInfo struct {
Quality string `json:"quality"`
Codec string `json:"codec"`
@@ -170,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
}
+14 -13
View File
@@ -1,16 +1,17 @@
// Package models provides data models for the Yandex Music API.
package models
import (
"encoding/json"
)
// TrackShort represents a lightweight track (used in lists)
// 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"`
@@ -19,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
@@ -31,7 +32,7 @@ type TrackShort struct {
RingtoneID string `json:"ringtone_id"`
}
// DeJSON deserializes a TrackShort from JSON
// DeJSON deserializes a TrackShort from JSON data.
func (t *TrackShort) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -50,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)
@@ -104,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
}
@@ -131,7 +132,7 @@ func (t *TrackShort) DeJSON(data []byte, c ClientInterface) (Model, error) {
return track, nil
}
// DeList deserializes a list of TrackShort
// DeList deserializes a list of TrackShort from JSON data.
func (t *TrackShort) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
@@ -149,7 +150,7 @@ func (t *TrackShort) DeList(data []byte, c ClientInterface) ([]Model, error) {
return result, nil
}
// TracksList represents a list of tracks with metadata
// TracksList represents a list of tracks with metadata.
type TracksList struct {
BaseModel
Tracks []TrackShort `json:"tracks"`
@@ -159,7 +160,7 @@ type TracksList struct {
Pager *Pager `json:"pager"`
}
// DeJSON deserializes a TracksList from JSON
// DeJSON deserializes a TracksList from JSON data.
func (t *TracksList) DeJSON(data []byte, c ClientInterface) (Model, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
@@ -193,7 +194,7 @@ func (t *TracksList) DeJSON(data []byte, c ClientInterface) (Model, error) {
return list, nil
}
// DeList deserializes a list of TracksList
// DeList deserializes a list of TracksList from JSON data.
func (t *TracksList) DeList(data []byte, c ClientInterface) ([]Model, error) {
var rawList []json.RawMessage
if err := json.Unmarshal(data, &rawList); err != nil {
+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")
}
}
+23 -12
View File
@@ -1,3 +1,4 @@
// Package utils provides utility functions for the Yandex Music API client.
package utils
import (
@@ -6,42 +7,49 @@ import (
"strings"
)
// JSONBackend defines the interface for JSON encoding/decoding
// JSONBackend defines the interface for JSON encoding/decoding.
// Implementations can use encoding/json, json-iterator, or other backends.
type JSONBackend interface {
// Marshal encodes a Go value to JSON.
Marshal(v any) ([]byte, error)
// Unmarshal decodes JSON data into a Go value.
Unmarshal(data []byte, v any) error
}
// DefaultJSONBackend uses the standard library encoding/json
// DefaultJSONBackend uses the standard library encoding/json.
type DefaultJSONBackend struct{}
// Marshal encodes a Go value to JSON using encoding/json.
func (DefaultJSONBackend) Marshal(v any) ([]byte, error) {
return json.Marshal(v)
}
// Unmarshal decodes JSON data into a Go value using encoding/json.
func (DefaultJSONBackend) Unmarshal(data []byte, v any) error {
return json.Unmarshal(data, v)
}
// GetDefaultJSONBackend returns the default JSON backend
// GetDefaultJSONBackend returns the default JSON backend (encoding/json).
func GetDefaultJSONBackend() JSONBackend {
return DefaultJSONBackend{}
}
// RawMessage is a raw encoded JSON value
// RawMessage is a raw encoded JSON value.
// It is an alias for encoding/json.RawMessage.
type RawMessage = json.RawMessage
// Marshal is a convenience function
// Marshal is a convenience function that marshals a value using the default JSON backend.
func Marshal(v any) ([]byte, error) {
return GetDefaultJSONBackend().Marshal(v)
}
// Unmarshal is a convenience function
// Unmarshal is a convenience function that unmarshals data using the default JSON backend.
func Unmarshal(data []byte, v any) error {
return GetDefaultJSONBackend().Unmarshal(data, v)
}
// ToSnakeCase converts camelCase to snake_case
// ToSnakeCase converts camelCase to snake_case.
// For example, "camelCase" becomes "camel_case".
func ToSnakeCase(s string) string {
if s == "" {
return s
@@ -60,7 +68,8 @@ func ToSnakeCase(s string) string {
return result.String()
}
// ToCamelCase converts snake_case to camelCase
// ToCamelCase converts snake_case to camelCase.
// For example, "snake_case" becomes "snakeCase".
func ToCamelCase(s string) string {
if s == "" {
return s
@@ -79,7 +88,7 @@ func ToCamelCase(s string) string {
return result.String()
}
// MapKeysToSnakeCase converts all keys in a map to snake_case
// MapKeysToSnakeCase converts all keys in a map to snake_case.
func MapKeysToSnakeCase(m map[string]any) map[string]any {
result := make(map[string]any, len(m))
for k, v := range m {
@@ -88,7 +97,7 @@ func MapKeysToSnakeCase(m map[string]any) map[string]any {
return result
}
// MapKeysToCamelCase converts all keys in a map to camelCase
// MapKeysToCamelCase converts all keys in a map to camelCase.
func MapKeysToCamelCase(m map[string]any) map[string]any {
result := make(map[string]any, len(m))
for k, v := range m {
@@ -97,7 +106,8 @@ func MapKeysToCamelCase(m map[string]any) map[string]any {
return result
}
// StructToMap converts a struct to a map using json tags
// StructToMap converts a struct to a map using json tags.
// The forRequest parameter is currently unused but reserved for future use.
func StructToMap(v any, forRequest bool) (map[string]any, error) {
data, err := Marshal(v)
if err != nil {
@@ -110,7 +120,8 @@ func StructToMap(v any, forRequest bool) (map[string]any, error) {
return result, nil
}
// GetJSONTag returns the json tag for a struct field
// GetJSONTag returns the json tag name for a struct field at the given index.
// It returns an empty string if the field has no json tag or the tag is "-".
func GetJSONTag(t reflect.Type, fieldIndex int) string {
if fieldIndex >= t.NumField() {
return ""
+26 -14
View File
@@ -1,3 +1,4 @@
// Package utils provides utility functions for the Yandex Music API client.
package utils
import (
@@ -5,19 +6,20 @@ import (
"errors"
)
// Response represents an API response
// Response represents an API response from the Yandex Music API.
type Response struct {
Result json.RawMessage `json:"result"`
Error string `json:"error,omitempty"`
ErrorDescription string `json:"errorDescription,omitempty"`
}
// GetResult returns the result as raw JSON
// GetResult returns the result as raw JSON.
func (r *Response) GetResult() json.RawMessage {
return r.Result
}
// GetError returns the error message
// GetError returns the error message from the response.
// It returns Error if set, otherwise ErrorDescription, or "Unknown error" if neither is set.
func (r *Response) GetError() string {
if r.Error != "" {
return r.Error
@@ -28,12 +30,13 @@ func (r *Response) GetError() string {
return "Unknown error"
}
// HasError returns true if the response contains an error
// HasError returns true if the response contains an error.
func (r *Response) HasError() bool {
return r.Error != "" || r.ErrorDescription != ""
}
// UnmarshalJSON implements custom unmarshaling
// UnmarshalJSON implements custom unmarshaling for Response.
// If result is missing, the whole response is wrapped in result.
func (r *Response) UnmarshalJSON(data []byte) error {
// First try to unmarshal as a standard response
type Alias Response
@@ -53,7 +56,7 @@ func (r *Response) UnmarshalJSON(data []byte) error {
return nil
}
// DeJSON creates a Response from JSON data
// DeJSON creates a Response from JSON data.
func DeJSON(data []byte) (*Response, error) {
var resp Response
if err := json.Unmarshal(data, &resp); err != nil {
@@ -62,22 +65,30 @@ func DeJSON(data []byte) (*Response, error) {
return &resp, nil
}
// Error types
// Error types for the Yandex Music API client.
var (
// ErrUnauthorized is returned when authentication fails (HTTP 401/403).
ErrUnauthorized = errors.New("unauthorized")
// ErrBadRequest is returned when the request is malformed (HTTP 400).
ErrBadRequest = errors.New("bad request")
// ErrNotFound is returned when the requested resource is not found (HTTP 404).
ErrNotFound = errors.New("not found")
// ErrNetwork is returned for network-related errors.
ErrNetwork = errors.New("network error")
// ErrTimeout is returned when a request times out.
ErrTimeout = errors.New("timeout")
// ErrSchemaMismatch is returned when the API response schema doesn't match expectations.
ErrSchemaMismatch = errors.New("schema mismatch")
)
// YandexMusicError is the base error type
// YandexMusicError is the base error type for Yandex Music API errors.
// It wraps an underlying error with a descriptive message.
type YandexMusicError struct {
Message string
Err error
}
// Error returns the error message, including the wrapped error if present.
func (e *YandexMusicError) Error() string {
if e.Err != nil {
return e.Message + ": " + e.Err.Error()
@@ -85,16 +96,17 @@ func (e *YandexMusicError) Error() string {
return e.Message
}
// Unwrap returns the underlying error for compatibility with errors.Is and errors.As.
func (e *YandexMusicError) Unwrap() error {
return e.Err
}
// NewYandexMusicError creates a new YandexMusicError
// NewYandexMusicError creates a new YandexMusicError with the given message and underlying error.
func NewYandexMusicError(message string, err error) *YandexMusicError {
return &YandexMusicError{Message: message, Err: err}
}
// IsUnauthorizedError checks if error is unauthorized
// IsUnauthorizedError checks if an error is an unauthorized error.
func IsUnauthorizedError(err error) bool {
var yme *YandexMusicError
if errors.As(err, &yme) {
@@ -103,7 +115,7 @@ func IsUnauthorizedError(err error) bool {
return errors.Is(err, ErrUnauthorized)
}
// IsBadRequestError checks if error is bad request
// IsBadRequestError checks if an error is a bad request error.
func IsBadRequestError(err error) bool {
var yme *YandexMusicError
if errors.As(err, &yme) {
@@ -112,7 +124,7 @@ func IsBadRequestError(err error) bool {
return errors.Is(err, ErrBadRequest)
}
// IsNotFoundError checks if error is not found
// IsNotFoundError checks if an error is a not found error.
func IsNotFoundError(err error) bool {
var yme *YandexMusicError
if errors.As(err, &yme) {
@@ -121,7 +133,7 @@ func IsNotFoundError(err error) bool {
return errors.Is(err, ErrNotFound)
}
// IsNetworkError checks if error is network error
// IsNetworkError checks if an error is a network error.
func IsNetworkError(err error) bool {
var yme *YandexMusicError
if errors.As(err, &yme) {
@@ -130,7 +142,7 @@ func IsNetworkError(err error) bool {
return errors.Is(err, ErrNetwork)
}
// IsTimeoutError checks if error is timeout
// IsTimeoutError checks if an error is a timeout error.
func IsTimeoutError(err error) bool {
var yme *YandexMusicError
if errors.As(err, &yme) {
+27 -6
View File
@@ -1,3 +1,5 @@
// Package utils provides utility functions for the Yandex Music API client,
// including JSON handling, request signing, schema mismatch tracking, and error types.
package utils
import (
@@ -7,16 +9,24 @@ import (
"sync"
)
// SchemaMismatch represents a mismatch between expected and actual API response
// SchemaMismatch represents a mismatch between expected and actual API response.
// It captures details about missing fields, unknown fields, and unknown values.
type SchemaMismatch struct {
// ModelName is the name of the model that had the mismatch.
ModelName string
// MissingFields lists fields that were expected but not found in the response.
MissingFields []string
// UnknownFields lists fields that were not expected but found in the response.
UnknownFields []string
// UnknownValues maps unknown field names to their values.
UnknownValues map[string]any
// Endpoint is the API endpoint where the mismatch occurred.
Endpoint string
// LibraryVersion is the version of the library when the mismatch occurred.
LibraryVersion string
}
// Describe returns a human-readable description of the schema mismatch.
func (sm *SchemaMismatch) Describe() string {
var parts []string
parts = append(parts, fmt.Sprintf("Model: %s", sm.ModelName))
@@ -34,17 +44,22 @@ func (sm *SchemaMismatch) Describe() string {
return strings.Join(parts, "\n")
}
// SchemaMismatchHandler is a callback for handling schema mismatches
// SchemaMismatchHandler is a callback for handling schema mismatches.
// It is invoked when a schema mismatch is detected, if configured.
type SchemaMismatchHandler func(*SchemaMismatch)
// SchemaMismatchConfig holds configuration for schema mismatch handling
// SchemaMismatchConfig holds configuration for schema mismatch handling.
type SchemaMismatchConfig struct {
// ReportUnknownFields indicates whether unknown fields should be reported.
ReportUnknownFields bool
// Strict indicates whether schema mismatches should be treated as errors.
Strict bool
// OnSchemaMismatch is an optional callback for custom mismatch handling.
OnSchemaMismatch SchemaMismatchHandler
}
// DefaultSchemaMismatchConfig returns the default configuration
// DefaultSchemaMismatchConfig returns the default configuration for schema mismatch handling.
// The default config does not report unknown fields, is not strict, and has no callback.
func DefaultSchemaMismatchConfig() SchemaMismatchConfig {
return SchemaMismatchConfig{
ReportUnknownFields: false,
@@ -53,7 +68,9 @@ func DefaultSchemaMismatchConfig() SchemaMismatchConfig {
}
}
// ReportSchemaMismatch reports a schema mismatch according to config
// ReportSchemaMismatch reports a schema mismatch according to the provided config.
// If a callback is configured, it is invoked with the mismatch details.
// If strict mode is enabled, an error is returned. Otherwise, a warning is logged.
func ReportSchemaMismatch(config SchemaMismatchConfig, modelName, endpoint string, missingFields, unknownFields []string, unknownValues map[string]any) error {
mismatch := &SchemaMismatch{
ModelName: modelName,
@@ -78,18 +95,22 @@ func ReportSchemaMismatch(config SchemaMismatchConfig, modelName, endpoint strin
return nil
}
// SchemaMismatchTracker tracks reported mismatches to avoid duplicates
// SchemaMismatchTracker tracks reported mismatches to avoid duplicate reports.
// It is safe for concurrent use.
type SchemaMismatchTracker struct {
mu sync.Mutex
reported map[string]bool
}
// NewSchemaMismatchTracker creates a new SchemaMismatchTracker.
func NewSchemaMismatchTracker() *SchemaMismatchTracker {
return &SchemaMismatchTracker{
reported: make(map[string]bool),
}
}
// ShouldReport returns true if the given model and fields have not been reported before.
// It marks them as reported so subsequent calls return false for the same key.
func (t *SchemaMismatchTracker) ShouldReport(modelName string, fields []string) bool {
t.mu.Lock()
defer t.mu.Unlock()
+12 -6
View File
@@ -1,3 +1,4 @@
// Package utils provides utility functions for the Yandex Music API client.
package utils
import (
@@ -10,21 +11,23 @@ import (
)
const (
// DefaultSignKey is the signing key from the Android app
// DefaultSignKey is the signing key from the Android app.
DefaultSignKey = "p93jhgh689SBReK6ghtw62"
// DesktopSignKey is the signing key from the desktop app
// DesktopSignKey is the signing key from the desktop app.
DesktopSignKey = "kzqU4XhfCaY6B6JTHODeq5"
// DesktopClient is the X-Yandex-Music-Client header value for desktop
// DesktopClient is the X-Yandex-Music-Client header value for desktop.
DesktopClient = "YandexMusicDesktopAppWindows/5.95.0"
)
// Sign represents a request signature
// Sign represents a request signature for signed API requests.
type Sign struct {
Timestamp int64
Value string
}
// GetSignRequest creates a signature for a track request
// GetSignRequest creates a signature for a track request using HMAC-SHA256.
// It returns a Sign with the timestamp and base64-encoded signature value.
// If key is empty, DefaultSignKey is used.
func GetSignRequest(trackID string, key string) Sign {
if key == "" {
key = DefaultSignKey
@@ -43,7 +46,9 @@ func GetSignRequest(trackID string, key string) Sign {
}
}
// GetFileInfoSign creates a signature for file info requests
// GetFileInfoSign creates a signature for file info requests using HMAC-SHA256.
// It returns a Sign with the timestamp and base64-encoded signature value (last character removed as in Python).
// If key is empty, DesktopSignKey is used.
func GetFileInfoSign(trackID, quality string, codecs []string, transport, key string) Sign {
if key == "" {
key = DesktopSignKey
@@ -66,6 +71,7 @@ func GetFileInfoSign(trackID, quality string, codecs []string, transport, key st
}
}
// joinCodecs joins a list of codec strings into a single string.
func joinCodecs(codecs []string) string {
result := ""
for _, c := range codecs {
+23 -19
View File
@@ -1,3 +1,4 @@
// Package ynison provides a Ynison WebSocket client for player synchronization across devices.
package ynison
import (
@@ -12,7 +13,7 @@ import (
"github.com/gorilla/websocket"
)
// YnisonClient represents a Ynison WebSocket client
// YnisonClient represents a Ynison WebSocket client for player synchronization across devices.
type YnisonClient struct {
client *client.Client
conn *websocket.Conn
@@ -28,7 +29,7 @@ type YnisonClient struct {
closeOnce sync.Once // Ensures doneCh is closed only once
}
// DeviceConfig holds device configuration for Ynison
// DeviceConfig holds device configuration for Ynison WebSocket connections.
type DeviceConfig struct {
DeviceID string
Title string
@@ -40,7 +41,8 @@ type DeviceConfig struct {
VolumeGran int
}
// NewYnisonClient creates a new Ynison client
// NewYnisonClient creates a new Ynison client with the given client and device config.
// Default values are applied for empty fields.
func NewYnisonClient(c *client.Client, config DeviceConfig) *YnisonClient {
if config.DeviceID == "" {
config.DeviceID = fmt.Sprintf("go-ynison-%d", time.Now().UnixNano())
@@ -67,7 +69,8 @@ func NewYnisonClient(c *client.Client, config DeviceConfig) *YnisonClient {
}
}
// Connect establishes a WebSocket connection to Ynison
// Connect establishes a WebSocket connection to the Ynison service.
// It sends an initial full state update after connecting.
func (y *YnisonClient) Connect(ctx context.Context) error {
token := y.client.GetToken()
if token == "" {
@@ -95,12 +98,12 @@ func (y *YnisonClient) Connect(ctx context.Context) error {
return y.sendUpdateFullState(ctx)
}
// overrideWSURL sets a custom WebSocket URL for testing
// overrideWSURL sets a custom WebSocket URL for testing purposes.
func (y *YnisonClient) overrideWSURL(url string) {
y.wsURL = url
}
// Disconnect closes the WebSocket connection
// Disconnect closes the WebSocket connection and cleans up resources.
func (y *YnisonClient) Disconnect() error {
y.mu.Lock()
defer y.mu.Unlock()
@@ -117,7 +120,7 @@ func (y *YnisonClient) Disconnect() error {
return nil
}
// IsConnected returns true if the client is connected
// IsConnected returns true if the client is currently connected to the Ynison service.
func (y *YnisonClient) IsConnected() bool {
y.mu.Lock()
defer y.mu.Unlock()
@@ -276,21 +279,22 @@ func (y *YnisonClient) sendRequest(ctx context.Context, req *proto.PutYnisonStat
return y.conn.WriteMessage(websocket.TextMessage, data)
}
// RegisterHandler registers a response handler
// RegisterHandler registers a response handler for the given name.
// Handlers are invoked when a PutYnisonStateResponse is received from the server.
func (y *YnisonClient) RegisterHandler(name string, handler func(*proto.PutYnisonStateResponse)) {
y.mu.Lock()
defer y.mu.Unlock()
y.handlers[name] = handler
}
// UnregisterHandler removes a response handler
// UnregisterHandler removes a previously registered response handler by name.
func (y *YnisonClient) UnregisterHandler(name string) {
y.mu.Lock()
defer y.mu.Unlock()
delete(y.handlers, name)
}
// UpdatePlayingStatus updates the playing status
// UpdatePlayingStatus updates the playing status on the Ynison server.
func (y *YnisonClient) UpdatePlayingStatus(ctx context.Context, status *proto.PlayingStatus) error {
req := &proto.PutYnisonStateRequest{
UpdatePlayingStatus: &proto.UpdatePlayingStatus{
@@ -302,7 +306,7 @@ func (y *YnisonClient) UpdatePlayingStatus(ctx context.Context, status *proto.Pl
return y.sendRequest(ctx, req)
}
// UpdateVolume updates the volume
// UpdateVolume updates the volume for a device on the Ynison server.
func (y *YnisonClient) UpdateVolume(ctx context.Context, deviceID string, volume float64, version *proto.UpdateVersion) error {
req := &proto.PutYnisonStateRequest{
UpdateVolumeInfo: &proto.UpdateVolumeInfo{
@@ -318,7 +322,7 @@ func (y *YnisonClient) UpdateVolume(ctx context.Context, deviceID string, volume
return y.sendRequest(ctx, req)
}
// UpdateActiveDevice updates the active device
// UpdateActiveDevice updates the active device on the Ynison server.
func (y *YnisonClient) UpdateActiveDevice(ctx context.Context, deviceID string) error {
req := &proto.PutYnisonStateRequest{
UpdateActiveDevice: &proto.UpdateActiveDevice{
@@ -330,7 +334,7 @@ func (y *YnisonClient) UpdateActiveDevice(ctx context.Context, deviceID string)
return y.sendRequest(ctx, req)
}
// UpdatePlayerState updates the player state (queue, repeat, shuffle)
// UpdatePlayerState updates the player state (queue, repeat, shuffle) on the Ynison server.
func (y *YnisonClient) UpdatePlayerState(ctx context.Context, state *proto.PlayerState) error {
req := &proto.PutYnisonStateRequest{
UpdatePlayerState: &proto.UpdatePlayerState{
@@ -342,7 +346,7 @@ func (y *YnisonClient) UpdatePlayerState(ctx context.Context, state *proto.Playe
return y.sendRequest(ctx, req)
}
// UpdatePlayerQueueInject updates the injected queue entity
// UpdatePlayerQueueInject updates the injected queue entity on the Ynison server.
func (y *YnisonClient) UpdatePlayerQueueInject(ctx context.Context, inject *proto.PlayerQueueInject) error {
req := &proto.PutYnisonStateRequest{
UpdatePlayerQueueInject: &proto.UpdatePlayerQueueInject{
@@ -354,7 +358,7 @@ func (y *YnisonClient) UpdatePlayerQueueInject(ctx context.Context, inject *prot
return y.sendRequest(ctx, req)
}
// SyncStateFromEOV requests synchronization from EOV
// SyncStateFromEOV requests synchronization from EOV (End of Verification).
func (y *YnisonClient) SyncStateFromEOV(ctx context.Context, actualQueueID string) error {
req := &proto.PutYnisonStateRequest{
SyncStateFromEov: &proto.SyncStateFromEOV{
@@ -366,25 +370,25 @@ func (y *YnisonClient) SyncStateFromEOV(ctx context.Context, actualQueueID strin
return y.sendRequest(ctx, req)
}
// GetDevices returns the list of connected devices from the last response
// GetDevices returns the list of connected devices from the last response.
func (y *YnisonClient) GetDevices() []*proto.Device {
// This would need to be implemented with proper state tracking
return nil
}
// GetActiveDeviceID returns the active device ID from the last response
// GetActiveDeviceID returns the active device ID from the last response.
func (y *YnisonClient) GetActiveDeviceID() string {
// This would need to be implemented with proper state tracking
return ""
}
// GetPlayerState returns the current player state from the last response
// GetPlayerState returns the current player state from the last response.
func (y *YnisonClient) GetPlayerState() *proto.PlayerState {
// This would need to be implemented with proper state tracking
return nil
}
// WaitForConnection waits for the connection to be established or context to be done
// WaitForConnection waits for the connection to be established or the context to be done.
func (y *YnisonClient) WaitForConnection(ctx context.Context) error {
select {
case <-ctx.Done():
+9 -7
View File
@@ -1,3 +1,4 @@
// Package ynison provides a Ynison WebSocket client for player synchronization.
package ynison
import (
@@ -6,7 +7,8 @@ import (
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/ynison/proto"
)
// CreatePlayingStatus creates a playing status
// CreatePlayingStatus creates a playing status with the given parameters.
// The version timestamp is set to the current time.
func CreatePlayingStatus(progressMs, durationMs int64, paused bool, playbackSpeed float64, deviceID string) *proto.PlayingStatus {
return &proto.PlayingStatus{
ProgressMs: progressMs,
@@ -21,7 +23,7 @@ func CreatePlayingStatus(progressMs, durationMs int64, paused bool, playbackSpee
}
}
// CreatePlayable creates a playable entity
// CreatePlayable creates a playable entity with the given parameters.
func CreatePlayable(playableID, albumID, from, title, coverURL string, playableType proto.PlayablePlayableType) *proto.Playable {
return &proto.Playable{
PlayableId: playableID,
@@ -33,7 +35,7 @@ func CreatePlayable(playableID, albumID, from, title, coverURL string, playableT
}
}
// CreateTrackPlayable creates a track playable
// CreateTrackPlayable creates a track playable with the given parameters.
func CreateTrackPlayable(playableID, albumID, from, title, coverURL string, trackSourceKey uint32, batchID string) *proto.Playable {
return &proto.Playable{
PlayableId: playableID,
@@ -49,7 +51,7 @@ func CreateTrackPlayable(playableID, albumID, from, title, coverURL string, trac
}
}
// CreateVideoClipPlayable creates a video clip playable
// CreateVideoClipPlayable creates a video clip playable with the given parameters.
func CreateVideoClipPlayable(playableID, from, title, coverURL string, recommendationType proto.VideoClipInfoRecommendationType) *proto.Playable {
return &proto.Playable{
PlayableId: playableID,
@@ -63,7 +65,7 @@ func CreateVideoClipPlayable(playableID, from, title, coverURL string, recommend
}
}
// CreatePlayerQueue creates a player queue
// CreatePlayerQueue creates a player queue with the given parameters.
func CreatePlayerQueue(entityID string, entityType proto.PlayerQueueEntityType, playables []*proto.Playable, currentIndex int32, deviceID string) *proto.PlayerQueue {
return &proto.PlayerQueue{
EntityId: entityID,
@@ -82,7 +84,7 @@ func CreatePlayerQueue(entityID string, entityType proto.PlayerQueueEntityType,
}
}
// CreateWaveQueue creates a wave queue
// CreateWaveQueue creates a wave queue with the given parameters.
func CreateWaveQueue(recommended []*proto.Playable, liveIndex int32, sessionID string, deviceID string) *proto.PlayerQueueQueue {
// Build track sources from playables
var trackSources []*proto.PlayerQueueQueueWaveQueueEntityOptionsTrackSourceWithKey
@@ -111,7 +113,7 @@ func CreateWaveQueue(recommended []*proto.Playable, liveIndex int32, sessionID s
}
}
// CreateGenerativeQueue creates a generative queue
// CreateGenerativeQueue creates a generative queue with the given ID.
func CreateGenerativeQueue(id string) *proto.PlayerQueueQueue {
return &proto.PlayerQueueQueue{
GenerativeQueue: &proto.PlayerQueueQueueGenerativeQueue{
+107 -99
View File
@@ -1,3 +1,5 @@
// Package yandexmusic provides the public API for the Yandex Music Go client.
// It re-exports types and functions from the internal packages for convenient use.
package yandexmusic
import (
@@ -7,307 +9,313 @@ import (
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/ynison"
)
// ClientConfig holds configuration for the Client
// ClientConfig holds configuration for the Client.
type ClientConfig = client.ClientConfig
// Client is the main Yandex Music API client
// Client is the main Yandex Music API client.
type Client = client.Client
// NewClient creates a new Yandex Music client
// NewClient creates a new Yandex Music client with the given configuration.
func NewClient(config ClientConfig) *Client {
return client.NewClient(config)
}
// Request is the HTTP request handler
// Request is the HTTP request handler.
type Request = client.Request
// DeviceCode represents the device authorization response
// DeviceCode represents the device authorization response.
type DeviceCode = client.DeviceCode
// OAuthToken represents an OAuth token response
// OAuthToken represents an OAuth token response.
type OAuthToken = client.OAuthToken
// Status represents account status
// Status represents account status.
type Status = client.Status
// Account represents a user account
// Account represents a user account.
type Account = client.Account
// Track represents a music track
// Track represents a music track.
type Track = models.Track
// Artist represents an artist
// Artist represents an artist.
type Artist = models.Artist
// Album represents an album
// Album represents an album.
type Album = models.Album
// Playlist represents a playlist
// Playlist represents a playlist.
type Playlist = models.Playlist
// User represents a user
// User represents a user.
type User = models.User
// DownloadInfo represents file download info
// DownloadInfo represents file download info.
type DownloadInfo = models.DownloadInfo
// SupplementLyrics represents lyrics content from supplement
// SupplementLyrics represents lyrics content from supplement.
type SupplementLyrics = models.SupplementLyrics
// Major represents major label info
// Major represents major label info.
type Major = models.Major
// Label represents a label
// Label represents a label.
type Label = models.Label
// Availability represents availability info
// Availability represents availability info.
type Availability = models.Availability
// Upsell represents upsell info
// Upsell represents upsell info.
type Upsell = models.Upsell
// AlbumMeta represents album metadata
// AlbumMeta represents album metadata.
type AlbumMeta = models.AlbumMeta
// Best represents best tracks info
// Best represents best tracks info.
type Best = models.Best
// Ratings represents artist ratings
// Ratings represents artist ratings.
type Ratings = models.Ratings
// Counts represents artist counts
// Counts represents artist counts.
type Counts = models.Counts
// TrackShort represents a lightweight track
// TrackShort represents a lightweight track.
type TrackShort = models.TrackShort
// TracksList represents a list of tracks with metadata
// TracksList represents a list of tracks with metadata.
type TracksList = models.TracksList
// Pager represents pagination info
// Pager represents pagination info.
type Pager = models.Pager
// TrackPosition represents track position
// TrackPosition represents track position.
type TrackPosition = models.TrackPosition
// TrailerInfo represents trailer info
// TrailerInfo represents trailer info.
type TrailerInfo = models.TrailerInfo
// Cover represents cover image info
// Cover represents cover image info.
type Cover = models.Cover
// ArtistBriefInfo represents brief artist info
// ArtistBriefInfo represents brief artist info.
type ArtistBriefInfo = models.ArtistBriefInfo
// ArtistDonations represents artist donations info
// ArtistDonations represents artist donations info.
type ArtistDonations = models.ArtistDonations
// DonationItem represents a donation item
// DonationItem represents a donation item.
type DonationItem = models.DonationItem
// Concert represents a concert (minimal)
// Concert represents a concert (minimal).
type Concert = models.Concert
// Clip represents a clip (short video)
// Clip represents a clip (short video).
type Clip = models.Clip
// SearchResult represents search results
// SearchResult represents search results.
type SearchResult = models.SearchResult
// SearchTracksResult represents track search results
// SearchTracksResult represents track search results.
type SearchTracksResult = models.SearchTracksResult
// SearchAlbumsResult represents album search results
// SearchAlbumsResult represents album search results.
type SearchAlbumsResult = models.SearchAlbumsResult
// SearchArtistsResult represents artist search results
// SearchArtistsResult represents artist search results.
type SearchArtistsResult = models.SearchArtistsResult
// SearchPlaylistsResult represents playlist search results
// SearchPlaylistsResult represents playlist search results.
type SearchPlaylistsResult = models.SearchPlaylistsResult
// SearchVideosResult represents video search results
// SearchVideosResult represents video search results.
type SearchVideosResult = models.SearchVideosResult
// SearchUsersResult represents user search results
// SearchUsersResult represents user search results.
type SearchUsersResult = models.SearchUsersResult
// SearchBanner represents a search banner
// SearchBanner represents a search banner.
type SearchBanner = models.SearchBanner
// Tag represents a search tag
// Tag represents a search tag.
type Tag = models.Tag
// Video represents a video
// Video represents a video.
type Video = models.Video
// ChartInfoMenu represents a chart info menu
// ChartInfoMenu represents a chart info menu.
type ChartInfoMenu = models.ChartInfoMenu
// ChartInfoMenuItem represents a chart info menu item
// ChartInfoMenuItem represents a chart info menu item.
type ChartInfoMenuItem = models.ChartInfoMenuItem
// ChartItems represents chart items response
// ChartItems represents chart items response.
type ChartItems = models.ChartItems
// AccountSettings represents account settings
// AccountSettings represents account settings.
type AccountSettings = models.AccountSettings
// Subscription represents subscription info
// Subscription represents subscription info.
type Subscription = models.Subscription
// PlusInfo represents Plus subscription info
// PlusInfo represents Plus subscription info.
type PlusInfo = models.PlusInfo
// PromoCodeStatus represents promo code status
// PromoCodeStatus represents promo code status.
type PromoCodeStatus = models.PromoCodeStatus
// Queue represents a playback queue
// Queue represents a playback queue.
type Queue = models.Queue
// QueueItem represents a queue item
// QueueItem represents a queue item.
type QueueItem = models.QueueItem
// PlayContext represents a play context
// PlayContext represents a play context.
type PlayContext = models.PlayContext
// PlayContextsData represents play contexts data
// PlayContextsData represents play contexts data.
type PlayContextsData = models.PlayContextsData
// Feed represents the main feed
// Feed represents the main feed.
type Feed = models.Feed
// Day represents a day in the feed
// Day represents a day in the feed.
type Day = models.Day
// DayBlock represents a block in a day
// DayBlock represents a block in a day.
type DayBlock = models.DayBlock
// DayBlockData represents data in a day block
// DayBlockData represents data in a day block.
type DayBlockData = models.DayBlockData
// GeneratedPlaylist represents a generated playlist
// GeneratedPlaylist represents a generated playlist.
type GeneratedPlaylist = models.GeneratedPlaylist
// Event represents a feed event
// Event represents a feed event.
type Event = models.Event
// AlbumEvent represents an album event
// AlbumEvent represents an album event.
type AlbumEvent = models.AlbumEvent
// ArtistEvent represents an artist event
// ArtistEvent represents an artist event.
type ArtistEvent = models.ArtistEvent
// TrackWithAds represents a track with ads
// TrackWithAds represents a track with ads.
type TrackWithAds = models.TrackWithAds
// Ad represents an ad
// Ad represents an ad.
type Ad = models.Ad
// AdParams represents ad parameters
// AdParams represents ad parameters.
type AdParams = models.AdParams
// Landing represents a landing page
// Landing represents a landing page.
type Landing = models.Landing
// LandingBlock represents a block in a landing
// LandingBlock represents a block in a landing.
type LandingBlock = models.LandingBlock
// LandingBlockData represents data in a landing block
// LandingBlockData represents data in a landing block.
type LandingBlockData = models.LandingBlockData
// LandingList represents a list of landings
// LandingList represents a list of landings.
type LandingList = models.LandingList
// LyricsInfo represents lyrics information
// LyricsInfo represents lyrics information.
type LyricsInfo = models.LyricsInfo
// VideoSupplement represents video supplement
// VideoSupplement represents video supplement.
type VideoSupplement = models.VideoSupplement
// Supplement represents track supplement
// Supplement represents track supplement.
type Supplement = models.Supplement
// LyricsMajor represents a lyrics provider (e.g., Genius, Musixmatch)
// LyricsMajor represents a lyrics provider (e.g., Genius, Musixmatch).
type LyricsMajor = models.LyricsMajor
// TrackLyrics represents lyrics information from /tracks/{id}/lyrics endpoint
// TrackLyrics represents lyrics information from /tracks/{id}/lyrics endpoint.
type TrackLyrics = models.TrackLyrics
// Credits represents track credits
// Credits represents track credits.
type Credits = models.Credits
// Credit represents a credit item
// Credit represents a credit item.
type Credit = models.Credit
// RadioStation represents a radio station
// ArtistTracks represents a page of tracks by an artist.
type ArtistTracks = models.ArtistTracks
// ArtistAlbums represents a page of albums by an artist.
type ArtistAlbums = models.ArtistAlbums
// RadioStation represents a radio station.
type RadioStation = models.RadioStation
// StationTracksResult represents station tracks result
// StationTracksResult represents station tracks result.
type StationTracksResult = models.StationTracksResult
// RotorStation represents a rotor (generative) station
// RotorStation represents a rotor (generative) station.
type RotorStation = models.RotorStation
// RotorSettings represents rotor station settings
// RotorSettings represents rotor station settings.
type RotorSettings = models.RotorSettings
// RotorStationTracksResult represents rotor station tracks result
// RotorStationTracksResult represents rotor station tracks result.
type RotorStationTracksResult = models.RotorStationTracksResult
// RotorSession represents a rotor session
// RotorSession represents a rotor session.
type RotorSession = models.RotorSession
// SessionFeedback represents session feedback
// SessionFeedback represents session feedback.
type SessionFeedback = models.SessionFeedback
// SessionFeedbacks represents multiple session feedbacks
// SessionFeedbacks represents multiple session feedbacks.
type SessionFeedbacks = models.SessionFeedbacks
// Experiments represents experiments
// Experiments represents experiments.
type Experiments = models.Experiments
// ExperimentDetail represents experiment details
// ExperimentDetail represents experiment details.
type ExperimentDetail = models.ExperimentDetail
// Permissions represents user permissions
// Permissions represents user permissions.
type Permissions = models.Permissions
// PermissionAlerts represents permission alerts
// PermissionAlerts represents permission alerts.
type PermissionAlerts = models.PermissionAlerts
// PermissionAlert represents a permission alert
// PermissionAlert represents a permission alert.
type PermissionAlert = models.PermissionAlert
// ContentRestrictions represents content restrictions
// ContentRestrictions represents content restrictions.
type ContentRestrictions = models.ContentRestrictions
// JSONBackend defines the interface for JSON encoding/decoding
// JSONBackend defines the interface for JSON encoding/decoding.
type JSONBackend = utils.JSONBackend
// GetDefaultJSONBackend returns the default JSON backend
// GetDefaultJSONBackend returns the default JSON backend.
func GetDefaultJSONBackend() JSONBackend {
return utils.GetDefaultJSONBackend()
}
// SchemaMismatchConfig holds configuration for schema mismatch handling
// SchemaMismatchConfig holds configuration for schema mismatch handling.
type SchemaMismatchConfig = utils.SchemaMismatchConfig
// DefaultSchemaMismatchConfig returns the default configuration
// DefaultSchemaMismatchConfig returns the default configuration.
func DefaultSchemaMismatchConfig() SchemaMismatchConfig {
return utils.DefaultSchemaMismatchConfig()
}
// SchemaMismatchHandler is a callback for handling schema mismatches
// SchemaMismatchHandler is a callback for handling schema mismatches.
type SchemaMismatchHandler = utils.SchemaMismatchHandler
// SchemaMismatch represents a mismatch between expected and actual API response
// SchemaMismatch represents a mismatch between expected and actual API response.
type SchemaMismatch = utils.SchemaMismatch
// YnisonClient represents a Ynison WebSocket client
// YnisonClient represents a Ynison WebSocket client.
type YnisonClient = ynison.YnisonClient
// DeviceConfig holds device configuration for Ynison
// DeviceConfig holds device configuration for Ynison.
type DeviceConfig = ynison.DeviceConfig
// Example usage: