spotify-ruby 0.2.2 → 0.2.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0b63f5681311753e57dd0f3465c03c92b2f075a0
4
- data.tar.gz: 1388c3f85c842cf7521360af71a62b7ac20e2620
3
+ metadata.gz: 118af1b2f6ab08785aa78c8028bf7927003154a1
4
+ data.tar.gz: 2d655193db96272b01a9f90aef9964c5a482e324
5
5
  SHA512:
6
- metadata.gz: db844e007c167f743d38434c9a94ab16992730a75b62dd47a28f0d161a609c96205975b6c6b03e3765769b2062cfda2068c19bc40bad8da9b25b35e5e0d7ff4d
7
- data.tar.gz: c205c712540fd6f205f902912765049112b4cf052554043562a2c431b3bbf6fed90c4cf3e99e08b4c107e1539de93d061ae90997607591a4ed4ec7e982665e75
6
+ metadata.gz: b6f4a4f4018790a020e3ebd1b926b6ceda2ce0fe8a5fc3d807cd2da87b81489ae19a68d1b3236eb59cb9bf1ecdbb74b51fe1a71900486b93719eaa490df63714
7
+ data.tar.gz: b2e0da362c7786da52a7eee27f1a1277f5f953fa499c82b7d615b97745f27e9aaf043229cbdfea02ba3d91c896e06caa4375e75b9049aff2cbb385084b27fe10
data/.travis.yml CHANGED
@@ -4,6 +4,7 @@ rvm:
4
4
  - 2.4.0
5
5
  - 2.4.1
6
6
  - 2.5.0
7
+ - 2.5.1
7
8
  before_install: >
8
9
  gem install bundler -v 1.15.1
9
10
  before_script:
data/COVERAGE.md CHANGED
@@ -87,15 +87,15 @@ This covers all the Spotify API endpoints that are covered.
87
87
 
88
88
  ### Follow Endpoints
89
89
 
90
- | Endpoint | Description | Coverage Status |
91
- | ------------------------------------------------------------------ | ---------------------------------------------- | ---------------------------------------------------------------------- |
92
- | GET /v1/me/following | Get Followed Artists | [me.rb] |
93
- | GET /v1/me/following/contains | Check if Current User Follows Artists or Users | [me.rb] |
94
- | PUT /v1/me/following | Follow Artists or Users | [🔘 Partial Support][artist.rb] (Following multiple isn't supported) |
95
- | DELETE /v1/me/following | Unfollow Artists or Users | [🔘 Partial Support][artist.rb] (Unfollowing multiple isn't supported) |
96
- | GET /v1/users/{user_id}/playlists/{playlist_id}/followers/contains | Check if Users Follow a Playlist | × Not Started |
97
- | PUT /v1/users/{user_id}/playlists/{playlist_id}/followers | Follow a Playlist | × Not Started |
98
- | DELETE /v1/users/{user_id}/playlists/{playlist_id}/followers | Unfollow a Playlist | × Not Started |
90
+ | Endpoint | Description | Coverage Status |
91
+ | ------------------------------------------------------------------ | ---------------------------------------------- | -------------------------------------------------------------------- |
92
+ | GET /v1/me/following | Get Followed Artists | [me.rb] |
93
+ | GET /v1/me/following/contains | Check if Current User Follows Artists or Users | [me.rb] |
94
+ | PUT /v1/me/following | Follow Artists or Users | [🔘 Partial Support][artist.rb] (Following multiple not supported) |
95
+ | DELETE /v1/me/following | Unfollow Artists or Users | [🔘 Partial Support][artist.rb] (Unfollowing multiple not supported) |
96
+ | GET /v1/users/{user_id}/playlists/{playlist_id}/followers/contains | Check if Users Follow a Playlist | × Not Started |
97
+ | PUT /v1/users/{user_id}/playlists/{playlist_id}/followers | Follow a Playlist | × Not Started |
98
+ | DELETE /v1/users/{user_id}/playlists/{playlist_id}/followers | Unfollow a Playlist | × Not Started |
99
99
 
100
100
  ### Playlists Endpoints
101
101
 
@@ -113,10 +113,10 @@ This covers all the Spotify API endpoints that are covered.
113
113
 
114
114
  ### History Endpoints
115
115
 
116
- | Endpoint | Description | Coverage Status |
117
- | --------------------------------- | --------------------------------------------- | --------------- |
118
- | GET /v1/me/top/{type} | Get User's Top Artists and Tracks | × Not Started |
119
- | GET /v1/me/player/recently-played | Get the Current User's Recently Played Tracks | × Not Started |
116
+ | Endpoint | Description | Coverage Status |
117
+ | --------------------------------- | --------------------------------------------- | ----------------------- |
118
+ | GET /v1/me/top/{type} | Get User's Top Artists and Tracks | × Not Started |
119
+ | GET /v1/me/player/recently-played | Get the Current User's Recently Played Tracks | [Full support ✔][me.rb] |
120
120
 
121
121
  ### Connect Endpoints
122
122
 
data/README.md CHANGED
@@ -23,13 +23,15 @@ The developer-friendly, opinionated Ruby SDK for [Spotify]. Works on Ruby 2.4+
23
23
  - [Creating a Session](#creating-a-session)
24
24
  - [Recreating a Session](#recreating-a-session)
25
25
  - [Using the SDK](#using-the-sdk)
26
- - [Spotify Connect](#spotify-connect)
26
+ - [Spotify Connect API](#spotify-connect-api)
27
+ - [Me API](#me-api)
28
+ - [Listening History API](#listening-history-api)
29
+ - [Following API](#following-api)
27
30
  - [Contributing](#contributing)
28
31
  - [Community Guidelines](#community-guidelines)
29
32
  - [Code of Conduct](#code-of-conduct)
30
33
  - [Getting Started](#getting-started)
31
34
  - [Releasing a Change](#releasing-a-change)
32
- - [Changelog](#changelog)
33
35
  - [License](#license)
34
36
 
35
37
  ## Introduction
@@ -95,11 +97,25 @@ To define your app credentials, you'll need to create an instance of `Spotify::A
95
97
 
96
98
  ```ruby
97
99
  @accounts = Spotify::Accounts.new
98
- @accounts.client_id = ENV["SPOTIFY_CLIENT_ID"]
99
- @accounts.client_secret = ENV["SPOTIFY_CLIENT_SECRET"]
100
- @accounts.redirect_uri = ENV["SPOTIFY_REDIRECT_URI"]
100
+ @accounts.client_id = "spotify client ID"
101
+ @accounts.client_secret = "spotify client secret"
102
+ @accounts.redirect_uri = "redirect URI"
101
103
  ```
102
104
 
105
+ Alternatively, these credentials can be supplied as environment variables when running your application:
106
+
107
+ ```ruby
108
+ @accounts = Spotify::Accounts.new # fetches configuration from ENV
109
+ ```
110
+
111
+ The respective environment variables you'll need to set are:
112
+
113
+ | Environment Variable | Description | Required? |
114
+ | ------------------------ | ----------------------------------------- | ------------- |
115
+ | `SPOTIFY_CLIENT_ID` | Your Spotify Client ID | **Yes** |
116
+ | `SPOTIFY_CLIENT_SECRET` | Your Spotify Client Secret | **Yes** |
117
+ | `SPOTIFY_REDIRECT_URI` | Your Spotify Redirect URI (must be exact) | **Yes** |
118
+
103
119
  ### Authorization
104
120
 
105
121
  In order to use Spotify's APIs on a user's behalf, you'll need to use the Spotify [Accounts API] to redirect them to `https://accounts.spotify.com`. They will then need to explicitly approve your application and what data you're asking for (technically referred to as authorization scopes).
@@ -168,7 +184,7 @@ To create an instance of the Spotify SDK, you'll need the `@session` from above
168
184
  @sdk = Spotify::SDK.new(@session)
169
185
  ```
170
186
 
171
- ### Spotify Connect
187
+ ### Spotify Connect API
172
188
 
173
189
  With [Spotify Connect], you can take your music experience anywhere on over 300 devices. And you can read and control most devices programmatically through the SDK:
174
190
 
@@ -213,6 +229,98 @@ With [Spotify Connect], you can take your music experience anywhere on over 300
213
229
  @sdk.connect.devices[0].repeat_mode = :context
214
230
  ```
215
231
 
232
+ #### Transfer playback\*
233
+
234
+ This will transfer state, and start playback.
235
+
236
+ ```ruby
237
+ @sdk.connect.devices[0].transfer_playback!
238
+ ```
239
+
240
+ #### Transfer state\*
241
+
242
+ This will transfer state, and pause playback.
243
+
244
+ ```ruby
245
+ @sdk.connect.devices[0].transfer_state!
246
+ ```
247
+
248
+ ### Me API
249
+
250
+ This allows you to perform specific actions on behalf of a user.
251
+
252
+ #### My information\*
253
+
254
+ ```ruby
255
+ @sdk.me.info
256
+ @sdk.me.info.free? # => false
257
+ @sdk.me.info.premium? # => true
258
+ @sdk.me.info.birthdate # => 1980-01-01
259
+ @sdk.me.info.display_name? # => true
260
+ @sdk.me.info.display_name # => "ABC Smith"
261
+ @sdk.me.info.images[0].url # => "https://profile-images.scdn.co/userprofile/default/..."
262
+ @sdk.me.info.followers # => 4913313
263
+ @sdk.me.info.spotify_uri # => "spotify:user:abcsmith"
264
+ @sdk.me.info.spotify_url # => "https://open.spotify.com/user/abcsmith"
265
+ ```
266
+
267
+ ### Listening History API
268
+
269
+ #### My recently played tracks (up to last 50)\*
270
+
271
+ ```ruby
272
+ @sdk.me.history(10) # => [#<Spotify::SDK::Item...>, ...]
273
+ @sdk.me.history(10).size # => 10
274
+ @sdk.me.history(50) # => [#<Spotify::SDK::Item...>, ...]
275
+ @sdk.me.history(50).size # => 50
276
+ ```
277
+
278
+ ### Following API
279
+
280
+ #### Follow an artist\*
281
+
282
+ ```ruby
283
+ @sdk.playback.item.artist.follow!
284
+ ```
285
+
286
+ #### Unfollow an artist\*
287
+
288
+ ```ruby
289
+ @sdk.playback.item.artist.unfollow!
290
+ ```
291
+
292
+ #### Check if following Spotify artists?\*
293
+
294
+ ```ruby
295
+ @sdk.me.following_artists?(%w(3TVXtAsR1Inumwj472S9r4 6LuN9FCkKOj5PcnpouEgny 69GGBxA162lTqCwzJG5jLp))
296
+ # => {
297
+ # "3TVXtAsR1Inumwj472S9r4" => false,
298
+ # "6LuN9FCkKOj5PcnpouEgny" => true,
299
+ # "69GGBxA162lTqCwzJG5jLp" => false
300
+ # }
301
+ ```
302
+
303
+ #### Check if following Spotify users?\*
304
+
305
+ ```ruby
306
+ @sdk.me.following_users?(%w(3TVXtAsR1Inumwj472S9r4 6LuN9FCkKOj5PcnpouEgny 69GGBxA162lTqCwzJG5jLp))
307
+ # => {
308
+ # "3TVXtAsR1Inumwj472S9r4" => false,
309
+ # "6LuN9FCkKOj5PcnpouEgny" => true,
310
+ # "69GGBxA162lTqCwzJG5jLp" => false
311
+ # }
312
+ ```
313
+
314
+ #### See all followed artists\*
315
+
316
+ ```ruby
317
+ @sdk.me.following(5) # => [#<Spotify::SDK::Artist...>, ...]
318
+ @sdk.me.following(5).size # => 5
319
+ @sdk.me.following(50) # => [#<Spotify::SDK::Artist...>, ...]
320
+ @sdk.me.following(50).size # => 50
321
+ ```
322
+
323
+
216
324
  <small><i>\* Requires specific user permissions/scopes. See [Authorization Scopes] for more information.</i></small>
217
325
 
218
326
  ## Contributing
@@ -252,16 +360,6 @@ For local development, you can run `bin/console` for an interactive prompt for e
252
360
  - Push git commits and tags
253
361
  - Push the `.gem` file to [rubygems.org].
254
362
 
255
- ### Changelog
256
-
257
- ```
258
- [2018-07-21] (0.2.1) First major release.
259
- - Support for Connect and User API endpoints.
260
- - Transitioned to YARD for documentation.
261
- - Website built using Jekyll with Contributing guide.
262
- - Removed Coveralls in favour for CodeClimate
263
- ```
264
-
265
363
  ## License
266
364
 
267
365
  The gem is available as open source under the terms of the [MIT License].
@@ -51,14 +51,17 @@ module Spotify
51
51
  # @accounts.client_secret = "[client secret goes here]"
52
52
  # @accounts.redirect_uri = "http://localhost"
53
53
  #
54
+ # # with SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET, and SPOTIFY_REDIRECT_URI in ENV:
55
+ # @accounts = Spotify::Accounts.new
56
+ #
54
57
  # @param [Hash] config The configuration containing your Client ID, Client Secret, and your Redirect URL.
55
58
  #
56
59
  # @see https://developer.spotify.com/dashboard/
57
60
  #
58
61
  def initialize(config={})
59
- @client_id = config.delete(:client_id)
60
- @client_secret = config.delete(:client_secret)
61
- @redirect_uri = config.delete(:redirect_uri)
62
+ @client_id = config.delete(:client_id) { ENV["SPOTIFY_CLIENT_ID"] }
63
+ @client_secret = config.delete(:client_secret) { ENV["SPOTIFY_CLIENT_SECRET"] }
64
+ @redirect_uri = config.delete(:redirect_uri) { ENV["SPOTIFY_REDIRECT_URI"] }
62
65
  end
63
66
 
64
67
  attr_accessor :client_id, :client_secret, :redirect_uri
@@ -133,7 +133,7 @@ module Spotify
133
133
  #
134
134
  def item
135
135
  raise "Playback information is not available if user has a private session enabled" if device.private_session?
136
- Spotify::SDK::Item.new(super, parent)
136
+ Spotify::SDK::Item.new(to_h, parent)
137
137
  end
138
138
  end
139
139
  end
@@ -3,6 +3,18 @@
3
3
  module Spotify
4
4
  class SDK
5
5
  class Item < Model
6
+ ##
7
+ # Let's transform the item object into better for us.
8
+ # Before: { track: ..., played_at: ..., context: ... }
9
+ # After: { track_properties..., played_at: ..., context: ... }
10
+ #
11
+ # :nodoc:
12
+ def initialize(payload, parent)
13
+ track = payload.delete(:track) || payload.delete(:item)
14
+ properties = payload.except(:parent, :device, :repeat_state, :shuffle_state)
15
+ super(track.merge(properties: properties), parent)
16
+ end
17
+
6
18
  ##
7
19
  # Get the album for this item.
8
20
  #
@@ -41,6 +53,17 @@ module Spotify
41
53
  artists.first
42
54
  end
43
55
 
56
+ ##
57
+ # Get the context.
58
+ #
59
+ # @example
60
+ # @sdk.connect.playback.item.context
61
+ # @sdk.me.history[0].context
62
+ #
63
+ # @return [Hash] context Information about the user's context.
64
+ #
65
+ alias_attribute :context, "properties.context"
66
+
44
67
  ##
45
68
  # Get the duration.
46
69
  # Alias to self.duration_ms
@@ -22,9 +22,47 @@ module Spotify
22
22
  Spotify::SDK::Me::Info.new(me_info, self)
23
23
  end
24
24
 
25
+ ##
26
+ # Check what tracks a user has recently played.
27
+ #
28
+ # @example
29
+ # @sdk.me.history
30
+ # @sdk.me.history(20)
31
+ #
32
+ # @param [Integer] limit How many results to request. Defaults to 10.
33
+ # @param [Hash] override_opts Custom options for HTTParty.
34
+ # @return [Array] response List of recently played tracked, in chronological order.
35
+ #
36
+ def history(n=10, override_opts={})
37
+ request = {
38
+ method: :get,
39
+ http_path: "/v1/me/player/recently-played",
40
+ keys: %i[items],
41
+ limit: n
42
+ }
43
+
44
+ send_multiple_http_requests(request, override_opts).map do |item|
45
+ Spotify::SDK::Item.new(item, self)
46
+ end
47
+ end
48
+
25
49
  ##
26
50
  # Check if the current user is following N users.
27
51
  #
52
+ # @example
53
+ # artists = %w(3q7HBObVc0L8jNeTe5Gofh 0NbfKEOTQCcwd6o7wSDOHI 3TVXtAsR1Inumwj472S9r4)
54
+ # @sdk.me.following?(artists, :artist)
55
+ # # => {"3q7HBObVc0L8jNeTe5Gofh" => false, "0NbfKEOTQCcwd6o7wSDOHI" => false, ...}
56
+ #
57
+ # users = %w(3q7HBObVc0L8jNeTe5Gofh 0NbfKEOTQCcwd6o7wSDOHI 3TVXtAsR1Inumwj472S9r4)
58
+ # @sdk.me.following?(users, :user)
59
+ # # => {"3q7HBObVc0L8jNeTe5Gofh" => false, "0NbfKEOTQCcwd6o7wSDOHI" => false, ...}
60
+ #
61
+ # @param [Array] list List of Spotify user/artist IDs. Cannot mix user and artist IDs in single request.
62
+ # @param [Symbol] type Either :user or :artist. Checks if follows respective type of account.
63
+ # @param [Hash] override_opts Custom options for HTTParty.
64
+ # @return [Hash] hash A hash containing a key with the ID, and a value that equals is_following (boolean).
65
+ #
28
66
  def following?(list, type=:artist, override_opts={})
29
67
  raise "Must contain an array" unless list.is_a?(Array)
30
68
  raise "Must contain an array of String or Spotify::SDK::Artist" if any_of?(list, [String, Spotify::SDK::Artist])
@@ -32,6 +70,14 @@ module Spotify
32
70
  send_is_following_http_requests(list.map {|id| id.try(:id) || id }, type, override_opts)
33
71
  end
34
72
 
73
+ def following_artists?(list, override_opts={})
74
+ following?(list, :artist, override_opts)
75
+ end
76
+
77
+ def following_users?(list, override_opts={})
78
+ following?(list, :user, override_opts)
79
+ end
80
+
35
81
  ##
36
82
  # Get the current user's followed artists. Requires the `user-read-follow` scope.
37
83
  # GET /v1/me/following
@@ -39,22 +85,52 @@ module Spotify
39
85
  # @example
40
86
  # @sdk.me.following
41
87
  #
88
+ # @param [Integer] n Number of results to return.
42
89
  # @param [Hash] override_opts Custom options for HTTParty.
43
90
  # @return [Array] artists A list of followed artists, wrapped in Spotify::SDK::Artist
44
91
  #
45
- def following(override_opts={})
46
- artists = send_following_http_requests("/v1/me/following?type=artist&limit=50", override_opts)
47
- artists.map do |artist|
92
+ def following(n=50, override_opts={})
93
+ request = {
94
+ method: :get,
95
+ # TODO: Spotify API bug - `limit={n}` returns n-1 artists.
96
+ # ^ Example: `limit=5` returns 4 artists.
97
+ # TODO: Support `type=users` as well as `type=artists`.
98
+ http_path: "/v1/me/following?type=artist&limit=#{[n, 50].min}",
99
+ keys: %i[artists items],
100
+ limit: n
101
+ }
102
+
103
+ send_multiple_http_requests(request, override_opts).map do |artist|
48
104
  Spotify::SDK::Artist.new(artist, self)
49
105
  end
50
106
  end
51
107
 
52
108
  private
53
109
 
54
- def any_of?(array, klasses)
110
+ def any_of?(array, klasses) # :nodoc:
55
111
  (array.map(&:class) - klasses).any?
56
112
  end
57
113
 
114
+ def send_multiple_http_requests(opts, override_opts) # :nodoc:
115
+ response = send_http_request(opts[:method], opts[:http_path], override_opts)
116
+ responses, next_request = hash_deep_lookup(response, opts[:keys].dup)
117
+ if next_request && responses.size < opts[:limit]
118
+ responses += send_multiple_http_requests(opts.merge(http_path: next_request), override_opts)
119
+ end
120
+ responses.first(opts[:limit])
121
+ end
122
+
123
+ def hash_deep_lookup(response, keys) # :nodoc:
124
+ error_message = "Cannot find '%s' key in Spotify::SDK::Me#hash_deep_lookup"
125
+ while keys.any?
126
+ next_request ||= response[:next]
127
+ next_key = keys.shift
128
+ response = next_key ? response[next_key] : raise(error_message % next_key)
129
+ end
130
+ [response, next_request ? next_request[23..-1] : nil]
131
+ end
132
+
133
+ # TODO: Migrate this into the abstracted send_multiple_http_requests
58
134
  def send_is_following_http_requests(list, type, override_opts) # :nodoc:
59
135
  max_ids = list.first(50)
60
136
  remaining_ids = list - max_ids
@@ -71,13 +147,6 @@ module Spotify
71
147
  ids.merge(send_is_following_http_requests(remaining_ids, type, override_opts))
72
148
  end || ids
73
149
  end
74
-
75
- def send_following_http_requests(http_path, override_opts) # :nodoc:
76
- request = send_http_request(:get, http_path, override_opts)[:artists]
77
- artists = request[:items]
78
- artists << send_following_http_requests(request[:next][23..-1], override_opts) if request[:next]
79
- artists.flatten
80
- end
81
150
  end
82
151
  end
83
152
  end
@@ -12,5 +12,5 @@ module Spotify
12
12
  # MINOR version when you add functionality in a backwards-compatible manner, and
13
13
  # PATCH version when you make backwards-compatible bug fixes.
14
14
  #
15
- VERSION = "0.2.2"
15
+ VERSION = "0.2.3"
16
16
  end
data/spotify-ruby.gemspec CHANGED
@@ -35,6 +35,7 @@ Gem::Specification.new do |spec|
35
35
  spec.add_development_dependency "rake", "~> 12.1"
36
36
 
37
37
  # Testing
38
+ spec.add_development_dependency "climate_control", "~> 0.2"
38
39
  spec.add_development_dependency "factory_bot", "~> 1.0.0.alpha"
39
40
  spec.add_development_dependency "rspec", "~> 3.7"
40
41
  spec.add_development_dependency "rspec-collection_matchers", "~> 1.1", ">= 1.1.2"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spotify-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bilawal Hameed
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-25 00:00:00.000000000 Z
11
+ date: 2018-08-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -44,6 +44,20 @@ dependencies:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
46
  version: '12.1'
47
+ - !ruby/object:Gem::Dependency
48
+ name: climate_control
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '0.2'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.2'
47
61
  - !ruby/object:Gem::Dependency
48
62
  name: factory_bot
49
63
  requirement: !ruby/object:Gem::Requirement