spotify-ruby-kev 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spotify
4
+ class SDK
5
+ class Artist < Model
6
+ ##
7
+ # Do we have the full information for this artist?
8
+ #
9
+ # @example
10
+ # artist = @sdk.connect.playback.artist
11
+ # artist.full_information? # => false
12
+ #
13
+ # @return [FalseClass,TrueClass] is_full_info Does this contain everything?
14
+ #
15
+ def full_information?
16
+ to_h.key?(:images)
17
+ end
18
+
19
+ ##
20
+ # Get full information for this artist by calling /v1/artists/:id
21
+ #
22
+ # @example
23
+ # artist = @sdk.connect.playback.artist
24
+ # artist.retrieve_full_information! unless artist.full_information?
25
+ #
26
+ # @return [TrueClass] success Always returns true.
27
+ #
28
+ def retrieve_full_information!
29
+ unless full_information?
30
+ parent.send_http_request(:get, "/v1/artists/%s" % id).map do |key, value|
31
+ send("%s=" % key, value)
32
+ end
33
+ end
34
+
35
+ true
36
+ end
37
+
38
+ ##
39
+ # Helper method for setting the following status.
40
+ # Requires the `user-follow-modify` scope.
41
+ # If true, PUT /v1/me/following otherwise DELETE /v1/me/following
42
+ #
43
+ # @example
44
+ # @sdk.playback.item.artist.following = true
45
+ # @sdk.playback.item.artist.following = false
46
+ #
47
+ def following=(should_follow)
48
+ raise "#following= must be true or false" unless [true, false].include?(should_follow)
49
+
50
+ should_follow ? follow! : unfollow!
51
+ end
52
+
53
+ ##
54
+ # Follow the artist.
55
+ # Requires the `user-follow-modify` scope.
56
+ # PUT /v1/me/following
57
+ #
58
+ # @example
59
+ # @sdk.playback.item.artist.follow!
60
+ #
61
+ # @return [Spotify::SDK::Artist] self Return the artist object, for chaining methods.
62
+ #
63
+ def follow!
64
+ parent.send_http_request(:put, "/v1/me/following?type=artist&ids=%s" % id, http_options: {expect_nil: true})
65
+ self
66
+ end
67
+
68
+ ##
69
+ # Unfollow the artist.
70
+ # Requires the `user-follow-modify` scope.
71
+ # DELETE /v1/me/following
72
+ #
73
+ # @example
74
+ # @sdk.playback.item.artist.unfollow!
75
+ #
76
+ # @return [Spotify::SDK::Artist] self Return the artist object, for chaining methods.
77
+ #
78
+ def unfollow!
79
+ parent.send_http_request(:delete, "/v1/me/following?type=artist&ids=%s" % id, http_options: {expect_nil: true})
80
+ self
81
+ end
82
+
83
+ ##
84
+ # Display the artist's images. If not obtained, request them from the API.
85
+ #
86
+ # @example
87
+ # artist = @sdk.connect.playback.artist
88
+ # artist.images[0] # => [#<Spotify::SDK::Image>, #<Spotify::SDK::Image>, ...]
89
+ #
90
+ # @return [Array] images Contains a list of images, wrapped in Spotify::SDK::Image
91
+ #
92
+ def images
93
+ retrieve_full_information! unless full_information?
94
+ super.map {|image| Spotify::SDK::Image.new(image, parent) }
95
+ end
96
+
97
+ ##
98
+ # Display the artist's popularity. If not obtained, request them from the API.
99
+ #
100
+ # @example
101
+ # artist = @sdk.connect.playback.artist
102
+ # artist.popularity # => 90
103
+ #
104
+ # @return [Integer] popularity The number of popularity, between 0-100.
105
+ #
106
+ def popularity
107
+ retrieve_full_information! unless full_information?
108
+ super
109
+ end
110
+
111
+ ##
112
+ # Display the artist's genres. If not obtained, request them from the API.
113
+ #
114
+ # @example
115
+ # artist = @sdk.connect.playback.artist
116
+ # artist.genres # => ["hip hop", "pop rap", "rap", ...]
117
+ #
118
+ # @return [Array] genres An array of genres, denoted in strings.
119
+ #
120
+ def genres
121
+ retrieve_full_information! unless full_information?
122
+ super
123
+ end
124
+
125
+ ##
126
+ # Return the Spotify URL for this artist.
127
+ #
128
+ # @example
129
+ # artist = @sdk.connect.playback.artist
130
+ # artist.spotify_url # => "https://open.spotify.com/artist/..."
131
+ #
132
+ # @return [String] spotify_url The URL to open this artist on open.spotify.com
133
+ #
134
+ def spotify_url
135
+ external_urls[:spotify]
136
+ end
137
+
138
+ ##
139
+ # Return the Spotify URI for this artist.
140
+ #
141
+ # @example
142
+ # artist = @sdk.connect.playback.artist
143
+ # artist.spotify_uri # => "spotify:uri:..."
144
+ #
145
+ # @return [String] spotify_uri The URI to open this artist in official apps.
146
+ #
147
+ alias_attribute :spotify_uri, :uri
148
+
149
+ ##
150
+ # Return the followers on Spotify for this artist.
151
+ #
152
+ # @example
153
+ # artist = @sdk.connect.playback.artist
154
+ # artist.followers # => 13913
155
+ #
156
+ # @return [Integer] followers The number of users following this artist.
157
+ #
158
+ def followers
159
+ super[:total]
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spotify
4
+ class SDK
5
+ ##
6
+ # For each SDK component, we have a Base class. We're using HTTParty.
7
+ #
8
+ class Base
9
+ include HTTParty
10
+ base_uri "api.spotify.com:443"
11
+
12
+ ##
13
+ # Initiate a Spotify SDK Base component.
14
+ #
15
+ # @example
16
+ # @sdk = Spotify::SDK.new(@session)
17
+ # @auth = Spotify::SDK::Base.new(@sdk)
18
+ #
19
+ # @sdk = Spotify::SDK.new(@session)
20
+ # @sdk.to_hash # => { access_token: ..., expires_at: ... }
21
+ #
22
+ # @param [Spotify::SDK] parent An instance of Spotify::SDK as a reference point.
23
+ #
24
+ def initialize(parent)
25
+ @parent = parent
26
+ @options = {
27
+ headers: {
28
+ Authorization: "Bearer %s" % @parent.session.access_token
29
+ }
30
+ }
31
+ end
32
+
33
+ ##
34
+ # Handle HTTParty responses.
35
+ #
36
+ # @example
37
+ # # Return the Hash from the JSON response.
38
+ # send_http_request(:get, "/v1/me/player/devices", @options)
39
+ #
40
+ # # Return the raw HTTParty::Response object.
41
+ # send_http_request(:get, "/v1/me/player/devices", @options.merge({http_options: { raw: true }}))
42
+ #
43
+ # # Return true for HTTP requests that return a 200 OK with an empty response.
44
+ # send_http_request(:put, "/v1/me/player/pause", @options.merge({http_options: { expect_nil: true }}))
45
+ #
46
+ # @param [Symbol] method The HTTP method you want to perform. Examples are :get, :post, :put, :delete
47
+ # @param [String] endpoint The HTTP endpoint you'd like to call. Example: /v1/me
48
+ # @param [Hash] override_opts Any headers, HTTParty config or application-specific config (see `http_options`)
49
+ # @return [Hash,HTTParty::Response,TrueClass] response The response from the HTTP request.
50
+ #
51
+ # TODO: Address and fix cyclomatic & code complexity issues by Rubocop.
52
+ # rubocop:disable CyclomaticComplexity, PerceivedComplexity, AbcSize
53
+ def send_http_request(method, endpoint, override_opts={})
54
+ opts = {
55
+ raw: false,
56
+ expect_nil: false
57
+ }.merge(override_opts[:http_options].presence || {})
58
+
59
+ httparty = self.class.send(method, endpoint, @options.merge(override_opts))
60
+ response = httparty.parsed_response
61
+ response = response.try(:deep_symbolize_keys) || response
62
+ raise response[:error][:message] if response.is_a?(Hash) && response[:error].present?
63
+ return httparty if opts[:raw] == true
64
+
65
+ response = opts[:expect_nil] ? true : raise("No response returned") if response.nil?
66
+ response
67
+ end
68
+ # rubocop:enable CyclomaticComplexity, PerceivedComplexity, AbcSize
69
+
70
+ def inspect # :nodoc:
71
+ "#<%s:0x00%x>" % [self.class.name, (object_id << 1)]
72
+ end
73
+
74
+ attr_reader :parent
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spotify
4
+ class SDK
5
+ class Connect < Base
6
+ ##
7
+ # Get the current playback.
8
+ # GET /v1/me/player
9
+ #
10
+ # @example
11
+ # playback = @sdk.connect.playback
12
+ #
13
+ # @see https://developer.spotify.com/console/get-user-player/
14
+ # @see https://developer.spotify.com/documentation/web-api/reference/player/get-information-about-the-users-current-playback/
15
+ #
16
+ # @param [String] market The market you'd like to request.
17
+ # @param [Hash] override_opts Custom options for HTTParty.
18
+ # @return [Spotify::SDK::Connect::PlaybackState] playback_state Return the playback state object.
19
+ #
20
+ def playback(market="from_token", override_opts={})
21
+ playback_state = send_http_request(:get, "/v1/me/player?market=%s" % market, override_opts)
22
+ Spotify::SDK::Connect::PlaybackState.new(playback_state, self)
23
+ end
24
+
25
+ ##
26
+ # Collect all the user's available devices.
27
+ # GET /v1/me/player/devices
28
+ #
29
+ # @example
30
+ # @sdk.connect.devices # => [#<Spotify::SDK::Connect::Device:...>, ...]
31
+ #
32
+ # @see https://developer.spotify.com/console/get-users-available-devices/
33
+ #
34
+ # @param [Hash] override_opts Custom options for HTTParty.
35
+ # @return [Array] devices A list of all devices.
36
+ #
37
+ def devices(override_opts={})
38
+ response = send_http_request(:get, "/v1/me/player/devices", override_opts)
39
+ response[:devices].map do |device|
40
+ Spotify::SDK::Connect::Device.new(device, self)
41
+ end
42
+ end
43
+
44
+ ##
45
+ # Collect all the active devices.
46
+ #
47
+ # @example
48
+ # @sdk.connect.active_devices # => [#<Spotify::SDK::Connect::Device:...>, ...]
49
+ #
50
+ # @see https://developer.spotify.com/console/get-users-available-devices/
51
+ #
52
+ # @param [Hash] override_opts Custom options for HTTParty.
53
+ # @return [Array] devices A list of all devices that are marked as `is_active`.
54
+ #
55
+ def active_devices(override_opts={})
56
+ devices(override_opts).select(&:active?)
57
+ end
58
+
59
+ ##
60
+ # Collect the first active device.
61
+ #
62
+ # @example
63
+ # @sdk.connect.active_device # => #<Spotify::SDK::Connect::Device:...>
64
+ #
65
+ # @see https://developer.spotify.com/console/get-users-available-devices/
66
+ #
67
+ # @param [Hash] override_opts Custom options for HTTParty.
68
+ # @return [Array,NilClass] device The first device with `is_active`. If no device found, returns `nil`.
69
+ #
70
+ def active_device(override_opts={})
71
+ devices(override_opts).find(&:active?)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,362 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spotify
4
+ class SDK
5
+ class Connect
6
+ class Device < Model
7
+ ##
8
+ # Get the device's volume.
9
+ #
10
+ # @example
11
+ # device = @sdk.connect.devices[0]
12
+ # device.volume
13
+ #
14
+ # @return [Integer] volume Get the volume. Between 0 and 100.
15
+ #
16
+ alias_attribute :volume, :volume_percent
17
+
18
+ ##
19
+ # Is the device active?
20
+ #
21
+ # @example
22
+ # device = @sdk.connect.devices[0]
23
+ # device.active?
24
+ #
25
+ # @return [Boolean] is_active Bool of whether device is active.
26
+ #
27
+ alias_attribute :active?, :is_active
28
+
29
+ ##
30
+ # Is the device's session private?
31
+ #
32
+ # @example
33
+ # device = @sdk.connect.devices[0]
34
+ # device.private_session?
35
+ #
36
+ # @return [Boolean] is_private_session Bool of whether device has a private session.
37
+ #
38
+ alias_attribute :private_session?, :is_private_session
39
+
40
+ ##
41
+ # Is the device restricted?
42
+ #
43
+ # @example
44
+ # device = @sdk.connect.devices[0]
45
+ # device.restricted?
46
+ #
47
+ # @return [Boolean] is_restricted Bool of whether device is restricted.
48
+ #
49
+ alias_attribute :restricted?, :is_restricted
50
+
51
+ ##
52
+ # Get the currently playing track.
53
+ # Alias to Spotify::SDK::Connect#playback
54
+ #
55
+ # @example
56
+ # device = @sdk.connect.devices[0]
57
+ # device.playback
58
+ #
59
+ # # Same as calling the following:
60
+ # @sdk.connect.playback
61
+ #
62
+ # @see lib/spotify/sdk/connect.rb
63
+ #
64
+ # @return [Spotify::SDK::Connect::PlaybackState] self Return the playback state object.
65
+ #
66
+ def playback
67
+ parent.playback
68
+ end
69
+
70
+ ##
71
+ # Play the currently playing track on device.
72
+ # PUT /v1/me/player/play
73
+ #
74
+ # @example
75
+ # device = @sdk.connect.devices[0]
76
+ #
77
+ # # Play from a playlist, album from a specific index in that list.
78
+ # # For example, play the 9th item on X playlist.
79
+ # device.play!(
80
+ # index: 5,
81
+ # context: "spotify:album:5ht7ItJgpBH7W6vJ5BqpPr",
82
+ # position_ms: 0
83
+ # )
84
+ #
85
+ # # Play any Spotify URI. Albums, artists, tracks, playlists, and more.
86
+ # device.play!(
87
+ # uri: "spotify:track:5MqkZd7a7u7N7hKMqquL2U",
88
+ # position_ms: 0
89
+ # )
90
+ #
91
+ # # Similar to just uri, but you can define the context.
92
+ # # Useful for playing a track that is part of a playlist, and you want the next
93
+ # # songs to play from that particular context.
94
+ # device.play!(
95
+ # uri: "spotify:track:5MqkZd7a7u7N7hKMqquL2U",
96
+ # context: "spotify:album:5ht7ItJgpBH7W6vJ5BqpPr",
97
+ # position_ms: 0
98
+ # )
99
+ #
100
+ # # Play a track, and immediately seek to 60 seconds.
101
+ # device.play!(
102
+ # index: 5,
103
+ # context: "spotify:album:5ht7ItJgpBH7W6vJ5BqpPr",
104
+ # position_ms: 60 * 1000
105
+ # )
106
+ #
107
+ # @see https://developer.spotify.com/console/put-play/
108
+ #
109
+ # @param [Hash] config The play config you'd like to set. See code examples.
110
+ # @return [Spotify::SDK::Connect::Device] self Return itself, so chained methods can be supported.
111
+ #
112
+ # rubocop:disable AbcSize
113
+ def play!(config)
114
+ payload = case config.keys
115
+ when %i[index context position_ms]
116
+ {context_uri: config[:context],
117
+ offset: {position: config[:index]},
118
+ position_ms: config[:position_ms]}
119
+ when %i[uri position_ms]
120
+ {uris: [config[:uri]],
121
+ position_ms: config[:position_ms]}
122
+ when %i[uri context position_ms]
123
+ {context_uri: config[:context],
124
+ offset: {uri: config[:uri]},
125
+ position_ms: config[:position_ms]}
126
+ else
127
+ raise <<-ERROR.strip_heredoc.strip
128
+ Unrecognized play instructions.
129
+ See https://www.rubydoc.info/github/bih/spotify-ruby/Spotify/SDK/Connect/Device#play!-instance_method for details.
130
+ ERROR
131
+ end
132
+
133
+ parent.send_http_request(:put, "/v1/me/player/play?device_id=%s" % id, http_options: {expect_nil: true},
134
+ body: payload.to_json)
135
+ self
136
+ end
137
+ # rubocop:enable AbcSize
138
+
139
+ ##
140
+ # Resume the currently playing track on device.
141
+ # PUT /v1/me/player/play
142
+ #
143
+ # @example
144
+ # device = @sdk.connect.devices[0]
145
+ # device.resume!
146
+ #
147
+ # @see https://developer.spotify.com/console/put-play/
148
+ #
149
+ # @return [Spotify::SDK::Connect::Device] self Return itself, so chained methods can be supported.
150
+ #
151
+ def resume!
152
+ parent.send_http_request(:put, "/v1/me/player/play?device_id=%s" % id, http_options: {expect_nil: true})
153
+ self
154
+ end
155
+
156
+ ##
157
+ # Pause the currently playing track on device.
158
+ # PUT /v1/me/player/pause
159
+ #
160
+ # @example
161
+ # device = @sdk.connect.devices[0]
162
+ # device.pause!
163
+ #
164
+ # @see https://developer.spotify.com/console/put-pause/
165
+ #
166
+ # @return [Spotify::SDK::Connect::Device] self Return itself, so chained methods can be supported.
167
+ #
168
+ def pause!
169
+ parent.send_http_request(:put, "/v1/me/player/pause?device_id=%s" % id, http_options: {expect_nil: true})
170
+ self
171
+ end
172
+
173
+ ##
174
+ # Skip to previous track on device.
175
+ # POST /v1/me/player/previous
176
+ #
177
+ # @example
178
+ # device = @sdk.connect.devices[0]
179
+ # device.previous!
180
+ #
181
+ # @see https://developer.spotify.com/console/put-previous/
182
+ #
183
+ # @return [Spotify::SDK::Connect::Device] self Return itself, so chained methods can be supported.
184
+ #
185
+ def previous!
186
+ parent.send_http_request(:post, "/v1/me/player/previous?device_id=%s" % id, http_options: {expect_nil: true})
187
+ self
188
+ end
189
+
190
+ ##
191
+ # Skip to next track on device.
192
+ # POST /v1/me/player/next
193
+ #
194
+ # @example
195
+ # device = @sdk.connect.devices[0]
196
+ # device.next!
197
+ #
198
+ # @see https://developer.spotify.com/console/put-next/
199
+ #
200
+ # @return [Spotify::SDK::Connect::Device] self Return itself, so chained methods can be supported.
201
+ #
202
+ def next!
203
+ parent.send_http_request(:post, "/v1/me/player/next?device_id=%s" % id, http_options: {expect_nil: true})
204
+ self
205
+ end
206
+
207
+ ##
208
+ # Set volume for current device.
209
+ # PUT /v1/me/player/volume
210
+ #
211
+ # @example
212
+ # device = @sdk.connect.devices[0]
213
+ # device.change_volume!(30)
214
+ #
215
+ # # or
216
+ #
217
+ # device = @sdk.connect.devices[0]
218
+ # device.volume = 30
219
+ #
220
+ # @see https://developer.spotify.com/console/put-volume/
221
+ #
222
+ # @param [Integer] volume_percent The 0-100 value to change the volume to. 100 is maximum.
223
+ # @return [Spotify::SDK::Connect::Device] self Return itself, so chained methods can be supported.
224
+ #
225
+ def change_volume!(volume_percent)
226
+ raise "Must be an integer" unless volume_percent.is_a?(Integer)
227
+
228
+ endpoint = "/v1/me/player/volume?volume_percent=%i&device_id=%s" % [volume_percent, id]
229
+ opts = {http_options: {expect_nil: true}}
230
+ parent.send_http_request(:put, endpoint, opts)
231
+ self
232
+ end
233
+
234
+ alias_method :volume=, :change_volume!
235
+
236
+ ##
237
+ # Seek position (in milliseconds) for the currently playing track on the device.
238
+ # PUT /v1/me/player/seek
239
+ #
240
+ # @example
241
+ # device = @sdk.connect.devices[0]
242
+ # device.seek_ms!(4000)
243
+ #
244
+ # @see https://developer.spotify.com/console/put-seek/
245
+ #
246
+ # @param [Integer] position_ms In milliseconds, where to seek in the current track on device.
247
+ # @return [Spotify::SDK::Connect::Device] self Return itself, so chained methods can be supported.
248
+ #
249
+ def seek_ms!(position_ms)
250
+ raise "Must be an integer" unless position_ms.is_a?(Integer)
251
+
252
+ endpoint = "/v1/me/player/seek?position_ms=%i&device_id=%s" % [position_ms, id]
253
+ opts = {http_options: {expect_nil: true}}
254
+ parent.send_http_request(:put, endpoint, opts)
255
+ self
256
+ end
257
+
258
+ alias_method :position_ms=, :seek_ms!
259
+
260
+ ##
261
+ # Set repeat mode for current device.
262
+ # PUT /v1/me/player/repeat
263
+ #
264
+ # @example
265
+ # device = @sdk.connect.devices[0]
266
+ # device.repeat!(:track)
267
+ # device.repeat!(:context)
268
+ # device.repeat!(:off)
269
+ #
270
+ # @see https://developer.spotify.com/console/put-repeat/
271
+ #
272
+ # @param [Boolean] state What to set the repeat state to - :track, :context, or :off
273
+ # @return [Spotify::SDK::Connect::Device] self Return itself, so chained methods can be supported.
274
+ #
275
+ def repeat!(state)
276
+ raise "Must be :track, :context, or :off" unless %i[track context off].include?(state)
277
+
278
+ endpoint = "/v1/me/player/repeat?state=%s&device_id=%s" % [state, id]
279
+ opts = {http_options: {expect_nil: true}}
280
+ parent.send_http_request(:put, endpoint, opts)
281
+ self
282
+ end
283
+
284
+ alias_method :repeat=, :repeat!
285
+
286
+ ##
287
+ # Set shuffle for current device.
288
+ # PUT /v1/me/player/shuffle
289
+ #
290
+ # @example
291
+ # device = @sdk.connect.devices[0]
292
+ # device.shuffle!(true)
293
+ #
294
+ # @see https://developer.spotify.com/console/put-shuffle/
295
+ #
296
+ # @param [Boolean] state The true/false of if you'd like to set shuffle on.
297
+ # @return [Spotify::SDK::Connect::Device] self Return itself, so chained methods can be supported.
298
+ #
299
+ def shuffle!(state)
300
+ raise "Must be true or false" unless [true, false].include?(state)
301
+
302
+ endpoint = "/v1/me/player/shuffle?state=%s&device_id=%s" % [state, id]
303
+ opts = {http_options: {expect_nil: true}}
304
+ parent.send_http_request(:put, endpoint, opts)
305
+ self
306
+ end
307
+
308
+ alias_method :shuffle=, :shuffle!
309
+
310
+ ##
311
+ # Transfer a user's playback to another device, and continue playing.
312
+ # PUT /v1/me/player
313
+ #
314
+ # @example
315
+ # device = @sdk.connect.devices[0]
316
+ # device.transfer_playback!
317
+ #
318
+ # @see https://developer.spotify.com/console/transfer-a-users-playback/
319
+ #
320
+ # @return [Spotify::SDK::Connect::Device] self Return itself, so chained methods can be supported.
321
+ #
322
+ def transfer_playback!
323
+ transfer_playback_method(playing: true)
324
+ self
325
+ end
326
+
327
+ ##
328
+ # Transfer a user's playback to another device, and pause.
329
+ # PUT /v1/me/player
330
+ #
331
+ # @example
332
+ # device = @sdk.connect.devices[0]
333
+ # device.transfer_state!
334
+ #
335
+ # @see https://developer.spotify.com/console/transfer-a-users-playback/
336
+ #
337
+ # @return [Spotify::SDK::Connect::Device] self Return itself, so chained methods can be supported.
338
+ #
339
+ def transfer_state!
340
+ transfer_playback_method(playing: false)
341
+ self
342
+ end
343
+
344
+ private
345
+
346
+ def transfer_playback_method(playing:) # :nodoc:
347
+ override_opts = {
348
+ http_options: {
349
+ expect_nil: true
350
+ },
351
+ body: {
352
+ device_ids: [id],
353
+ play: playing
354
+ }.to_json
355
+ }
356
+
357
+ parent.send_http_request(:put, "/v1/me/player", override_opts)
358
+ end
359
+ end
360
+ end
361
+ end
362
+ end