napster 0.0.0 → 0.1.0

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.
data/lib/napster.rb CHANGED
@@ -1,5 +1,28 @@
1
- require "napster/version"
1
+ require 'faraday'
2
+ require 'oj'
2
3
 
3
- module Napster
4
- # Your code goes here...
5
- end
4
+ require 'string_helper'
5
+ require 'napster/client'
6
+ require 'napster/me'
7
+ require 'napster/moniker'
8
+ require 'napster/response_error'
9
+ require 'napster/request'
10
+ require 'napster/version'
11
+
12
+ # Models
13
+ require 'napster/models/artist'
14
+ require 'napster/models/album'
15
+ require 'napster/models/chart'
16
+ require 'napster/models/content'
17
+ require 'napster/models/follower'
18
+ require 'napster/models/following'
19
+ require 'napster/models/favorite'
20
+ require 'napster/models/favorite_status'
21
+ require 'napster/models/library'
22
+ require 'napster/models/library_date_time'
23
+ require 'napster/models/member'
24
+ require 'napster/models/playlist'
25
+ require 'napster/models/profile'
26
+ require 'napster/models/tag'
27
+ require 'napster/models/track'
28
+ require 'napster/models/uploaded_image'
@@ -0,0 +1,276 @@
1
+ module Napster
2
+ # The Client class implements a client object that prepares
3
+ # information such as api_key, api_secret, and :redirect_uri
4
+ # needed to call Napster API.
5
+ class Client
6
+ MODELS_LIST = %w(artist album track genre member playlist tag
7
+ station radio favorite).freeze
8
+ AUTH_METHODS = [:password_grant, :oauth2].freeze
9
+
10
+ attr_accessor :api_key,
11
+ :api_secret,
12
+ :redirect_uri,
13
+ :username,
14
+ :password,
15
+ :state,
16
+ :auth_code,
17
+ :access_token,
18
+ :refresh_token,
19
+ :expires_in,
20
+ :request
21
+
22
+ # Instantiate a client object
23
+ # @note request attribute is always overwritten by Napster::Request
24
+ # object.
25
+ # @param options [Hash] Required options are :api_key and :api_secret
26
+ def initialize(options)
27
+ options.each do |name, value|
28
+ instance_variable_set("@#{name}", value)
29
+ end
30
+
31
+ request_hash = {
32
+ api_key: @api_key,
33
+ api_secret: @api_secret
34
+ }
35
+ @request = Napster::Request.new(request_hash)
36
+ authenticate_client
37
+ set_models
38
+ end
39
+
40
+ # Make a post request to Napster API
41
+ # @param path [String] API path
42
+ # @param body [Hash] Body for the post request
43
+ # @param options [Hash] Faraday adapter options
44
+ # @return [Hash] parsed response from Napster API
45
+ def post(path, body = {}, options = {})
46
+ validate_request(path, options)
47
+ raw_response = @request.faraday.post do |req|
48
+ req.url path, options[:params]
49
+ req.body = body
50
+ req.headers['apikey'] = @api_key
51
+ if options[:headers]
52
+ options[:headers].each do |key, value|
53
+ req.headers[key] = value
54
+ end
55
+ end
56
+ end
57
+ handle_response(raw_response)
58
+ end
59
+
60
+ # Make a get request to Napster API
61
+ # @param path [String] API path
62
+ # @param options [Hash] Faraday adapter options accepting
63
+ # headers
64
+ # @return [Hash] parsed response from Napster API
65
+ def get(path, options = {})
66
+ validate_request(path, options)
67
+ raw_response = @request.faraday.get do |req|
68
+ req.url path, options[:params]
69
+ req.headers['apikey'] = @api_key
70
+ if options[:headers]
71
+ options[:headers].each do |key, value|
72
+ req.headers[key] = value
73
+ end
74
+ end
75
+ end
76
+ handle_response(raw_response)
77
+ end
78
+
79
+ # Make a put request to Napster API
80
+ # @param path [String] API path
81
+ # @param body [Hash] Body for the put request
82
+ # @param options [Hash] Faraday apapter options accepting
83
+ # headers, params
84
+ # @return [Hash] parsed response from Napster API
85
+ def put(path, body = {}, options = {})
86
+ validate_request(path, options)
87
+ raw_response = @request.faraday.put do |req|
88
+ req.url path, options[:params]
89
+ req.body = body
90
+ req.headers['apikey'] = @api_key
91
+ if options[:headers]
92
+ options[:headers].each do |key, value|
93
+ req.headers[key] = value
94
+ end
95
+ end
96
+ end
97
+ handle_response(raw_response)
98
+ end
99
+
100
+ # Make a delete request to Napster API
101
+ # @param path [String] API path
102
+ # @param options [Hash] Faraday apapter options accepting
103
+ # headers, params
104
+ # @return [Hash] parsed response from Napster API
105
+ def delete(path, options = {})
106
+ validate_request(path, options)
107
+ raw_response = @request.faraday.delete do |req|
108
+ req.url path, options[:params]
109
+ req.headers['apikey'] = @api_key
110
+ if options[:headers]
111
+ options[:headers].each do |key, value|
112
+ req.headers[key] = value
113
+ end
114
+ end
115
+ end
116
+ handle_response(raw_response)
117
+ end
118
+
119
+ # Smarter method for authentication via password_grant or oauth2
120
+ # @return [Client]
121
+ def connect
122
+ return authenticate(:password_grant) if authenticate_via_password_grant?
123
+ return authenticate(:oauth2) if authenticate_via_oauth2?
124
+ raise ArgumentError
125
+ end
126
+
127
+ # Main method for authenticating against Napster API
128
+ # @param auth_method [Symbol] authentication methods that are
129
+ # :password_grant or :oauth2
130
+ # @return [Hash] response from Napster API
131
+ def authenticate(auth_method)
132
+ validate_authenticate(auth_method)
133
+
134
+ return auth_password_grant if auth_method == :password_grant
135
+ return auth_oauth2 if auth_method == :oauth2
136
+ end
137
+
138
+ # Get URL for OAuth2 authentication flow
139
+ # @return [String] OAuth2 authentication URL
140
+ def authentication_url
141
+ validate_authentication_url
142
+ query_params = {
143
+ client_id: @api_key,
144
+ redirect_uri: @redirect_uri,
145
+ response_type: 'code'
146
+ }
147
+ query_params[:state] = @state if @state
148
+ query_params_string = URI.encode_www_form(query_params)
149
+ Napster::Request::HOST_URL + '/oauth/authorize?' + query_params_string
150
+ end
151
+
152
+ # Include Me module for calling authenticated methods
153
+ def me
154
+ Napster::Me.new(self)
155
+ end
156
+
157
+ private
158
+
159
+ # Helper method for .new, choose authentication method, and authenticate
160
+ # @return [Client] Authenticated client
161
+ def authenticate_client
162
+ return authenticate(:password_grant) if authenticate_via_password_grant?
163
+ self
164
+ end
165
+
166
+ # Helper method for #authenticate_client, check if password_grant auth is
167
+ # possible with current attributes.
168
+ # @return [Boolean]
169
+ def authenticate_via_password_grant?
170
+ @api_key && @api_secret && @username && @password
171
+ end
172
+
173
+ def authenticate_via_oauth2?
174
+ @api_key && @api_secret && @redirect_uri && @auth_code
175
+ end
176
+
177
+ def validate_request(path, options)
178
+ raise ArgumentError, 'path is missing' unless path
179
+ raise ArgumentError, 'options should be a hash' unless options.is_a?(Hash)
180
+ if options[:headers] && !options[:headers].is_a?(Hash)
181
+ raise ArgumentError, 'options[:headers] should be a hash'
182
+ end
183
+ end
184
+
185
+ def validate_authenticate(auth_method)
186
+ unless auth_method.is_a?(Symbol)
187
+ err = 'Authentication method must be passed as a symbol'
188
+ raise ArgumentError, err
189
+ end
190
+
191
+ unless AUTH_METHODS.include?(auth_method)
192
+ err = "Wrong authentication method. Valid methods are #{AUTH_METHODS}"
193
+ raise ArgumentError, err
194
+ end
195
+ end
196
+
197
+ def auth_password_grant
198
+ validate_auth_password_grant
199
+ body = post('/oauth/token',
200
+ auth_password_grant_post_body,
201
+ auth_password_grant_post_options)
202
+ @access_token = body['access_token']
203
+ @refresh_token = body['refresh_token']
204
+ @expires_in = body['expires_in']
205
+
206
+ self
207
+ end
208
+
209
+ def auth_password_grant_post_body
210
+ {
211
+ response_type: 'code',
212
+ grant_type: 'password',
213
+ username: @username,
214
+ password: @password
215
+ }
216
+ end
217
+
218
+ def auth_password_grant_post_options
219
+ basic_auth = Faraday::Request::BasicAuthentication.header(@api_key,
220
+ @api_secret)
221
+ { headers: { Authorization: basic_auth } }
222
+ end
223
+
224
+ def validate_auth_password_grant
225
+ raise 'The client is missing username' unless @username
226
+ raise 'The client is missing password' unless @password
227
+ end
228
+
229
+ def auth_oauth2
230
+ validate_auth_oauth2
231
+ response_body = post('/oauth/access_token', auth_oauth2_post_body, {})
232
+ @access_token = response_body['access_token']
233
+ @refresh_token = response_body['refresh_token']
234
+ @expires_in = response_body['expires_in']
235
+ self
236
+ end
237
+
238
+ def auth_oauth2_post_body
239
+ {
240
+ client_id: @api_key,
241
+ client_secret: @api_secret,
242
+ response_type: 'code',
243
+ grant_type: 'authorization_code',
244
+ code: @auth_code,
245
+ redirect_uri: @redirect_uri
246
+ }
247
+ end
248
+
249
+ def validate_auth_oauth2
250
+ raise 'The client is missing redirect_uri' unless @redirect_uri
251
+ raise 'The client is missing auth_code' unless @auth_code
252
+ end
253
+
254
+ def validate_authentication_url
255
+ raise 'The client is missing redirect_uri' unless @redirect_uri
256
+ end
257
+
258
+ def set_models
259
+ MODELS_LIST.each do |model|
260
+ define_singleton_method("#{model}s") do
261
+ Object.const_get(model_class_name(model)).new(client: self)
262
+ end
263
+ end
264
+ self
265
+ end
266
+
267
+ def model_class_name(model)
268
+ "Napster::Models::#{model.capitalize}"
269
+ end
270
+
271
+ def handle_response(response)
272
+ raise ResponseError.new(response) if response && !response.success?
273
+ Oj.load(response.body)
274
+ end
275
+ end
276
+ end
data/lib/napster/me.rb ADDED
@@ -0,0 +1,58 @@
1
+ module Napster
2
+ # Authenticated endpoints under /me namespace
3
+ class Me
4
+ MODELS_LIST = %w(favorite playlist follower tag).freeze
5
+ attr_accessor :client
6
+
7
+ def initialize(client)
8
+ validate_access_token(client)
9
+ @client = client
10
+ set_models
11
+ end
12
+
13
+ def listening_history(params)
14
+ get_options = {
15
+ params: params,
16
+ headers: {
17
+ Authorization: 'Bearer ' + @client.access_token,
18
+ 'Content-Type' => 'application/json',
19
+ 'Accept-Version' => '2.0.0'
20
+ }
21
+ }
22
+ response = @client.get('/me/listens', get_options)
23
+ Napster::Models::Track
24
+ .collection(data: response['tracks'], client: @client)
25
+ end
26
+
27
+ def library
28
+ Napster::Models::Library.new(client: @client)
29
+ end
30
+
31
+ def profile
32
+ Napster::Models::Profile.new(client: @client)
33
+ end
34
+
35
+ def following
36
+ Napster::Models::Following.new(client: @client)
37
+ end
38
+
39
+ private
40
+
41
+ def validate_access_token(client)
42
+ raise 'The client is missing access_token' unless client.access_token
43
+ end
44
+
45
+ def set_models
46
+ MODELS_LIST.each do |model|
47
+ define_singleton_method("#{model}s") do
48
+ Object.const_get(model_class_name(model)).new(client: @client)
49
+ end
50
+ end
51
+ self
52
+ end
53
+
54
+ def model_class_name(model)
55
+ "Napster::Models::#{model.capitalize}"
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,97 @@
1
+ using StringHelper
2
+
3
+ module Napster
4
+ module Models
5
+ # Album model
6
+ class Album
7
+ ATTRIBUTES = [:type,
8
+ :id,
9
+ :upc,
10
+ :shortcut,
11
+ :href,
12
+ :name,
13
+ :released,
14
+ :originally_released,
15
+ :label,
16
+ :copyright,
17
+ :tags,
18
+ :disc_count,
19
+ :track_count,
20
+ :explicit,
21
+ :single,
22
+ :account_partner,
23
+ :artist_name,
24
+ :contributing_artists].freeze
25
+
26
+ ATTRIBUTES.each do |attribute|
27
+ attr_accessor attribute
28
+ end
29
+
30
+ attr_accessor :client
31
+
32
+ def initialize(arg)
33
+ @client = arg[:client] if arg[:client]
34
+ return unless arg[:data]
35
+
36
+ ATTRIBUTES.each do |attribute|
37
+ send("#{attribute}=", arg[:data][attribute.to_s.camel_case_lower])
38
+ end
39
+ end
40
+
41
+ def self.collection(arg)
42
+ arg[:data].map do |album|
43
+ Album.new(data: album, client: @client)
44
+ end
45
+ end
46
+
47
+ # Top level methods
48
+
49
+ def new_releases(params)
50
+ response = @client.get('/albums/new', params: params)
51
+ Album.collection(data: response['albums'], client: @client)
52
+ end
53
+
54
+ def staff_picks(params)
55
+ response = @client.get('/albums/picks', params: params)
56
+ Album.collection(data: response['albums'], client: @client)
57
+ end
58
+
59
+ def top(params)
60
+ response = @client.get('/albums/top', params: params)
61
+ Album.collection(data: response['albums'], client: @client)
62
+ end
63
+
64
+ def find(arg)
65
+ return find_by_id(arg) if Napster::Moniker.check(arg, :album)
66
+ find_by_name(arg)
67
+ end
68
+
69
+ def find_by_id(id)
70
+ response = @client.get("/albums/#{id}")
71
+ Album.new(data: response['albums'].first, client: @client)
72
+ end
73
+
74
+ def find_all_by_name(name)
75
+ options = {
76
+ params: {
77
+ q: name,
78
+ type: 'album'
79
+ }
80
+ }
81
+ response = @client.get('/search', options)
82
+ Album.collection(data: response['data'])
83
+ end
84
+
85
+ def find_by_name(name)
86
+ find_all_by_name(name).first
87
+ end
88
+
89
+ # Instance methods
90
+
91
+ def tracks(params)
92
+ response = @client.get("/albums/#{@id}/tracks", params: params)
93
+ Track.collection(data: response['tracks'], client: @client)
94
+ end
95
+ end
96
+ end
97
+ end