spotify-ruby-kev 0.2.5

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,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
@@ -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