- 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
180 lines
5.2 KiB
Go
180 lines
5.2 KiB
Go
// 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.
|
|
// All model types implement this interface for JSON deserialization and serialization.
|
|
type Model interface {
|
|
// DeJSON deserializes a model from JSON data.
|
|
DeJSON(data []byte, c ClientInterface) (Model, error)
|
|
// DeList deserializes a list of models from JSON data.
|
|
DeList(data []byte, c ClientInterface) ([]Model, error)
|
|
// ToJSON serializes the model to JSON.
|
|
ToJSON() ([]byte, error)
|
|
// ToMap converts the model to a map.
|
|
ToMap() (map[string]any, error)
|
|
}
|
|
|
|
// BaseModel provides common functionality for models, including a client reference.
|
|
type BaseModel struct {
|
|
Client ClientInterface `json:"-"`
|
|
}
|
|
|
|
// 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.
|
|
// 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.
|
|
// 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 {
|
|
return nil, err
|
|
}
|
|
|
|
result := make([]Model, 0, len(rawList))
|
|
for range rawList {
|
|
// This should be overridden by concrete types
|
|
result = append(result, nil)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// 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 by serializing to JSON and unmarshaling into a map.
|
|
func (b *BaseModel) ToMap() (map[string]any, error) {
|
|
data, err := b.ToJSON()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var result map[string]any
|
|
if err := json.Unmarshal(data, &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// SetClient sets the client interface on the model.
|
|
func (b *BaseModel) SetClient(c ClientInterface) {
|
|
b.Client = c
|
|
}
|
|
|
|
// 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
|
|
}
|