spotify-ruby-kev 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/CONTRIBUTING.md +3 -0
- data/.github/ISSUE_TEMPLATE.md +27 -0
- data/.gitignore +24 -0
- data/.rspec +3 -0
- data/.rubocop.yml +161 -0
- data/.ruby-version +1 -0
- data/.rvm-version +1 -0
- data/.travis.yml +17 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/COVERAGE.md +148 -0
- data/Gemfile +6 -0
- data/LICENSE +21 -0
- data/README.md +388 -0
- data/Rakefile +23 -0
- data/lib/spotify.rb +17 -0
- data/lib/spotify/accounts.rb +133 -0
- data/lib/spotify/accounts/session.rb +177 -0
- data/lib/spotify/sdk.rb +93 -0
- data/lib/spotify/sdk/.keep +0 -0
- data/lib/spotify/sdk/album.rb +84 -0
- data/lib/spotify/sdk/artist.rb +163 -0
- data/lib/spotify/sdk/base.rb +77 -0
- data/lib/spotify/sdk/connect.rb +75 -0
- data/lib/spotify/sdk/connect/device.rb +362 -0
- data/lib/spotify/sdk/connect/playback_state.rb +143 -0
- data/lib/spotify/sdk/image.rb +44 -0
- data/lib/spotify/sdk/item.rb +157 -0
- data/lib/spotify/sdk/me.rb +155 -0
- data/lib/spotify/sdk/me/info.rb +108 -0
- data/lib/spotify/sdk/model.rb +70 -0
- data/lib/spotify/version.rb +16 -0
- data/spotify-ruby-kev.gemspec +56 -0
- metadata +291 -0
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spotify
|
4
|
+
##
|
5
|
+
# Spotify::Accounts deals with authorization using the Spotify Accounts API.
|
6
|
+
#
|
7
|
+
class Accounts
|
8
|
+
##
|
9
|
+
# An entire list of Spotify's OAuth scopes. Stored
|
10
|
+
# in the form of a symbolized array.
|
11
|
+
# Example: `[:scope1, :scope2]`
|
12
|
+
#
|
13
|
+
# @see https://developer.spotify.com/documentation/general/guides/scopes/
|
14
|
+
#
|
15
|
+
# Last updated: 23 June 2018
|
16
|
+
#
|
17
|
+
SCOPES = %i[
|
18
|
+
playlist-read-private
|
19
|
+
playlist-read-collaborative
|
20
|
+
playlist-modify-public
|
21
|
+
playlist-modify-private
|
22
|
+
ugc-image-upload
|
23
|
+
user-follow-modify
|
24
|
+
user-follow-read
|
25
|
+
user-library-read
|
26
|
+
user-library-modify
|
27
|
+
user-read-private
|
28
|
+
user-read-birthdate
|
29
|
+
user-read-email
|
30
|
+
user-top-read
|
31
|
+
user-read-playback-state
|
32
|
+
user-modify-playback-state
|
33
|
+
user-read-currently-playing
|
34
|
+
user-read-recently-played
|
35
|
+
streaming
|
36
|
+
app-remote-control
|
37
|
+
].freeze
|
38
|
+
|
39
|
+
##
|
40
|
+
# Initialize the Spotify Accounts object.
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# @accounts = Spotify::Accounts.new({
|
44
|
+
# client_id: "[client id goes here]",
|
45
|
+
# client_secret: "[client secret goes here]",
|
46
|
+
# redirect_uri: "http://localhost"
|
47
|
+
# })
|
48
|
+
#
|
49
|
+
# @accounts = Spotify::Accounts.new
|
50
|
+
# @accounts.client_id = "[client id goes here]"
|
51
|
+
# @accounts.client_secret = "[client secret goes here]"
|
52
|
+
# @accounts.redirect_uri = "http://localhost"
|
53
|
+
#
|
54
|
+
# # with SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET, and SPOTIFY_REDIRECT_URI in ENV:
|
55
|
+
# @accounts = Spotify::Accounts.new
|
56
|
+
#
|
57
|
+
# @param [Hash] config The configuration containing your Client ID, Client Secret, and your Redirect URL.
|
58
|
+
#
|
59
|
+
# @see https://developer.spotify.com/dashboard/
|
60
|
+
#
|
61
|
+
def initialize(config={})
|
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"] }
|
65
|
+
end
|
66
|
+
|
67
|
+
attr_accessor :client_id, :client_secret, :redirect_uri
|
68
|
+
|
69
|
+
##
|
70
|
+
# Get a HTTP URL to send user for authorizing with Spotify.
|
71
|
+
#
|
72
|
+
# @example
|
73
|
+
# @accounts = Spotify::Accounts.new({
|
74
|
+
# client_id: "[client id goes here]",
|
75
|
+
# client_secret: "[client secret goes here]",
|
76
|
+
# redirect_uri: "http://localhost"
|
77
|
+
# })
|
78
|
+
#
|
79
|
+
# @auth.authorize_url
|
80
|
+
# @auth.authorize_url({ scope: "user-read-private user-top-read" })
|
81
|
+
#
|
82
|
+
# @param [Hash] override_params Optional hash containing any overriding values for parameters.
|
83
|
+
# Parameters used are client_id, redirect_uri, response_type and scope.
|
84
|
+
# @return [String] A fully qualified Spotify authorization URL to send the user to.
|
85
|
+
#
|
86
|
+
# @see https://developer.spotify.com/documentation/general/guides/authorization-guide/
|
87
|
+
#
|
88
|
+
def authorize_url(override_params={})
|
89
|
+
validate_credentials!
|
90
|
+
params = {
|
91
|
+
client_id: @client_id,
|
92
|
+
redirect_uri: @redirect_uri,
|
93
|
+
response_type: "code",
|
94
|
+
scope: SCOPES.join(" ")
|
95
|
+
}.merge(override_params)
|
96
|
+
"https://accounts.spotify.com/authorize?%s" % params.to_query
|
97
|
+
end
|
98
|
+
|
99
|
+
##
|
100
|
+
# Start a session from your authentication code.
|
101
|
+
#
|
102
|
+
# @example
|
103
|
+
# @accounts = Spotify::Accounts.new({
|
104
|
+
# client_id: "[client id goes here]",
|
105
|
+
# client_secret: "[client secret goes here]",
|
106
|
+
# redirect_uri: "http://localhost"
|
107
|
+
# })
|
108
|
+
#
|
109
|
+
# @accounts.exchange_for_session("code")
|
110
|
+
#
|
111
|
+
# @param [String] code The code provided back to your application upon authorization.
|
112
|
+
# @return [Spotify::Accounts::Session] session The session object.
|
113
|
+
#
|
114
|
+
# @see https://developer.spotify.com/documentation/general/guides/authorization-guide/
|
115
|
+
#
|
116
|
+
def exchange_for_session(code)
|
117
|
+
validate_credentials!
|
118
|
+
Spotify::Accounts::Session.from_authorization_code(code)
|
119
|
+
end
|
120
|
+
|
121
|
+
def inspect # :nodoc:
|
122
|
+
"#<%s:0x00%x>" % [self.class.name, (object_id << 1)]
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def validate_credentials! # :nodoc:
|
128
|
+
raise "Missing client id" if @client_id.nil?
|
129
|
+
raise "Missing client secret" if @client_secret.nil?
|
130
|
+
raise "Missing redirect uri" if @redirect_uri.nil?
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spotify
|
4
|
+
class Accounts
|
5
|
+
##
|
6
|
+
# A class representing an access token, with the ability to refresh.
|
7
|
+
#
|
8
|
+
class Session
|
9
|
+
class << self
|
10
|
+
##
|
11
|
+
# Parse the response we collect from the authorization code.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# @session = Spotify::Accounts.from_authorization_code(@accounts, "authorization code here")
|
15
|
+
#
|
16
|
+
# @param [Spotify::Accounts] accounts A valid instance of Spotify::Accounts.
|
17
|
+
# @param [String] code The code provided in the Redirect URI from the Spotify Accounts API.
|
18
|
+
# @return [Spotify::Accounts::Session] access_token An instance of Spotify::Accounts::Session
|
19
|
+
# @see lib/spotify/accounts.rb
|
20
|
+
#
|
21
|
+
def from_authorization_code(accounts, code)
|
22
|
+
params = {
|
23
|
+
client_id: @accounts.instance_variable_get(:@client_id),
|
24
|
+
client_secret: @accounts.instance_variable_get(:@client_secret),
|
25
|
+
redirect_uri: @accounts.instance_variable_get(:@redirect_uri),
|
26
|
+
grant_type: "authorization_code",
|
27
|
+
code: code
|
28
|
+
}
|
29
|
+
request = HTTParty.post("https://accounts.spotify.com/api/token", body: params)
|
30
|
+
response = request.parsed_response.with_indifferent_access
|
31
|
+
raise response[:error_description] if response[:error]
|
32
|
+
|
33
|
+
new(accounts, response[:access_token], response[:expires_in], response[:refresh_token], response[:scope])
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Set up an instance of Access Token with just a refresh_token.
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# @access_token = Spotify::Accounts::Session.from_refresh_token(@accounts, "refresh token here")
|
41
|
+
# @access_token.force_refresh!
|
42
|
+
#
|
43
|
+
# @param [Spotify::Accounts] accounts A valid instance of Spotify::Accounts.
|
44
|
+
# @param [String] refresh_token A valid refresh token. You'll want to store the refresh_token in your database.
|
45
|
+
# @return [Spotify::Accounts::Session] access_token An instance of Spotify::Accounts::Session
|
46
|
+
#
|
47
|
+
def from_refresh_token(accounts, refresh_token)
|
48
|
+
new(accounts, nil, nil, refresh_token, nil)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def initialize(accounts, access_token, expires_in, refresh_token, scopes)
|
53
|
+
unless accounts.instance_of?(Spotify::Accounts)
|
54
|
+
raise "You need a valid Spotify::Accounts instance in order to use Spotify authentication."
|
55
|
+
end
|
56
|
+
|
57
|
+
@accounts = accounts
|
58
|
+
@access_token = access_token
|
59
|
+
@expires_in = expires_in
|
60
|
+
@expires_at = expires_in + Time.now.to_i unless expires_in.nil?
|
61
|
+
@refresh_token = refresh_token
|
62
|
+
@scopes = scopes
|
63
|
+
end
|
64
|
+
|
65
|
+
attr_reader :accounts, :access_token, :expires_in, :refresh_token
|
66
|
+
|
67
|
+
##
|
68
|
+
# Converts the space-delimited scope list to a symbolized array.
|
69
|
+
#
|
70
|
+
# @example
|
71
|
+
# @access_token.scopes # => [:"user-read-private", :"user-top-read", ...]
|
72
|
+
#
|
73
|
+
# @return [Array] scopes A symbolized list of scopes.
|
74
|
+
#
|
75
|
+
def scopes
|
76
|
+
return [] if @scopes.nil?
|
77
|
+
|
78
|
+
@scopes.split(" ").map(&:to_sym)
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# Checks if a specific scope has been granted by the user.
|
83
|
+
#
|
84
|
+
# @example
|
85
|
+
# @access_token.contains_scope?("user-read-top")
|
86
|
+
# @access_token.contains_scope?(:"user-read-top")
|
87
|
+
#
|
88
|
+
# @param [String,Symbol] scope The name of the scope you'd like to check. For example, "user-read-private".
|
89
|
+
# @return [TrueClass,FalseClass] scope_included A true/false boolean if the scope is included.
|
90
|
+
#
|
91
|
+
def contains_scope?(scope)
|
92
|
+
scopes.include?(scope.downcase.to_sym)
|
93
|
+
end
|
94
|
+
|
95
|
+
##
|
96
|
+
# When will the access token expire? Returns nil if no expires_in is defined.
|
97
|
+
#
|
98
|
+
# @example
|
99
|
+
# @session.expires_at
|
100
|
+
#
|
101
|
+
# @return [Time] time When the access token will expire.
|
102
|
+
#
|
103
|
+
def expires_at
|
104
|
+
return nil if @expires_in.nil?
|
105
|
+
|
106
|
+
Time.at(@expires_at)
|
107
|
+
end
|
108
|
+
|
109
|
+
##
|
110
|
+
# Check if the access token has expired. Returns nil if no expires_in is defined.
|
111
|
+
#
|
112
|
+
# @example
|
113
|
+
# @session.expired?
|
114
|
+
#
|
115
|
+
# @return [TrueClass,FalseClass,NilClass] has_expired Has the access token expired?
|
116
|
+
#
|
117
|
+
def expired?
|
118
|
+
return nil if expires_at.nil?
|
119
|
+
|
120
|
+
Time.now > expires_at
|
121
|
+
end
|
122
|
+
|
123
|
+
##
|
124
|
+
# Refresh the access token.
|
125
|
+
#
|
126
|
+
# @example
|
127
|
+
# @session.refresh!
|
128
|
+
#
|
129
|
+
# @return [TrueClass,FalseClass] success Have we been able to refresh the access token?
|
130
|
+
#
|
131
|
+
# rubocop:disable AbcSize
|
132
|
+
def refresh!
|
133
|
+
raise "You cannot refresh without a valid refresh_token." if @refresh_token.nil?
|
134
|
+
|
135
|
+
params = {
|
136
|
+
client_id: @accounts.instance_variable_get(:@client_id),
|
137
|
+
client_secret: @accounts.instance_variable_get(:@client_secret),
|
138
|
+
grant_type: "refresh_token",
|
139
|
+
refresh_token: @refresh_token
|
140
|
+
}
|
141
|
+
request = HTTParty.post("https://accounts.spotify.com/api/token", body: params)
|
142
|
+
response = request.parsed_response.with_indifferent_access
|
143
|
+
|
144
|
+
@access_token = response[:access_token]
|
145
|
+
@expires_in = response[:expires_in]
|
146
|
+
@expires_at = response[:expires_in] + Time.now.to_i
|
147
|
+
@scopes = response[:scope]
|
148
|
+
|
149
|
+
true
|
150
|
+
rescue HTTParty::Error
|
151
|
+
false
|
152
|
+
end
|
153
|
+
# rubocop:enable AbcSize
|
154
|
+
|
155
|
+
##
|
156
|
+
# Export to JSON. Designed mostly for iOS, Android, or external use cases.
|
157
|
+
#
|
158
|
+
# @example
|
159
|
+
# @session.to_json
|
160
|
+
#
|
161
|
+
# @return [String] json The JSON output of the session instance.
|
162
|
+
#
|
163
|
+
def to_json(*_args)
|
164
|
+
{
|
165
|
+
access_token: @access_token.presence,
|
166
|
+
expires_at: @expires_at.presence,
|
167
|
+
refresh_token: @refresh_token.presence,
|
168
|
+
scopes: scopes
|
169
|
+
}.to_json
|
170
|
+
end
|
171
|
+
|
172
|
+
def inspect # :nodoc:
|
173
|
+
"#<%s:0x00%x>" % [self.class.name, (object_id << 1)]
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
data/lib/spotify/sdk.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Scaffolding
|
4
|
+
require "spotify/sdk/base"
|
5
|
+
require "spotify/sdk/model"
|
6
|
+
|
7
|
+
# Components
|
8
|
+
require "spotify/sdk/connect"
|
9
|
+
require "spotify/sdk/me"
|
10
|
+
|
11
|
+
# Models
|
12
|
+
require "spotify/sdk/connect/device"
|
13
|
+
require "spotify/sdk/connect/playback_state"
|
14
|
+
require "spotify/sdk/me/info"
|
15
|
+
require "spotify/sdk/artist"
|
16
|
+
require "spotify/sdk/album"
|
17
|
+
require "spotify/sdk/image"
|
18
|
+
require "spotify/sdk/item"
|
19
|
+
|
20
|
+
module Spotify
|
21
|
+
##
|
22
|
+
# Spotify::SDK contains the complete Ruby DSL to interact with the Spotify Platform.
|
23
|
+
#
|
24
|
+
class SDK
|
25
|
+
##
|
26
|
+
# Initialize the Spotify SDK object.
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# # Example 1: Load it in from an access token value.
|
30
|
+
# @sdk = Spotify::SDK.new("access_token_here")
|
31
|
+
#
|
32
|
+
# # Example 2: Load it in with values from your database.
|
33
|
+
# @sdk = Spotify::SDK.new({
|
34
|
+
# access_token: "access_token_here",
|
35
|
+
# expires_in: 3_000_000,
|
36
|
+
# refresh_token: "refresh_token_here"
|
37
|
+
# })
|
38
|
+
#
|
39
|
+
# # Example 4: Load it in from an OAuth2::AccessToken object.
|
40
|
+
# @sdk = Spotify::SDK.new(@auth.auth_code.get_token("auth code"))
|
41
|
+
#
|
42
|
+
# # Example 5: Load it from a query string or a fully qualified URL.
|
43
|
+
# @sdk = Spotify::SDK.new("https://localhost:8080/#token=...&expires_in=...")
|
44
|
+
# @sdk = Spotify::SDK.new("token=...&expires_in=...")
|
45
|
+
#
|
46
|
+
# @param [String,Hash,OAuth2::AccessToken] session Any supported object containing an access token.
|
47
|
+
#
|
48
|
+
def initialize(session)
|
49
|
+
raise "Invalid Spotify::Accounts::Session object" unless session.instance_of?(Spotify::Accounts::Session)
|
50
|
+
|
51
|
+
@session = session
|
52
|
+
mount_sdk_components
|
53
|
+
end
|
54
|
+
|
55
|
+
attr_reader :session
|
56
|
+
|
57
|
+
def inspect # :nodoc:
|
58
|
+
"#<%s:0x00%x>" % [self.class.name, (object_id << 1)]
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# This is where we mount new SDK components to the Spotify::SDK object.
|
63
|
+
# Simply add a key (this is your identifier) with the value being the object.
|
64
|
+
#
|
65
|
+
# Notes:
|
66
|
+
# - Make sure your SDK component is being loaded at the top of this page.
|
67
|
+
# - You can name your identifier whatever you want:
|
68
|
+
# - This will be what people will use to call your code
|
69
|
+
# - For example: it would be the `connect` in `Spotify::SDK.new(@session).connect`
|
70
|
+
# - We'll call .new on your class, providing one parameter being the instance of this SDK (aka self).
|
71
|
+
# - Make sure to a test for it in spec/lib/spotify/sdk_spec.rb (see how we did it for others)
|
72
|
+
#
|
73
|
+
SDK_COMPONENTS = {
|
74
|
+
connect: Spotify::SDK::Connect,
|
75
|
+
me: Spotify::SDK::Me
|
76
|
+
}.freeze
|
77
|
+
|
78
|
+
SDK_COMPONENTS.each_key do |component|
|
79
|
+
attr_reader(component)
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
##
|
85
|
+
# This is where we map the SDK component classes to the SDK component vairables.
|
86
|
+
#
|
87
|
+
def mount_sdk_components # :nodoc:
|
88
|
+
SDK_COMPONENTS.map do |key, klass|
|
89
|
+
instance_variable_set "@#{key}".to_sym, klass.new(self)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
File without changes
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spotify
|
4
|
+
class SDK
|
5
|
+
class Album < Model
|
6
|
+
##
|
7
|
+
# Is this an album?
|
8
|
+
# Note: This is mostly to support other types of albums in the future.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# album = @sdk.connect.playback.item.album
|
12
|
+
# album.album?
|
13
|
+
#
|
14
|
+
# @return [TrueClass,FalseClass] is_album Returns true if type is an album.
|
15
|
+
#
|
16
|
+
def album?
|
17
|
+
type == "album"
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# Display the album's images.
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# album = @sdk.connect.playback.item.album
|
25
|
+
# album.images[0] # => [#<Spotify::SDK::Image>, #<Spotify::SDK::Image>, ...]
|
26
|
+
#
|
27
|
+
# @return [Array] album_images Contains a list of images, wrapped in Spotify::SDK::Image
|
28
|
+
#
|
29
|
+
def images
|
30
|
+
super.map do |image|
|
31
|
+
Spotify::SDK::Image.new(image, parent)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# Get the artists/creators for this album.
|
37
|
+
#
|
38
|
+
# @example
|
39
|
+
# @sdk.connect.playback.item.album.artists
|
40
|
+
#
|
41
|
+
# @return [Array] artists A list of artists, wrapped in Spotify::SDK::Artist
|
42
|
+
#
|
43
|
+
def artists
|
44
|
+
super.map do |artist|
|
45
|
+
Spotify::SDK::Artist.new(artist, parent)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Get the primary artist/creator for this album.
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
# @sdk.connect.playback.item.album.artist
|
54
|
+
#
|
55
|
+
# @return [Spotify::SDK::Artist] artist The primary artist, wrapped in Spotify::SDK::Artist
|
56
|
+
#
|
57
|
+
def artist
|
58
|
+
artists.first
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Get the Spotify URI for this album.
|
63
|
+
# Alias to self.uri
|
64
|
+
#
|
65
|
+
# @example
|
66
|
+
# @sdk.connect.playback.item.album.spotify_uri # => "spotify:track:..."
|
67
|
+
#
|
68
|
+
# @return [String] spotify_uri The direct URI to this Spotify resource.
|
69
|
+
#
|
70
|
+
alias_attribute :spotify_uri, :uri
|
71
|
+
|
72
|
+
##
|
73
|
+
# Get the Spotify HTTP URL for this album.
|
74
|
+
# Alias to self.external_urls[:spotify]
|
75
|
+
#
|
76
|
+
# @example
|
77
|
+
# @sdk.connect.playback.item.album.spotify_url # => "https://open.spotify.com/..."
|
78
|
+
#
|
79
|
+
# @return [String] spotify_url The direct HTTP URL to this Spotify resource.
|
80
|
+
#
|
81
|
+
alias_attribute :spotify_url, "external_urls.spotify"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|