From dbb823ade34954227822f93a463c0420b2c307f0 Mon Sep 17 00:00:00 2001 From: Vladimir Zagainov Date: Sun, 18 Oct 2026 11:01:26 +0300 Subject: [PATCH] Fix R128 and remaining ID fields to accept both JSON types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- internal/models/account.go | 2 +- internal/models/artist.go | 8 ++++---- internal/models/base.go | 31 +++++++++++++++++++++++++++++++ internal/models/clip.go | 6 +++--- internal/models/concert.go | 10 +++++----- internal/models/feed.go | 6 +++--- internal/models/label.go | 2 +- internal/models/landing.go | 4 ++-- internal/models/metatag.go | 12 ++++++------ internal/models/music_history.go | 8 ++++---- internal/models/pin.go | 4 ++-- internal/models/queue.go | 4 ++-- internal/models/radio.go | 4 ++-- internal/models/search.go | 6 +++--- internal/models/settings.go | 4 ++-- internal/models/supplement.go | 2 +- internal/models/track.go | 2 +- internal/models/track_short.go | 4 ++-- 18 files changed, 75 insertions(+), 44 deletions(-) diff --git a/internal/models/account.go b/internal/models/account.go index b5a753c..f4c9f99 100644 --- a/internal/models/account.go +++ b/internal/models/account.go @@ -112,7 +112,7 @@ func (a *AccountSettings) DeList(data []byte, c ClientInterface) ([]Model, error // Subscription represents subscription info. type Subscription struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Type string `json:"type"` Status string `json:"status"` StartDate string `json:"start_date"` diff --git a/internal/models/artist.go b/internal/models/artist.go index 31253a1..37c74d4 100644 --- a/internal/models/artist.go +++ b/internal/models/artist.go @@ -8,7 +8,7 @@ import ( // Artist represents a full artist from the Yandex Music API. type Artist struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Name string `json:"name"` CoverURI string `json:"cover_uri"` Genres []string `json:"genres"` @@ -210,7 +210,7 @@ type ArtistDonations struct { // DonationItem represents a donation item. type DonationItem struct { - ID string `json:"id"` + ID JSONID `json:"id"` Title string `json:"title"` Description string `json:"description"` Amount int `json:"amount"` @@ -222,7 +222,7 @@ type DonationItem struct { // ConcertShort represents a concert (minimal for artist). type ConcertShort struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Title string `json:"title"` Date string `json:"date"` Venue string `json:"venue"` @@ -298,7 +298,7 @@ func (c *ConcertShort) DeList(data []byte, client ClientInterface) ([]Model, err // ClipShort represents a clip (minimal for artist). type ClipShort struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Title string `json:"title"` Duration int `json:"duration"` CoverURI string `json:"cover_uri"` diff --git a/internal/models/base.go b/internal/models/base.go index daf3f96..2c680dd 100644 --- a/internal/models/base.go +++ b/internal/models/base.go @@ -110,3 +110,34 @@ func (id *JSONID) UnmarshalJSON(data []byte) error { func (id JSONID) String() string { return string(id) } + +// JSONR128 is a flexible R128 loudness normalization value that accepts both JSON numbers and objects. +// The Yandex Music API sometimes returns r128 as a number (e.g., -23.5) and sometimes +// as an object (e.g., {"value": -23.5}). JSONR128 accepts both and stores as float64. +type JSONR128 float64 + +// UnmarshalJSON implements json.Unmarshaler, accepting both JSON numbers and objects. +func (r *JSONR128) UnmarshalJSON(data []byte) error { + // Try float64 first + var f float64 + if err := json.Unmarshal(data, &f); err == nil { + *r = JSONR128(f) + return nil + } + // Try object with "value" key + var obj struct { + Value float64 `json:"value"` + } + if err := json.Unmarshal(data, &obj); err == nil { + *r = JSONR128(obj.Value) + return nil + } + // Default to 0 if neither format works + *r = 0 + return nil +} + +// Float64 returns the R128 value as a float64. +func (r JSONR128) Float64() float64 { + return float64(r) +} diff --git a/internal/models/clip.go b/internal/models/clip.go index 36f30e7..6d364d1 100644 --- a/internal/models/clip.go +++ b/internal/models/clip.go @@ -8,7 +8,7 @@ import ( // Clip represents a clip (short video) from the Yandex Music API. type Clip struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Title string `json:"title"` Duration int `json:"duration"` CoverURI string `json:"cover_uri"` @@ -132,7 +132,7 @@ type ArtistClipItem struct { // Shot represents a shot (short video). type Shot struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Title string `json:"title"` Description string `json:"description"` Duration int `json:"duration"` @@ -258,7 +258,7 @@ type ShotEvent struct { // ShotType represents a shot type. type ShotType struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Name string `json:"name"` Description string `json:"description"` Icon string `json:"icon"` diff --git a/internal/models/concert.go b/internal/models/concert.go index 317ab8c..b5c22fc 100644 --- a/internal/models/concert.go +++ b/internal/models/concert.go @@ -8,7 +8,7 @@ import ( // Concert represents a concert from the Yandex Music API. type Concert struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Title string `json:"title"` Description string `json:"description"` Date string `json:"date"` @@ -133,7 +133,7 @@ func (c *Concert) DeList(data []byte, client ClientInterface) ([]Model, error) { // ConcertVenue represents a concert venue. type ConcertVenue struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Name string `json:"name"` Address string `json:"address"` City string `json:"city"` @@ -145,7 +145,7 @@ type ConcertVenue struct { // ConcertLocation represents a concert location. type ConcertLocation struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Name string `json:"name"` City string `json:"city"` Country string `json:"country"` @@ -214,7 +214,7 @@ type ConcertMinPrice struct { // ConcertTabConfig represents concert tab config. type ConcertTabConfig struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Title string `json:"title"` Type string `json:"type"` Active bool `json:"active"` @@ -237,7 +237,7 @@ type ConcertSkeleton struct { // ConcertSkeletonBlock represents a concert skeleton block. type ConcertSkeletonBlock struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Type string `json:"type"` Title string `json:"title"` Data json.RawMessage `json:"data"` diff --git a/internal/models/feed.go b/internal/models/feed.go index 2569971..12d12d6 100644 --- a/internal/models/feed.go +++ b/internal/models/feed.go @@ -122,7 +122,7 @@ func (d *Day) DeList(data []byte, c ClientInterface) ([]Model, error) { // DayBlock represents a block in a day. type DayBlock struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Type string `json:"type"` Title string `json:"title"` Description string `json:"description"` @@ -141,7 +141,7 @@ type DayBlockData struct { // GeneratedPlaylist represents a generated playlist. type GeneratedPlaylist struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Title string `json:"title"` Description string `json:"description"` CoverURI string `json:"cover_uri"` @@ -261,7 +261,7 @@ type TrackWithAds struct { // Ad represents an ad. type Ad struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Type string `json:"type"` Title string `json:"title"` Description string `json:"description"` diff --git a/internal/models/label.go b/internal/models/label.go index e553865..44a5ead 100644 --- a/internal/models/label.go +++ b/internal/models/label.go @@ -8,7 +8,7 @@ import ( // Label represents a label from the Yandex Music API. type Label struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Name string `json:"name"` CoverURI string `json:"cover_uri"` Description string `json:"description"` diff --git a/internal/models/landing.go b/internal/models/landing.go index f5823bf..23a7a6a 100644 --- a/internal/models/landing.go +++ b/internal/models/landing.go @@ -8,7 +8,7 @@ import ( // Landing represents a landing page from the Yandex Music API. type Landing struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Title string `json:"title"` Description string `json:"description"` CoverURI string `json:"cover_uri"` @@ -75,7 +75,7 @@ func (l *Landing) DeList(data []byte, c ClientInterface) ([]Model, error) { // LandingBlock represents a block in a landing. type LandingBlock struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Type string `json:"type"` Title string `json:"title"` Description string `json:"description"` diff --git a/internal/models/metatag.go b/internal/models/metatag.go index 419c985..a8becd3 100644 --- a/internal/models/metatag.go +++ b/internal/models/metatag.go @@ -8,7 +8,7 @@ import ( // Metatag represents a metatag from the Yandex Music API. type Metatag struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Title string `json:"title"` Description string `json:"description"` Image string `json:"image"` @@ -81,7 +81,7 @@ type MetatagTree struct { // MetatagLeaf represents a metatag leaf. type MetatagLeaf struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Title string `json:"title"` Description string `json:"description"` Image string `json:"image"` @@ -139,7 +139,7 @@ type MetatagArtistEntry struct { // Genre represents a genre from the Yandex Music API. type Genre struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Title string `json:"title"` Description string `json:"description"` Image string `json:"image"` @@ -205,7 +205,7 @@ func (g *Genre) DeList(data []byte, client ClientInterface) ([]Model, error) { // Chart represents a chart from the Yandex Music API. type Chart struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Title string `json:"title"` Description string `json:"description"` CoverURI string `json:"cover_uri"` @@ -286,7 +286,7 @@ type ChartItem struct { // ChartInfoMenu represents a chart info menu. type ChartInfoMenu struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Title string `json:"title"` Description string `json:"description"` Items []ChartInfoMenuItem `json:"items"` @@ -295,7 +295,7 @@ type ChartInfoMenu struct { // ChartInfoMenuItem represents a chart info menu item. type ChartInfoMenuItem struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Title string `json:"title"` Description string `json:"description"` Action string `json:"action"` diff --git a/internal/models/music_history.go b/internal/models/music_history.go index 505ef6a..1b74987 100644 --- a/internal/models/music_history.go +++ b/internal/models/music_history.go @@ -68,7 +68,7 @@ func (mh *MusicHistory) DeList(data []byte, client ClientInterface) ([]Model, er // MusicHistoryTab represents a music history tab. type MusicHistoryTab struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Title string `json:"title"` Description string `json:"description"` Icon string `json:"icon"` @@ -79,7 +79,7 @@ type MusicHistoryTab struct { // MusicHistoryGroup represents a music history group. type MusicHistoryGroup struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Title string `json:"title"` Description string `json:"description"` Items []MusicHistoryItem `json:"items"` @@ -89,7 +89,7 @@ type MusicHistoryGroup struct { // MusicHistoryItem represents a music history item. type MusicHistoryItem struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Type string `json:"type"` // "track", "album", "artist", "playlist" Track *Track `json:"track"` Album *Album `json:"album"` @@ -116,7 +116,7 @@ type MusicHistoryItemData struct { // MusicHistoryItemID represents music history item ID. type MusicHistoryItemID struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Type string `json:"type"` } diff --git a/internal/models/pin.go b/internal/models/pin.go index bf0d1fe..826ae6f 100644 --- a/internal/models/pin.go +++ b/internal/models/pin.go @@ -8,7 +8,7 @@ import ( // Pin represents a pin from the Yandex Music API. type Pin struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Title string `json:"title"` Description string `json:"description"` Image string `json:"image"` @@ -130,7 +130,7 @@ type Presaves struct { // Presave represents a presave. type Presave struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Title string `json:"title"` Description string `json:"description"` Image string `json:"image"` diff --git a/internal/models/queue.go b/internal/models/queue.go index efd8fed..2212324 100644 --- a/internal/models/queue.go +++ b/internal/models/queue.go @@ -8,7 +8,7 @@ import ( // Queue represents a playback queue from the Yandex Music API. type Queue struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` TrackIDs []string `json:"track_ids"` CurrentIndex int `json:"current_index"` QueueType string `json:"queue_type"` @@ -162,7 +162,7 @@ func (qi *QueueItem) DeList(data []byte, c ClientInterface) ([]Model, error) { // PlayContext represents a play context. type PlayContext struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Type string `json:"type"` // "playlist", "album", "artist", "radio", "search" SourceID string `json:"source_id"` SourceKind string `json:"source_kind"` diff --git a/internal/models/radio.go b/internal/models/radio.go index 35eb54e..a733f1b 100644 --- a/internal/models/radio.go +++ b/internal/models/radio.go @@ -8,7 +8,7 @@ import ( // RadioStation represents a radio station from the Yandex Music API. type RadioStation struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Title string `json:"title"` Description string `json:"description"` CoverURI string `json:"cover_uri"` @@ -122,7 +122,7 @@ func (s *StationTracksResult) DeList(data []byte, client ClientInterface) ([]Mod // RotorStation represents a rotor (generative) station. type RotorStation struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Title string `json:"title"` Description string `json:"description"` CoverURI string `json:"cover_uri"` diff --git a/internal/models/search.go b/internal/models/search.go index af731f2..906b369 100644 --- a/internal/models/search.go +++ b/internal/models/search.go @@ -49,7 +49,7 @@ type SearchUsersResult struct { // SearchBanner represents a search banner. type SearchBanner struct { - ID string `json:"id"` + ID JSONID `json:"id"` Title string `json:"title"` Description string `json:"description"` Image string `json:"image"` @@ -60,7 +60,7 @@ type SearchBanner struct { // Tag represents a search tag. type Tag struct { - ID string `json:"id"` + ID JSONID `json:"id"` Title string `json:"title"` Description string `json:"description"` Count int `json:"count"` @@ -70,7 +70,7 @@ type Tag struct { // Video represents a video (minimal). type Video struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Title string `json:"title"` Duration int `json:"duration"` CoverURI string `json:"cover_uri"` diff --git a/internal/models/settings.go b/internal/models/settings.go index a4c878f..e4134ed 100644 --- a/internal/models/settings.go +++ b/internal/models/settings.go @@ -54,7 +54,7 @@ func (e *Experiments) DeList(data []byte, client ClientInterface) ([]Model, erro // ExperimentDetail represents experiment details. type ExperimentDetail struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Name string `json:"name"` Description string `json:"description"` Status string `json:"status"` @@ -240,7 +240,7 @@ func (p *PermissionAlerts) DeList(data []byte, client ClientInterface) ([]Model, // PermissionAlert represents a permission alert. type PermissionAlert struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Type string `json:"type"` Title string `json:"title"` Description string `json:"description"` diff --git a/internal/models/supplement.go b/internal/models/supplement.go index bea004f..c822b3a 100644 --- a/internal/models/supplement.go +++ b/internal/models/supplement.go @@ -333,7 +333,7 @@ func (vs *VideoSupplement) DeList(data []byte, c ClientInterface) ([]Model, erro // Supplement represents a track supplement. type Supplement struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Lyrics *LyricsInfo `json:"lyrics"` Videos []VideoSupplement `json:"videos"` RadioIsAvailable bool `json:"radio_is_available"` diff --git a/internal/models/track.go b/internal/models/track.go index 1490ff8..4ef73ad 100644 --- a/internal/models/track.go +++ b/internal/models/track.go @@ -20,7 +20,7 @@ type Track struct { Explicit bool `json:"explicit"` Available bool `json:"available"` AvailableForPremiumUsers bool `json:"available_for_premium_users"` - R128 float64 `json:"r128"` + R128 JSONR128 `json:"r128"` TrackSharingFlag string `json:"track_sharing_flag"` Major *Major `json:"major"` Lyrics *TrackLyricsInfo `json:"lyrics"` diff --git a/internal/models/track_short.go b/internal/models/track_short.go index a50a886..db3d1b0 100644 --- a/internal/models/track_short.go +++ b/internal/models/track_short.go @@ -8,7 +8,7 @@ import ( // TrackShort represents a lightweight track used in lists. type TrackShort struct { BaseModel - ID string `json:"id"` + ID JSONID `json:"id"` Title string `json:"title"` DurationMs int `json:"duration_ms"` StorageID string `json:"storage_id"` @@ -20,7 +20,7 @@ type TrackShort struct { Explicit bool `json:"explicit"` Available bool `json:"available"` AvailableForPremiumUsers bool `json:"available_for_premium_users"` - R128 float64 `json:"r128"` + R128 JSONR128 `json:"r128"` TrackSharingFlag string `json:"track_sharing_flag"` Major *Major `json:"major"` Lyrics *SupplementLyrics `json:"lyrics"`