spotify-client 1.0.1 → 1.0.2
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.
- checksums.yaml +4 -4
- data/README.md +6 -4
- data/lib/spotify/exceptions.rb +1 -0
- data/lib/spotify/version.rb +1 -1
- data/lib/spotify_client.rb +40 -14
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8531ff35eb3c2054c9d4d99b6e97c5e22848e3805dae010d4be01841e3d535a6
|
|
4
|
+
data.tar.gz: f4775af30bd3ad47662b06ba8ebaefdef6b0d30378773e2ec520c54a4c672a36
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1892e985e4eff8f8f635e9438fc081832a65a6ac8717d73edaa17ee165219bdfcf29838e33b86ea6f06cf77489bedf418a90d06c2f114f1c5fc343e16615560a
|
|
7
|
+
data.tar.gz: 2ff791f2c9c431305935f27345f1aa4e0001c59eaa132a7d4f1d839b3030ee8d8090aa4ee5be0b8aa75492ec113a243e996b6bd119b81340892e2c593c7cc106
|
data/README.md
CHANGED
|
@@ -33,6 +33,7 @@ The CI matrix runs this gem against Ruby `3.2`, `3.3`, `3.4`, `4.0`, and `ruby-h
|
|
|
33
33
|
```ruby
|
|
34
34
|
config = {
|
|
35
35
|
access_token: 'tk',
|
|
36
|
+
app_mode: :development, # optional; use :development to fail fast on dev-mode restricted endpoints
|
|
36
37
|
raise_errors: true,
|
|
37
38
|
retries: 0,
|
|
38
39
|
read_timeout: 10,
|
|
@@ -80,11 +81,12 @@ client.request!(:post, '/v1/some-endpoint', [201], payload, false)
|
|
|
80
81
|
Spotify's Web API changed and removed several legacy endpoints in 2026. This gem now uses current routes while keeping backward-compatible method signatures:
|
|
81
82
|
|
|
82
83
|
- Playlist reads/writes use `/v1/me/playlists` and `/v1/playlists/{playlist_id}/*`.
|
|
83
|
-
- `follow(type, ids)`
|
|
84
|
-
- `artist_top_tracks`
|
|
84
|
+
- `follow(type, ids)` now targets `/v1/me/library` using Spotify URIs (`spotify:{type}:{id}`), while still accepting prebuilt URIs.
|
|
85
|
+
- `artist_top_tracks` uses `/v1/artists/{id}/top-tracks`.
|
|
86
|
+
- `user(user_id)` and `artist_top_tracks` rely on endpoints that Spotify marks unavailable for Development Mode apps (still usable for Extended Quota Mode apps).
|
|
87
|
+
- If initialized with `app_mode: :development`, `user(user_id)` and `artist_top_tracks` raise `Spotify::EndpointUnavailableInDevelopmentMode` before making the HTTP request.
|
|
85
88
|
|
|
86
|
-
-
|
|
87
|
-
- Migration guide: [Spotify Web API Migration Guide](https://developer.spotify.com/documentation/web-api/concepts/migration-guide)
|
|
89
|
+
- February 2026 changes: [Spotify Web API Changes](https://developer.spotify.com/documentation/web-api/references/changes/february-2026)
|
|
88
90
|
|
|
89
91
|
## Development
|
|
90
92
|
|
data/lib/spotify/exceptions.rb
CHANGED
data/lib/spotify/version.rb
CHANGED
data/lib/spotify_client.rb
CHANGED
|
@@ -21,6 +21,7 @@ module Spotify
|
|
|
21
21
|
@retries = config[:retries] || 0
|
|
22
22
|
@read_timeout = config[:read_timeout] || 10
|
|
23
23
|
@write_timeout = config[:write_timeout] || 10
|
|
24
|
+
@app_mode = config[:app_mode].to_s.strip.downcase
|
|
24
25
|
@connection = Excon.new(BASE_URI, persistent: config[:persistent] || false)
|
|
25
26
|
end
|
|
26
27
|
|
|
@@ -53,6 +54,10 @@ module Spotify
|
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
def user(user_id)
|
|
57
|
+
raise_endpoint_unavailable_in_development_mode!(
|
|
58
|
+
endpoint: 'GET /v1/users/{id}',
|
|
59
|
+
replacement: 'GET /v1/me'
|
|
60
|
+
)
|
|
56
61
|
run(:get, "/v1/users/#{user_id}", [200])
|
|
57
62
|
end
|
|
58
63
|
|
|
@@ -66,7 +71,7 @@ module Spotify
|
|
|
66
71
|
|
|
67
72
|
def user_playlist_tracks(_user_id, playlist_id, params = {})
|
|
68
73
|
tracks = { 'items' => [] }
|
|
69
|
-
path = "/v1/playlists/#{playlist_id}/
|
|
74
|
+
path = "/v1/playlists/#{playlist_id}/items"
|
|
70
75
|
|
|
71
76
|
while path
|
|
72
77
|
response = run(:get, path, [200], params)
|
|
@@ -107,7 +112,7 @@ module Spotify
|
|
|
107
112
|
# '1181346016', '7i3thJWDtmX04dJhFwYb0x', [{ uri: 'spotify:track:...', positions: [0] }]
|
|
108
113
|
# )
|
|
109
114
|
def remove_user_tracks_from_playlist(_user_id, playlist_id, tracks)
|
|
110
|
-
run(:delete, "/v1/playlists/#{playlist_id}/
|
|
115
|
+
run(:delete, "/v1/playlists/#{playlist_id}/items", [200], JSON.dump(items: tracks))
|
|
111
116
|
end
|
|
112
117
|
|
|
113
118
|
# Replaces all occurrences of tracks with what's in the playlist
|
|
@@ -116,7 +121,7 @@ module Spotify
|
|
|
116
121
|
# '1181346016', '7i3thJWDtmX04dJhFwYb0x', %w(spotify:track:... spotify:track:...)
|
|
117
122
|
# )
|
|
118
123
|
def replace_user_tracks_in_playlist(_user_id, playlist_id, tracks)
|
|
119
|
-
run(:put, "/v1/playlists/#{playlist_id}/
|
|
124
|
+
run(:put, "/v1/playlists/#{playlist_id}/items", [200, 201], JSON.dump(uris: tracks))
|
|
120
125
|
end
|
|
121
126
|
|
|
122
127
|
# Removes all tracks in playlist
|
|
@@ -135,8 +140,7 @@ module Spotify
|
|
|
135
140
|
end
|
|
136
141
|
|
|
137
142
|
def albums(album_ids)
|
|
138
|
-
|
|
139
|
-
run(:get, '/v1/albums', [200], params)
|
|
143
|
+
{ 'albums' => Array(album_ids).map { |album_id| album(album_id) } }
|
|
140
144
|
end
|
|
141
145
|
|
|
142
146
|
def track(track_id)
|
|
@@ -144,8 +148,7 @@ module Spotify
|
|
|
144
148
|
end
|
|
145
149
|
|
|
146
150
|
def tracks(track_ids)
|
|
147
|
-
|
|
148
|
-
run(:get, '/v1/tracks', [200], params)
|
|
151
|
+
{ 'tracks' => Array(track_ids).map { |track_id| track(track_id) } }
|
|
149
152
|
end
|
|
150
153
|
|
|
151
154
|
def artist(artist_id)
|
|
@@ -153,8 +156,7 @@ module Spotify
|
|
|
153
156
|
end
|
|
154
157
|
|
|
155
158
|
def artists(artist_ids)
|
|
156
|
-
|
|
157
|
-
run(:get, '/v1/artists', [200], params)
|
|
159
|
+
{ 'artists' => Array(artist_ids).map { |artist_id| artist(artist_id) } }
|
|
158
160
|
end
|
|
159
161
|
|
|
160
162
|
def artist_albums(artist_id)
|
|
@@ -166,6 +168,9 @@ module Spotify
|
|
|
166
168
|
raise(ImplementationError, "entity needs to be either artist, album or track, got: #{entity}")
|
|
167
169
|
end
|
|
168
170
|
|
|
171
|
+
options = options.dup
|
|
172
|
+
options[:limit] = [options[:limit].to_i, 10].min if options.key?(:limit)
|
|
173
|
+
|
|
169
174
|
params = {
|
|
170
175
|
q: term.to_s,
|
|
171
176
|
type: entity
|
|
@@ -177,7 +182,8 @@ module Spotify
|
|
|
177
182
|
#
|
|
178
183
|
# +country_id+ is required. An ISO 3166-1 alpha-2 country code.
|
|
179
184
|
def artist_top_tracks(artist_id, country_id)
|
|
180
|
-
|
|
185
|
+
raise_endpoint_unavailable_in_development_mode!(endpoint: 'GET /v1/artists/{id}/top-tracks')
|
|
186
|
+
run(:get, "/v1/artists/#{artist_id}/top-tracks", [200], country: country_id)
|
|
181
187
|
end
|
|
182
188
|
|
|
183
189
|
def related_artists(artist_id)
|
|
@@ -188,16 +194,24 @@ module Spotify
|
|
|
188
194
|
#
|
|
189
195
|
# client.follow('artist', ['0BvkDsjIUla7X0k6CSWh1I'])
|
|
190
196
|
def follow(type, ids)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
197
|
+
entity_type = type.to_s.strip
|
|
198
|
+
uris = Array(ids).map do |id|
|
|
199
|
+
raw = id.to_s
|
|
200
|
+
next raw if raw.start_with?('spotify:')
|
|
201
|
+
|
|
202
|
+
raise(ImplementationError, 'type is required when ids are not full Spotify URIs') if entity_type.empty?
|
|
203
|
+
|
|
204
|
+
"spotify:#{entity_type}:#{raw}"
|
|
205
|
+
end
|
|
206
|
+
run(:put, '/v1/me/library', [200, 204], JSON.dump(uris: uris), false)
|
|
194
207
|
end
|
|
195
208
|
|
|
196
209
|
# Follow a playlist
|
|
197
210
|
#
|
|
198
211
|
# client.follow_playlist('lukebryan', '0obRj9nNySESpFelMCLSya')
|
|
199
212
|
def follow_playlist(_user_id, playlist_id, is_public = true)
|
|
200
|
-
|
|
213
|
+
_is_public = is_public # kept for backward-compatible signature
|
|
214
|
+
run(:put, '/v1/me/library', [200, 204], JSON.dump(uris: ["spotify:playlist:#{playlist_id}"]), false)
|
|
201
215
|
end
|
|
202
216
|
|
|
203
217
|
# Generic API helper for forward compatibility with newly added endpoints.
|
|
@@ -212,6 +226,18 @@ module Spotify
|
|
|
212
226
|
|
|
213
227
|
protected
|
|
214
228
|
|
|
229
|
+
def raise_endpoint_unavailable_in_development_mode!(endpoint:, replacement: nil)
|
|
230
|
+
return unless development_mode?
|
|
231
|
+
|
|
232
|
+
message = "#{endpoint} is unavailable for Spotify Development Mode apps as of March 9, 2026."
|
|
233
|
+
message += " Use #{replacement} instead." if replacement
|
|
234
|
+
raise(EndpointUnavailableInDevelopmentMode, message)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def development_mode?
|
|
238
|
+
@app_mode == 'development' || @app_mode == 'development_mode'
|
|
239
|
+
end
|
|
240
|
+
|
|
215
241
|
def run(verb, path, expected_status_codes, params = {}, idempotent = true)
|
|
216
242
|
run!(verb, path, expected_status_codes, params, idempotent)
|
|
217
243
|
rescue Error => e
|