napster 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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