drum 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,114 @@
1
+ module Drum
2
+ # A album, i.e. a composition of tracks by an artist.
3
+ #
4
+ # @!attribute id
5
+ # @return [String] The (internal) id of the album
6
+ # @!attribute name
7
+ # @return [String] The name of the album
8
+ # @!attribute artist_ids
9
+ # @return [Array<String>] The artist ids of the album
10
+ # @!attribute spotify
11
+ # @return [optional, AlbumSpotify] Spotify-specific metadata
12
+ # @!attribute applemusic
13
+ # @return [optional, AlbumAppleMusic] Apple Music-specific metadata
14
+ Album = Struct.new(
15
+ :id,
16
+ :name,
17
+ :artist_ids,
18
+ :spotify, :applemusic,
19
+ keyword_init: true
20
+ ) do
21
+ def initialize(*)
22
+ super
23
+ self.artist_ids ||= []
24
+ end
25
+
26
+ # Parses an album from a nested Hash that uses string keys.
27
+ #
28
+ # @param [Hash<String, Object>] h The Hash to be parsed
29
+ # @return [Album] The parsed album
30
+ def self.deserialize(h)
31
+ Album.new(
32
+ id: h['id'],
33
+ name: h['name'],
34
+ artist_ids: h['artist_ids'],
35
+ spotify: h['spotify'].try { |s| AlbumSpotify.deserialize(s) },
36
+ applemusic: h['applemusic'].try { |s| AlbumAppleMusic.deserialize(s) }
37
+ )
38
+ end
39
+
40
+ # Serializes the album to a nested Hash that uses string keys.
41
+ #
42
+ # @return [Hash<String, Object>] The serialized representation
43
+ def serialize
44
+ {
45
+ 'id' => self.id,
46
+ 'name' => self.name,
47
+ 'artist_ids' => self.artist_ids,
48
+ 'spotify' => self.spotify&.serialize,
49
+ 'applemusic' => self.applemusic&.serialize
50
+ }.compact
51
+ end
52
+ end
53
+
54
+ # Spotify-specific metadata about the album.
55
+ #
56
+ # @!attribute id
57
+ # @return [String] The id of the album on Spotify
58
+ # @!attribute image_url
59
+ # @return [String] The URL of the album cover art on Spotify
60
+ AlbumSpotify = Struct.new(
61
+ :id,
62
+ :image_url,
63
+ keyword_init: true
64
+ ) do
65
+ # Parses spotify metadata from a Hash that uses string keys.
66
+ #
67
+ # @param [Hash<String, Object>] h The Hash to be parsed
68
+ # @return [AlbumSpotify] The parsed metadata
69
+ def self.deserialize(h)
70
+ AlbumSpotify.new(
71
+ id: h['id'],
72
+ image_url: h['image_url']
73
+ )
74
+ end
75
+
76
+ # Serializes the metadata to a Hash that uses string keys.
77
+ #
78
+ # @return [Hash<String, Object>] The serialized representation
79
+ def serialize
80
+ {
81
+ 'id' => self.id,
82
+ 'image_url' => self.image_url
83
+ }.compact
84
+ end
85
+ end
86
+
87
+ # Apple Music-specific metadata about the album.
88
+ #
89
+ # @!attribute image_url
90
+ # @return [optional, String] The cover image of the album
91
+ AlbumAppleMusic = Struct.new(
92
+ :image_url,
93
+ keyword_init: true
94
+ ) do
95
+ # Parses Apple Music metadata from a Hash that uses string keys.
96
+ #
97
+ # @param [Hash<String, Object>] h The Hash to be parsed
98
+ # @return [AlbumAppleMusic] The parsed metadata
99
+ def self.deserialize(h)
100
+ AlbumAppleMusic.new(
101
+ image_url: h['image_url']
102
+ )
103
+ end
104
+
105
+ # Serializes the metadata to a Hash that uses string keys.
106
+ #
107
+ # @return [Hash<String, Object>] The serialized representation
108
+ def serialize
109
+ {
110
+ 'image_url' => self.image_url
111
+ }.compact
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,72 @@
1
+ module Drum
2
+ # An artist.
3
+ #
4
+ # @!attribute id
5
+ # @return [String] The (internal) id of the artist
6
+ # @!attribute name
7
+ # @return [optional, String] The displayed/formatted name of the artist
8
+ # @!attribute spotify
9
+ # @return [optional, ArtistSpotify] Spotify-specific metadata
10
+ Artist = Struct.new(
11
+ :id,
12
+ :name,
13
+ :spotify,
14
+ keyword_init: true
15
+ ) do
16
+ # Parses an artist from a nested Hash that uses string keys.
17
+ #
18
+ # @param [Hash<String, Object>] h The Hash to be parsed
19
+ # @return [Artist] The parsed artist
20
+ def self.deserialize(h)
21
+ Artist.new(
22
+ id: h['id'],
23
+ name: h['name'],
24
+ spotify: h['spotify'].try { |s| ArtistSpotify.deserialize(s) }
25
+ )
26
+ end
27
+
28
+ # Serializes the artist to a nested Hash that uses string keys.
29
+ #
30
+ # @return [Hash<String, Object>] The serialized representation
31
+ def serialize
32
+ {
33
+ 'id' => self.id,
34
+ 'name' => self.name,
35
+ 'spotify' => self.spotify&.serialize
36
+ }.compact
37
+ end
38
+ end
39
+
40
+ # Spotify-specific metadata about the artist.
41
+ #
42
+ # @!attribute id
43
+ # @return [String] The id of the artist on Spotify
44
+ # @!attribute image_url
45
+ # @return [optional, String] An image of the artist
46
+ ArtistSpotify = Struct.new(
47
+ :id,
48
+ :image_url,
49
+ keyword_init: true
50
+ ) do
51
+ # Parses spotify metadata from a Hash that uses string keys.
52
+ #
53
+ # @param [Hash<String, Object>] h The Hash to be parsed
54
+ # @return [ArtistSpotify] The parsed metadata
55
+ def self.deserialize(h)
56
+ ArtistSpotify.new(
57
+ id: h['id'],
58
+ image_url: h['image_url']
59
+ )
60
+ end
61
+
62
+ # Serializes the metadata to a Hash that uses string keys.
63
+ #
64
+ # @return [Hash<String, Object>] The serialized representation
65
+ def serialize
66
+ {
67
+ 'id' => self.id,
68
+ 'image_url' => self.image_url
69
+ }.compact
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,222 @@
1
+ require 'drum/utils/ext'
2
+ require 'set'
3
+
4
+ module Drum
5
+ # TODO: Smart playlists!
6
+ # TODO: Add created_at/added_at or similar
7
+ # (the Apple Music API provides us with a 'dateAdded', perhaps Spotify has something similar?)
8
+
9
+ # A list of tracks with metadata.
10
+ #
11
+ # @!attribute id
12
+ # @return [String] An internal id for the playlist
13
+ # @!attribute name
14
+ # @return [String] The name of the playlist
15
+ # @!attribute description
16
+ # @return [optional, String] A description of the playlist
17
+ # @!attribute author_id
18
+ # @return [optional, String] The author id
19
+ # @!attribute path
20
+ # @return [optional, Array<String>] The path of parent 'folders' to this playlist.
21
+ # @!attribute users
22
+ # @return [optional, Hash<String, User>] A hash of ids to users used somewhere in the playlist
23
+ # @!attribute artists
24
+ # @return [optional, Hash<String, Artist>] A hash of ids to artists used somewhere in the playlist
25
+ # @!attribute albums
26
+ # @return [optional, Hash<String, Album>] A hash of ids to albums used somewhere in the playlist
27
+ # @!attribute tracks
28
+ # @return [optional, Array<Track>] The list of tracks of the playlist, order matters here
29
+ # @!attribute spotify
30
+ # @return [optional, PlaylistSpotify] Spotify-specific metadata
31
+ # @!attribute applemusic
32
+ # @return [optional, PlaylistAppleMusic] Apple Music-specific metadata
33
+ class Playlist < Struct.new(
34
+ :id, :name, :description,
35
+ :path,
36
+ :author_id,
37
+ :users, :artists, :albums, :tracks,
38
+ :spotify, :applemusic,
39
+ keyword_init: true
40
+ )
41
+ def initialize(*)
42
+ super
43
+ self.description ||= ''
44
+ self.path ||= []
45
+ self.users ||= {}
46
+ self.artists ||= {}
47
+ self.albums ||= {}
48
+ self.tracks ||= []
49
+ end
50
+
51
+ # TODO: Handle merging in the store_x methods?
52
+
53
+ # Stores a user if it does not exist already.
54
+ #
55
+ # @param [User] user The user to store.
56
+ def store_user(user)
57
+ unless self.users.key?(user.id)
58
+ self.users[user.id] = user
59
+ end
60
+ end
61
+
62
+ # Stores an artist if it does not exist already.
63
+ #
64
+ # @param [Artist] artist The artist to store.
65
+ def store_artist(artist)
66
+ unless self.artists.key?(artist.id)
67
+ self.artists[artist.id] = artist
68
+ end
69
+ end
70
+
71
+ # Stores an album if it does not exist already.
72
+ #
73
+ # @param [Album] album The album to store.
74
+ def store_album(album)
75
+ unless self.albums.key?(album.id)
76
+ self.albums[album.id] = album
77
+ end
78
+ end
79
+
80
+ # Stores a track.
81
+ #
82
+ # @param [Track] track The track to store.
83
+ def store_track(track)
84
+ self.tracks << track
85
+ end
86
+
87
+ # Describes a track in a way useful for song matching by search.
88
+ #
89
+ # @return [String] A short description of track name and artists
90
+ def track_search_phrase(track)
91
+ "#{track.name} #{track.artist_ids.filter_map { |i| self.artists[i]&.name }.join(' ')}"
92
+ end
93
+
94
+ # Parses a playlist from a nested Hash that uses string keys.
95
+ #
96
+ # @param [Hash<String, Object>] h The Hash to be parsed
97
+ # @return [Playlist] The parsed playlist
98
+ def self.deserialize(h)
99
+ Playlist.new(
100
+ id: h['id'],
101
+ name: h['name'],
102
+ description: h['description'],
103
+ author_id: h['author_id'],
104
+ path: h['path'],
105
+ users: h['users']&.map { |u| User.deserialize(u) }&.to_h_by_id,
106
+ artists: h['artists']&.map { |a| Artist.deserialize(a) }&.to_h_by_id,
107
+ albums: h['albums']&.map { |a| Album.deserialize(a) }&.to_h_by_id,
108
+ tracks: h['tracks']&.map { |t| Track.deserialize(t) },
109
+ spotify: h['spotify'].try { |s| PlaylistSpotify.deserialize(s) },
110
+ applemusic: h['applemusic'].try { |a| PlaylistAppleMusic.deserialize(a) }
111
+ )
112
+ end
113
+
114
+ # Serializes the playlist to a nested Hash that uses string keys.
115
+ #
116
+ # @return [Hash<String, Object>] The serialized representation
117
+ def serialize
118
+ {
119
+ 'id' => self.id,
120
+ 'name' => self.name,
121
+ 'description' => (self.description unless self.description.empty?),
122
+ 'author_id' => self.author_id,
123
+ 'path' => (self.path unless self.path.empty?),
124
+ 'users' => (self.users.each_value.map { |u| u.serialize } unless self.users.empty?),
125
+ 'artists' => (self.artists.each_value.map { |a| a.serialize } unless self.artists.empty?),
126
+ 'albums' => (self.albums.each_value.map { |a| a.serialize } unless self.albums.empty?),
127
+ 'tracks' => (self.tracks.map { |t| t.serialize } unless self.tracks.empty?),
128
+ 'spotify' => self.spotify&.serialize,
129
+ 'applemusic' => self.applemusic&.serialize
130
+ }.compact
131
+ end
132
+ end
133
+
134
+ # Spotify-specific metadata about the playlist.
135
+ #
136
+ # @!attribute id
137
+ # @return [String] The id of the playlist on Spotify
138
+ # @!attribute public
139
+ # @return [optional, Boolean] Whether the playlist is public on Spotify
140
+ # @!attribute collaborative
141
+ # @return [optional, Boolean] Whether the playlist is collaborative on Spotify
142
+ # @!attribute image_url
143
+ # @return [optional, String] The playlist cover URL
144
+ PlaylistSpotify = Struct.new(
145
+ :id,
146
+ :public, :collaborative,
147
+ :image_url,
148
+ keyword_init: true
149
+ ) do
150
+ # Parses spotify metadata from a Hash that uses string keys.
151
+ #
152
+ # @param [Hash<String, Object>] h The Hash to be parsed
153
+ # @return [PlaylistSpotify] The parsed metadata
154
+ def self.deserialize(h)
155
+ PlaylistSpotify.new(
156
+ id: h['id'],
157
+ public: h['public'],
158
+ collaborative: h['collaborative'],
159
+ image_url: h['image_url']
160
+ )
161
+ end
162
+
163
+ # Serializes the metadata to a Hash that uses string keys.
164
+ #
165
+ # @return [Hash<String, Object>] The serialized representation
166
+ def serialize
167
+ {
168
+ 'id' => self.id,
169
+ 'public' => self.public,
170
+ 'collaborative' => self.collaborative,
171
+ 'image_url' => self.image_url
172
+ }.compact
173
+ end
174
+ end
175
+
176
+ # TODO: Add image URL to Apple Music metadata?
177
+
178
+ # Apple Music-specific metadata about the playlist.
179
+ #
180
+ # @!attribute library_id
181
+ # @return [optional, String] The library-internal id of the playlist
182
+ # @!attribute global_id
183
+ # @return [optional, String] The global id of the playlist (implies that it is available through the catalog API)
184
+ # @!attribute public
185
+ # @return [optional, Boolean] Whether the playlist is public
186
+ # @!attribute editable
187
+ # @return [optional, Boolean] Whether the playlist is editable
188
+ # @!attribute image_url
189
+ # @return [optional, String] The playlist cover image, if present
190
+ PlaylistAppleMusic = Struct.new(
191
+ :library_id, :global_id,
192
+ :public, :editable, :image_url,
193
+ keyword_init: true
194
+ ) do
195
+ # Parses Apple Music metadata from a Hash that uses string keys.
196
+ #
197
+ # @param [Hash<String, Object>] h The Hash to be parsed
198
+ # @return [PlaylistAppleMusic] The parsed metadata
199
+ def self.deserialize(h)
200
+ PlaylistAppleMusic.new(
201
+ library_id: h['library_id'],
202
+ global_id: h['global_id'],
203
+ public: h['public'],
204
+ editable: h['editable'],
205
+ image_url: h['image_url']
206
+ )
207
+ end
208
+
209
+ # Serializes the metadata to a Hash that uses string keys.
210
+ #
211
+ # @return [Hash<String, Object>] The serialized representation
212
+ def serialize
213
+ {
214
+ 'library_id' => self.library_id,
215
+ 'global_id' => self.global_id,
216
+ 'public' => self.public,
217
+ 'editable' => self.editable,
218
+ 'image_url' => self.image_url
219
+ }.compact
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,29 @@
1
+ module Drum
2
+ # A 'half-parsed' reference to a resource, either a token or
3
+ # something else. The specifics are left to the service-specific
4
+ # Ref-parser.
5
+ #
6
+ # @!attribute raw
7
+ # @return [String] The raw text (@-stripped, though, if it's a token)
8
+ # @!attribute is_token
9
+ # @return [Boolean] Whether the ref is a token (i.e. begins with @)
10
+ RawRef = Struct.new(
11
+ :text,
12
+ :is_token,
13
+ keyword_init: true
14
+ ) do
15
+ TOKEN_PREFIX = '@'
16
+
17
+ # Parses a RawRef from the given string.
18
+ #
19
+ # @param [String] raw The raw string to be parsed
20
+ # @return [RawRef] The parsed RawRef
21
+ def self.parse(raw)
22
+ if raw.start_with?(TOKEN_PREFIX)
23
+ RawRef.new(text: raw.delete_prefix(TOKEN_PREFIX), is_token: true)
24
+ else
25
+ RawRef.new(text: raw, is_token: false)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,19 @@
1
+ module Drum
2
+ # A parsed reference to a resource, usually one or multiple playlists.
3
+ # Can be a folder, a library or the like, located on the local machine
4
+ # or a remote service.
5
+ #
6
+ # See the README for examples.
7
+ #
8
+ # @!attribute service_name
9
+ # @return [String] The name of the service
10
+ # @!attribute resource_type
11
+ # @return [Symbol] The type of the resource, service-dependent
12
+ # @!attribute resource_location
13
+ # @return [Object] The path/id of the resource, service-dependent (usually a String or Symbol)
14
+ Ref = Struct.new(
15
+ :service_name,
16
+ :resource_type,
17
+ :resource_location
18
+ )
19
+ end
@@ -0,0 +1,157 @@
1
+
2
+ module Drum
3
+ # TODO: Add Spotify's audio features
4
+
5
+ # A track/song.
6
+ #
7
+ # @!attribute name
8
+ # @return [String] The name of the track
9
+ # @!attribute artist_ids
10
+ # @return [Array<String>] The (internal) artist ids
11
+ # @!attribute composer_ids
12
+ # @return [optional, Array<String>] The (internal) composer ids
13
+ # @!attribute genres
14
+ # @return [optional, Array<String>] The track's genre names
15
+ # @!attribute album_id
16
+ # @return [optional, String] The (internal) album id
17
+ # @!attribute duration_ms
18
+ # @return [optional, Float] The duration of the track in milliseconds
19
+ # @!attribute explicit
20
+ # @return [optional, Boolean] Whether the track is explicit
21
+ # @!attribute released_at
22
+ # @return [optional, DateTime] The date/time the this track was released
23
+ # @!attribute added_at
24
+ # @return [optional, DateTime] The date/time the this track was added to the playlist
25
+ # @!attribute added_by
26
+ # @return [optional, String] The user id of the user who added this track to the playlist
27
+ # @!attribute isrc
28
+ # @return [optional, String] The International Standard Recording Code of this track
29
+ # @!attribute spotify
30
+ # @return [optional, TrackSpotify] Spotify-specific metadata
31
+ # @!attribute applemusic
32
+ # @return [optional, TrackAppleMusic] Apple Music-specific metadata
33
+ Track = Struct.new(
34
+ :name,
35
+ :artist_ids, :composer_ids, :album_id,
36
+ :genres,
37
+ :duration_ms, :explicit,
38
+ :released_at, :added_at, :added_by,
39
+ :isrc, :spotify, :applemusic,
40
+ keyword_init: true
41
+ ) do
42
+ def initialize(*)
43
+ super
44
+ self.artist_ids ||= []
45
+ self.composer_ids ||= []
46
+ self.genres ||= []
47
+ end
48
+
49
+ # Parses a track from a nested Hash that uses string keys.
50
+ #
51
+ # @param [Hash<String, Object>] h The Hash to be parsed
52
+ # @return [Track] The parsed track
53
+ def self.deserialize(h)
54
+ Track.new(
55
+ name: h['name'],
56
+ artist_ids: h['artist_ids'],
57
+ composer_ids: h['composer_ids'],
58
+ genres: h['genres'],
59
+ album_id: h['album_id'],
60
+ duration_ms: h['duration_ms'],
61
+ explicit: h['explicit'],
62
+ released_at: h['released_at'].try { |d| DateTime.parse(d) },
63
+ added_at: h['added_at'].try { |d| DateTime.parse(d) },
64
+ added_by: h['added_by'],
65
+ isrc: h['isrc'],
66
+ spotify: h['spotify'].try { |s| TrackSpotify.deserialize(s) },
67
+ applemusic: h['applemusic'].try { |s| TrackAppleMusic.deserialize(s) }
68
+ )
69
+ end
70
+
71
+ # Serializes the track to a nested Hash that uses string keys.
72
+ #
73
+ # @return [Hash<String, Object>] The serialized representation
74
+ def serialize
75
+ {
76
+ 'name' => self.name,
77
+ 'artist_ids' => self.artist_ids,
78
+ 'composer_ids' => (self.composer_ids unless self.composer_ids.empty?),
79
+ 'genres' => (self.genres unless self.genres.empty?),
80
+ 'album_id' => self.album_id,
81
+ 'duration_ms' => self.duration_ms,
82
+ 'explicit' => self.explicit,
83
+ 'released_at' => self.released_at&.iso8601,
84
+ 'added_at' => self.added_at&.iso8601,
85
+ 'added_by' => self.added_by,
86
+ 'isrc' => self.isrc,
87
+ 'spotify' => self.spotify&.serialize,
88
+ 'applemusic' => self.applemusic&.serialize
89
+ }.compact
90
+ end
91
+ end
92
+
93
+ # Spotify-specific metadata about the track.
94
+ #
95
+ # @!attribute id
96
+ # @return [String] The id of the track on Spotify
97
+ TrackSpotify = Struct.new(
98
+ :id,
99
+ keyword_init: true
100
+ ) do
101
+ # Parses spotify metadata from a Hash that uses string keys.
102
+ #
103
+ # @param [Hash<String, Object>] h The Hash to be parsed
104
+ # @return [TrackSpotify] The parsed metadata
105
+ def self.deserialize(h)
106
+ TrackSpotify.new(
107
+ id: h['id']
108
+ )
109
+ end
110
+
111
+ # Serializes the metadata to a Hash that uses string keys.
112
+ #
113
+ # @return [Hash<String, Object>] The serialized representation
114
+ def serialize
115
+ {
116
+ 'id' => self.id
117
+ }.compact
118
+ end
119
+ end
120
+
121
+ # Apple Music-specific metadata about the track.
122
+ #
123
+ # @!attribute library_id
124
+ # @return [optional, String] The library-internal id of the track
125
+ # @!attribute catalog_id
126
+ # @return [optional, String] The global catalog id of the track
127
+ # @!attribute preview_url
128
+ # @return [optional, String] A short preview of the song audio
129
+ TrackAppleMusic = Struct.new(
130
+ :library_id, :catalog_id,
131
+ :preview_url,
132
+ keyword_init: true
133
+ ) do
134
+ # Parses Apple Music metadata from a Hash that uses string keys.
135
+ #
136
+ # @param [Hash<String, Object>] h The Hash to be parsed
137
+ # @return [TrackAppleMusic] The parsed metadata
138
+ def self.deserialize(h)
139
+ TrackAppleMusic.new(
140
+ library_id: h['library_id'],
141
+ catalog_id: h['catalog_id'],
142
+ preview_url: h['preview_url']
143
+ )
144
+ end
145
+
146
+ # Serializes the metadata to a Hash that uses string keys.
147
+ #
148
+ # @return [Hash<String, Object>] The serialized representation
149
+ def serialize
150
+ {
151
+ 'library_id' => self.library_id,
152
+ 'catalog_id' => self.catalog_id,
153
+ 'preview_url' => self.preview_url
154
+ }.compact
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,72 @@
1
+ module Drum
2
+ # A user.
3
+ #
4
+ # @!attribute id
5
+ # @return [String] The (internal) id of the user
6
+ # @!attribute display_name
7
+ # @return [optional, String] The general formatted name of the user
8
+ # @!attribute spotify
9
+ # @return [optional, UserSpotify] Spotify-specific metadata
10
+ User = Struct.new(
11
+ :id,
12
+ :display_name,
13
+ :spotify,
14
+ keyword_init: true
15
+ ) do
16
+ # Parses a user from a nested Hash that uses string keys.
17
+ #
18
+ # @param [Hash<String, Object>] h The Hash to be parsed
19
+ # @return [User] The parsed user
20
+ def self.deserialize(h)
21
+ User.new(
22
+ id: h['id'],
23
+ display_name: h['display_name'],
24
+ spotify: h['spotify'].try { |s| UserSpotify.deserialize(s) }
25
+ )
26
+ end
27
+
28
+ # Serializes the user to a nested Hash that uses string keys.
29
+ #
30
+ # @return [Hash<String, Object>] The serialized representation
31
+ def serialize
32
+ {
33
+ 'id' => self.id,
34
+ 'display_name' => self.display_name,
35
+ 'spotify' => self.spotify&.serialize
36
+ }.compact
37
+ end
38
+ end
39
+
40
+ # Spotify-specific metadata about the user.
41
+ #
42
+ # @!attribute id
43
+ # @return [String] The id of the artist on Spotify
44
+ # @!attribute image_url
45
+ # @return [optional, String] The profile image of the user
46
+ UserSpotify = Struct.new(
47
+ :id,
48
+ :image_url,
49
+ keyword_init: true
50
+ ) do
51
+ # Parses Spotify metadata from a Hash that uses string keys.
52
+ #
53
+ # @param [Hash<String, Object>] h The Hash to be parsed
54
+ # @return [UserSpotify] The parsed user
55
+ def self.deserialize(h)
56
+ UserSpotify.new(
57
+ id: h['id'],
58
+ image_url: h['image_url']
59
+ )
60
+ end
61
+
62
+ # Serializes the metadata to a Hash that uses string keys.
63
+ #
64
+ # @return [Hash<String, Object>] The serialized representation
65
+ def serialize
66
+ {
67
+ 'id' => self.id,
68
+ 'image_url' => self.image_url
69
+ }.compact
70
+ end
71
+ end
72
+ end