soundcloud 0.3.1 → 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: df5b0cab01fdc8985e0059c4fbaebbb29672f58c17c33ce1cce2765861eeee3c
4
+ data.tar.gz: 730fc7f919b60e2aa582e755560284ebc07957d51054115da4093adfa71ced0c
5
+ SHA512:
6
+ metadata.gz: 93d8a10bd11b13c43b18e6c5449a3e640f0f43e93eb40473869f3326d84e4f6c4c347929acf9e93ddb0e4f1fa6cf8df75f68d25818636f6738efbe2a2dfafd54
7
+ data.tar.gz: 71e706b85e323052f13bd39c1f32368c4cbaaccb41ceaf5e2eed65438502dc025e8adf30191af06ae9ea35efad492faad3a85443ef2cb0d57b4ad0a0b29c6e66
data/LICENSE.md ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009-2015 SoundCloud Ltd., Johannes Wagener, Erik Michaels-Ober
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,155 +1,189 @@
1
- # Soundcloud API Wrapper
2
- ## Description
3
- The Soundcloud gem is a thin wrapper for the Soundcloud API based off of the httparty gem.
4
- It provides simple methods to handle authorization and to execute HTTP calls.
1
+ # ⚠️⚠️DEPRECATED - NO LONGER MAINTAINED⚠️⚠️
2
+ This repository is no longer maintained by the SoundCloud team due to capacity constraints. We're instead focusing our efforts on improving the API & the developer platform. Please note, at the time of updating this, the repo is already not in sync with the latest API changes.
3
+
4
+ We recommend the community to fork this repo in order to maintain the SDK. We'd be more than happy to make a reference on our developer that the developers can use different SDKs build by the community. In case you need to reach out to us, please head over to https://github.com/soundcloud/api/issues
5
+
6
+ ---
7
+
8
+ # SoundCloud API Wrapper
9
+
10
+ [![Build Status](https://travis-ci.org/soundcloud/soundcloud-ruby.png?branch=master)](https://travis-ci.org/soundcloud/soundcloud-ruby)
5
11
 
6
- ## Requirements
7
- * httmultiparty
8
- * httparty
9
- * crack
10
- * multipart-upload
11
- * hashie
12
+ ## Description
13
+ The official SoundCloud API wrapper. It provides simple methods to handle
14
+ authorization and to execute HTTP calls.
12
15
 
13
16
  ## Installation
14
- gem install soundcloud
17
+ ```sh
18
+ gem install soundcloud
19
+ ```
15
20
 
16
21
  ## Examples
17
- #### Print links of the 10 hottest tracks
18
- # register a client with YOUR_CLIENT_ID as client_id_
19
- client = Soundcloud.new(:client_id => YOUR_CLIENT_ID)
20
- # get 10 hottest tracks
21
- tracks = client.get('/tracks', :limit => 10, :order => 'hotness')
22
- # print each link
23
- tracks.each do |track|
24
- puts track.permalink_url
25
- end
26
-
22
+
23
+ The following examples are for the [latest gem version](https://rubygems.org/gems/soundcloud).
24
+
25
+ ```ruby
26
+ SoundCloud::VERSION
27
+ # => "0.3.4"
28
+ ```
29
+
30
+ #### Print links of the 10 most recent tracks
31
+ ```ruby
32
+ # register a client with YOUR_CLIENT_ID as client_id_
33
+ client = SoundCloud.new(:client_id => YOUR_CLIENT_ID)
34
+ # get newest tracks
35
+ tracks = client.get('/tracks', :limit => 10)
36
+ # print each link
37
+ tracks.each do |track|
38
+ puts track.permalink_url
39
+ end
40
+ ```
41
+
27
42
  #### OAuth2 user credentials flow and print the username of the authenticated user
28
- # register a new client, which will exchange the username, password for an access_token
29
- # NOTE: the SoundCloud API Docs advise not to use the user credentials flow in a web app.
30
- # In any case, never store the password of a user.
31
- client = Soundcloud.new({
32
- :client_id => YOUR_CLIENT_ID,
33
- :client_secret => YOUR_CLIENT_SECRET,
34
- :username => 'some@email.com',
35
- :password => 'userpass'
36
- })
37
-
38
- # print logged in username
39
- puts client.get('/me').username
43
+ ```ruby
44
+ # register a new client, which will exchange the username, password for an access_token
45
+ # NOTE: the SoundCloud API Docs advise not to use the user credentials flow in a web app.
46
+ # In any case, never store the password of a user.
47
+ client = SoundCloud.new({
48
+ :client_id => YOUR_CLIENT_ID,
49
+ :client_secret => YOUR_CLIENT_SECRET,
50
+ :username => 'some@email.com',
51
+ :password => 'userpass'
52
+ })
53
+
54
+ # print logged in username
55
+ puts client.get('/me').username
56
+ ```
40
57
 
41
58
  #### OAuth2 authorization code flow
42
- client = Soundcloud.new({
43
- :client_id => YOUR_CLIENT_ID,
44
- :client_secret => YOUR_CLIENT_SECRET,
45
- :redirect_uri => YOUR_REDIRECT_URI,
46
- })
47
-
48
- redirect client.authorize_url()
49
- # the user should be redirected to "https://soundcloud.com/connect?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=YOUR_REDIRECT_URI"
50
- # after granting access he will be redirected back to YOUR_REDIRECT_URI
51
- # in your respective handler you can build an exchange token from the transmitted code
52
- client.exchange_token(:code => params[:code])
59
+ ```ruby
60
+ client = SoundCloud.new({
61
+ :client_id => YOUR_CLIENT_ID,
62
+ :client_secret => YOUR_CLIENT_SECRET,
63
+ :redirect_uri => YOUR_REDIRECT_URI,
64
+ })
65
+
66
+ redirect client.authorize_url()
67
+ # the user should be redirected to "https://soundcloud.com/connect?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=YOUR_REDIRECT_URI"
68
+ # after granting access he will be redirected back to YOUR_REDIRECT_URI
69
+ # in your respective handler you can build an exchange token from the transmitted code
70
+ client.exchange_token(:code => params[:code])
71
+ ```
53
72
 
54
73
  #### OAuth2 refresh token flow, upload a track and print its link
55
- # register a new client which will exchange an existing refresh_token for an access_token
56
- client = Soundcloud.new({
57
- :client_id => YOUR_CLIENT_ID,
58
- :client_secret => YOUR_CLIENT_SECRET,
59
- :refresh_token => SOME_REFRESH_TOKEN
60
- })
61
-
62
- # upload a new track with track.mp3 as audio and image.jpg as artwork
63
- track = client.post('/tracks', :track => {
64
- :title => 'a new track',
65
- :asset_data => File.new('audio.mp3')
66
- })
67
-
68
- # print new tracks link
69
- puts track.permalink_url
74
+ ```ruby
75
+ # register a new client which will exchange an existing refresh_token for an access_token
76
+ client = SoundCloud.new({
77
+ :client_id => YOUR_CLIENT_ID,
78
+ :client_secret => YOUR_CLIENT_SECRET,
79
+ :refresh_token => SOME_REFRESH_TOKEN
80
+ })
81
+
82
+ # upload a new track with audio.mp3 as audio and image.jpg as artwork
83
+ track = client.post('/tracks', :track => {
84
+ :title => 'a new track',
85
+ :asset_data => File.new('audio.mp3')
86
+ })
87
+
88
+ # print new tracks link
89
+ puts track.permalink_url
90
+ ```
70
91
 
71
92
  #### Resolve a track url and print its id
72
- # register the client
73
- client = Soundcloud.new(:client_id => YOUR_CLIENT_ID)
74
-
75
- # call the resolve endpoint with a track url
76
- track = client.get('/resolve', :url => "http://soundcloud.com/forss/flickermood")
77
-
78
- # print the track id
79
- puts track.id
80
-
81
- #### Register a client for http://sandbox-soundcloud.com with an existing access_token and start following a user
82
- # register a client for http://sandbox-soundcloud.com with existing access_token
83
- client = Soundcloud.new(:site => 'sandbox-soundcloud.com', :access_token => SOME_ACCESS_TOKEN)
84
-
85
- # create a new following
86
- user_id_to_follow = 123
87
- client.put("/me/followings/#{user_id_to_follow}")
93
+ ```ruby
94
+ # register the client
95
+ client = SoundCloud.new(:client_id => YOUR_CLIENT_ID)
96
+
97
+ # call the resolve endpoint with a track url
98
+ track = client.get('/resolve', :url => "http://soundcloud.com/forss/flickermood")
99
+
100
+ # print the track id
101
+ puts track.id
102
+ ```
88
103
 
89
104
  ### Initializing a client with an access token and updating the users profile description
90
- # initializing a client with an access token
91
- client = Soundcloud.new(:access_token => SOME_ACCESS_TOKEN)
92
-
93
- # updating the users profile description
94
- client.put("/me", :user => {:description => "a new description"})
95
-
96
-
97
- ### Add a track to a playlist / set
98
- client = Soundcloud.new(:access_token => "A_VALID_TOKEN")
99
-
100
- # get my last playlist
101
- playlist = client.get("/me/playlists").first
102
-
103
- # get ids of contained tracks
104
- track_ids = playlist.tracks.map(&:id) # => [22448500, 21928809]
105
-
106
- # adding a new track 21778201
107
- track_ids << 21778201 # => [22448500, 21928809, 21778201]
108
-
109
- # map array of ids to array of track objects:
110
- tracks = track_ids.map { |id| {:id => id} } # => [{:id=>22448500}, {:id=>21928809}, {:id=>21778201}]
111
-
112
- # send update/put request to playlist
113
- playlist = client.put(playlist.uri, :playlist => {
114
- :tracks => tracks
115
- })
116
-
117
- # print the list of track ids of the updated playlist:
118
- p playlist.tracks.map(&:id)
105
+ ```ruby
106
+ # initializing a client with an access token
107
+ client = SoundCloud.new(:access_token => SOME_ACCESS_TOKEN)
108
+
109
+ # updating the users profile description
110
+ client.put("/me", :user => {:description => "a new description"})
111
+ ```
112
+
113
+ ### Add a track to a playlist / set
114
+ ```ruby
115
+ client = SoundCloud.new(:access_token => "A_VALID_TOKEN")
116
+
117
+ # get my last playlist
118
+ playlist = client.get("/me/playlists").first
119
+
120
+ # get ids of contained tracks
121
+ track_ids = playlist.tracks.map(&:id) # => [22448500, 21928809]
122
+
123
+ # adding a new track 21778201
124
+ track_ids << 21778201 # => [22448500, 21928809, 21778201]
125
+
126
+ # map array of ids to array of track objects:
127
+ tracks = track_ids.map{|id| {:id => id}} # => [{:id=>22448500}, {:id=>21928809}, {:id=>21778201}]
128
+
129
+ # send update/put request to playlist
130
+ playlist = client.put(playlist.uri, :playlist => {
131
+ :tracks => tracks
132
+ })
133
+
134
+ # print the list of track ids of the updated playlist:
135
+ p playlist.tracks.map(&:id)
136
+ ```
119
137
 
120
138
  ## Interface
121
- #### Soundcloud.new(options={})
122
- Stores the passed options and call exchange_token in case options are passed that allow an exchange of tokens.
123
-
124
- #### Soundcloud#exchange_token(options={})
125
- Stores the passed options and try to exchange tokens if no access_token is present and:
126
- - refresh_token, client_id and client_secret is present.
127
- - client_id, client_secret, username, password is present
128
- - client_id, client_secret, redirect_uri, code is present
129
-
130
- #### Soundcloud#authorize_url(options={})
131
- Stores the passed options except for ``state`` and ``display`` and return an authorize url.
132
- The ``client_id`` and ``redirect_uri`` options need to present to generate the authorize url.
133
- The ``state`` and ``display`` options can be used to set the parameters accordingly in the authorize url.
134
-
135
- #### Soundcloud#get, Soundcloud#post, Soundcloud#put, Soundcloud#delete, Soundcloud#head
136
- These methods expose all available HTTP methods. They all share the signature (path_or_uri, query={}, options={}).
137
- The query hash will be merged with the options hash and passed to httparty. Depending on if the client is authorized it will either add the client_id or the access_token as a query parameter.
138
- In case an access_token is expired and a refresh_token, client_id and client_secret is present it will try to refresh the access_token and retry the call.
139
- The response is either a Hashie::Mash or an array of Hashie::Mashs. The mashs expose all resource attributes as methods and the original response through #response.
140
-
141
- #### Soundcloud#client_id, client_secret, access_token, refresh_token, use_ssl?
139
+ #### SoundCloud.new(options={})
140
+ Stores the passed options and call exchange_token in case options are passed
141
+ that allow an exchange of tokens.
142
+
143
+ #### SoundCloud#exchange_token(options={})
144
+ Stores the passed options and try to exchange tokens if no access_token is
145
+ present and:
146
+
147
+ * `refresh_token`, `client_id` and `client_secret` is present.
148
+ * `client_id`, `client_secret`, `username`, and `password` is present
149
+ * `client_id`, `client_secret`, `redirect_uri`, and `code` is present
150
+
151
+ #### SoundCloud#authorize_url(options={})
152
+ Stores the passed options except for `state` and `display` and return an
153
+ authorize url. The `client_id` and `redirect_uri` options need to present to
154
+ generate the authorize url. The `state` and `display` options can be used to
155
+ set the parameters accordingly in the authorize url.
156
+
157
+ #### SoundCloud#get, SoundCloud#post, SoundCloud#put, SoundCloud#delete, SoundCloud#head
158
+ These methods expose all available HTTP methods. They all share the signature
159
+ `(path_or_uri, query={}, options={})`. The query hash will be merged with the
160
+ options hash and passed to httparty. Depending on if the client is authorized
161
+ it will either add the client_id or the access_token as a query parameter. In
162
+ case an access_token is expired and a `refresh_token`, `client_id` and
163
+ `client_secret` is present it will try to refresh the `access_token` and retry
164
+ the call. The response is either a Hashie::Mash or an array of Hashie::Mashes.
165
+ The mashes expose all resource attributes as methods and the original response
166
+ through `HashResponseWrapper#response`.
167
+
168
+ #### SoundCloud#client_id, client_secret, access_token, refresh_token, use_ssl?
142
169
  These methods are accessors for the stored options.
143
170
 
144
- ### Soundcloud#on_exchange_token
145
- A Proc passed to on_exchange_token will be called each time a token was successfully exchanged or refreshed
171
+ #### SoundCloud#on_exchange_token
172
+ A Proc passed to on_exchange_token will be called each time a token was
173
+ successfully exchanged or refreshed
146
174
 
147
- ### Soundcloud#expires_at
148
- Returns a date based on the expires_in attribute returned from a token exchange.
175
+ #### SoundCloud#expires_at
176
+ Returns a date based on the `expires_in` attribute returned from a token
177
+ exchange.
149
178
 
150
- ### Soundcloud#expired?
151
- Will return true or false depending on if expires_at is in the past.
179
+ #### SoundCloud#expired?
180
+ Will return true or false depending on if `expires_at` is in the past.
152
181
 
153
182
  #### Error Handling
154
- In case a request was not successful a Soundcloud::ResponseError will be raised.
155
- The original HTTParty response is available through Soundcloud::ResponseError#response.
183
+ In case a request was not successful a SoundCloud::ResponseError will be
184
+ raised. The original HTTParty response is available through
185
+ `SoundCloud::ResponseError#response`.
186
+
187
+ ## Documentation
188
+
189
+ For more code examples, please visit the [SoundCloud API Documentation](http://developers.soundcloud.com/docs).
@@ -1,8 +1,12 @@
1
- class Soundcloud::ArrayResponseWrapper < Array
2
- attr_reader :response
3
- def initialize(response=[])
4
- mashes = response.map { |o| Hashie::Mash.new(o) }
5
- self.replace(mashes)
6
- @response = response
1
+ module SoundCloud
2
+ class ArrayResponseWrapper < Array
3
+ attr_reader :response
4
+ def initialize(response=[])
5
+ @response = response
6
+ mashes = response.map do |object|
7
+ Hashie::Mash.new(object)
8
+ end
9
+ replace(mashes)
10
+ end
7
11
  end
8
- end
12
+ end
@@ -0,0 +1,211 @@
1
+ require 'soundcloud/version'
2
+
3
+ module SoundCloud
4
+ class Client
5
+ include HTTMultiParty
6
+ USER_AGENT = "SoundCloud Ruby Wrapper #{SoundCloud::VERSION}"
7
+ CLIENT_ID_PARAM_NAME = :client_id
8
+ API_SUBHOST = 'api'
9
+ AUTHORIZE_PATH = '/connect'
10
+ TOKEN_PATH = '/oauth2/token'
11
+ DEFAULT_OPTIONS = {
12
+ :site => 'soundcloud.com',
13
+ :on_exchange_token => lambda {}
14
+ }
15
+
16
+ attr_accessor :options
17
+ headers({"User-Agent" => USER_AGENT})
18
+
19
+ def initialize(options={})
20
+ store_options(options)
21
+ if access_token.nil? && (options_for_refresh_flow_present? || options_for_credentials_flow_present? || options_for_code_flow_present? || client_secret)
22
+ exchange_token
23
+ end
24
+ raise ArgumentError, "At least a client_id or an access_token must be present" if client_id.nil? && access_token.nil?
25
+ end
26
+
27
+ def get(path, query={}, options={})
28
+ handle_response {
29
+ self.class.get(*construct_query_arguments(path, options.merge(:query => query)))
30
+ }
31
+ end
32
+
33
+ def post(path, body={}, options={})
34
+ handle_response {
35
+ self.class.post(*construct_query_arguments(path, options.merge(:body => body), :body))
36
+ }
37
+ end
38
+
39
+ def put(path, body={}, options={})
40
+ handle_response {
41
+ self.class.put(*construct_query_arguments(path, options.merge(:body => body), :body))
42
+ }
43
+ end
44
+
45
+ def delete(path, query={}, options={})
46
+ handle_response {
47
+ self.class.delete(*construct_query_arguments(path, options.merge(:query => query)))
48
+ }
49
+ end
50
+
51
+ def head(path, query={}, options={})
52
+ handle_response {
53
+ self.class.head(*construct_query_arguments(path, options.merge(:query => query)))
54
+ }
55
+ end
56
+
57
+ # accessors for options
58
+ def client_id
59
+ @options[:client_id]
60
+ end
61
+
62
+ def client_secret
63
+ @options[:client_secret]
64
+ end
65
+
66
+ def access_token
67
+ @options[:access_token]
68
+ end
69
+
70
+ def refresh_token
71
+ @options[:refresh_token]
72
+ end
73
+
74
+ def redirect_uri
75
+ @options[:redirect_uri]
76
+ end
77
+
78
+ def expires_at
79
+ @options[:expires_at]
80
+ end
81
+
82
+ def expired?
83
+ (expires_at.nil? || expires_at < Time.now)
84
+ end
85
+
86
+ def use_ssl?
87
+ !!(@options[:use_ssl?] || access_token)
88
+ end
89
+
90
+ def site
91
+ @options[:site]
92
+ end
93
+ alias host site
94
+
95
+ def api_host
96
+ [API_SUBHOST, host].join('.')
97
+ end
98
+
99
+ def authorize_url(options={})
100
+ additional_params = [:display, :state, :scope].map do |param_name|
101
+ value = options.delete(param_name)
102
+ "#{param_name}=#{CGI.escape value}" unless value.nil?
103
+ end.compact.join("&")
104
+ store_options(options)
105
+ "https://#{host}#{AUTHORIZE_PATH}?response_type=code_and_token&client_id=#{client_id}&redirect_uri=#{URI.escape(redirect_uri)}&#{additional_params}"
106
+ end
107
+
108
+ def exchange_token(options={})
109
+ store_options(options)
110
+ raise ArgumentError, 'client_id and client_secret is required to retrieve an access_token' if client_id.nil? || client_secret.nil?
111
+
112
+ params = if options_for_refresh_flow_present?
113
+ {
114
+ :grant_type => 'refresh_token',
115
+ :refresh_token => refresh_token,
116
+ }
117
+ elsif options_for_credentials_flow_present?
118
+ puts "Warning: Password grant is deprecated, see https://developers.soundcloud.com/blog/security-updates-api"
119
+ {
120
+ :grant_type => 'password',
121
+ :username => @options[:username],
122
+ :password => @options[:password],
123
+ }
124
+ elsif options_for_code_flow_present?
125
+ {
126
+ :grant_type => 'authorization_code',
127
+ :redirect_uri => @options[:redirect_uri],
128
+ :code => @options[:code],
129
+ }
130
+ elsif client_secret
131
+ { :grant_type => 'client_credentials' }
132
+ end
133
+
134
+ params.merge!(:client_id => client_id, :client_secret => client_secret)
135
+
136
+ response = handle_response(false) {
137
+ self.class.post("https://#{api_host}#{TOKEN_PATH}", :query => params)
138
+ }
139
+
140
+ @options.merge!(:access_token => response.access_token, :refresh_token => response.refresh_token)
141
+ @options[:expires_at] = Time.now + response.expires_in if response.expires_in
142
+ @options[:on_exchange_token].call(*[(self if @options[:on_exchange_token].arity == 1)].compact)
143
+ response
144
+ end
145
+
146
+ def on_exchange_token(&block)
147
+ store_options(:on_exchange_token => block)
148
+ end
149
+
150
+ private
151
+
152
+ def handle_response(refreshing_enabled=true, &block)
153
+ response = block.call
154
+ if response && !response.success?
155
+ if response.code == 401 && refreshing_enabled && options_for_refresh_flow_present?
156
+ exchange_token
157
+ # TODO it should return the original
158
+ handle_response(false, &block)
159
+ else
160
+ raise ResponseError.new(response)
161
+ end
162
+ elsif response_is_a?(response, Hash)
163
+ HashResponseWrapper.new(response)
164
+ elsif response_is_a?(response, Array)
165
+ ArrayResponseWrapper.new(response)
166
+ elsif response && response.success?
167
+ response
168
+ end
169
+ end
170
+
171
+ def options_for_refresh_flow_present?
172
+ !!@options[:refresh_token]
173
+ end
174
+
175
+ def options_for_credentials_flow_present?
176
+ !!(@options[:username] && @options[:password])
177
+ end
178
+
179
+ def options_for_code_flow_present?
180
+ !!(@options[:code] && @options[:redirect_uri])
181
+ end
182
+
183
+ def store_options(options={})
184
+ @options ||= DEFAULT_OPTIONS.dup
185
+ @options.merge!(options)
186
+ end
187
+
188
+ def construct_query_arguments(path_or_uri, options={}, body_or_query=:query)
189
+ uri = URI.parse(path_or_uri)
190
+ path = uri.path
191
+ scheme = use_ssl? ? 'https' : 'http'
192
+ options = options.dup
193
+ options[body_or_query] ||= {}
194
+ options[body_or_query][:format] = "json"
195
+ if access_token
196
+ options[:headers] = { 'Authorization' => "OAuth #{access_token}" }
197
+ else
198
+ puts "Warning: Authorization via ClientId is deprecated, see https://developers.soundcloud.com/blog/security-updates-api"
199
+ options[body_or_query][CLIENT_ID_PARAM_NAME] = client_id
200
+ end
201
+ [
202
+ "#{scheme}://#{api_host}#{path}#{uri.query ? "?#{uri.query}" : ""}",
203
+ options
204
+ ]
205
+ end
206
+
207
+ def response_is_a?(response, type)
208
+ response.is_a?(type) || (response.respond_to?(:parsed_response) && response.parsed_response.is_a?(type))
209
+ end
210
+ end
211
+ end
@@ -1,7 +1,9 @@
1
- class Soundcloud::HashResponseWrapper < Hashie::Mash
2
- attr_reader :response
3
- def initialize(response=nil, *args)
4
- super(response, *args)
5
- @response = response
1
+ module SoundCloud
2
+ class HashResponseWrapper < Hashie::Mash
3
+ attr_reader :response
4
+ def initialize(response=nil, *args)
5
+ @response = response
6
+ super(response, *args)
7
+ end
6
8
  end
7
9
  end
@@ -0,0 +1,44 @@
1
+ module SoundCloud
2
+ class ResponseError < HTTParty::ResponseError
3
+ STATUS_CODES = {
4
+ 400 => "Bad Request",
5
+ 401 => "Unauthorized",
6
+ 402 => "Payment Required",
7
+ 403 => "Forbidden",
8
+ 404 => "Not Found",
9
+ 405 => "Method Not Allowed",
10
+ 406 => "Not Acceptable",
11
+ 407 => "Proxy Authentication Required",
12
+ 408 => "Request Timeout",
13
+ 409 => "Conflict",
14
+ 410 => "Gone",
15
+ 411 => "Length Required",
16
+ 412 => "Precondition Failed",
17
+ 413 => "Request Entity Too Large",
18
+ 414 => "Request-URI Too Long",
19
+ 415 => "Unsupported Media Type",
20
+ 416 => "Requested Range Not Satisfiable",
21
+ 417 => "Expectation Failed",
22
+ 422 => "Unprocessable Entity",
23
+ 423 => "Locked",
24
+ 424 => "Failed Dependency",
25
+ 426 => "Upgrade Required",
26
+ 500 => "Internal Server Error",
27
+ 501 => "Not Implemented",
28
+ 502 => "Bad Gateway",
29
+ 503 => "Service Unavailable",
30
+ 504 => "Gateway Timeout",
31
+ 505 => "HTTP Version Not Supported",
32
+ 507 => "Insufficient Storage",
33
+ 510 => "Not Extended"
34
+ }
35
+
36
+ def message
37
+ error = response.parsed_response['error'] || response.parsed_response['errors']['error']
38
+ "HTTP status: #{response.code} #{STATUS_CODES[response.code]} Error: #{error}"
39
+ rescue
40
+ "HTTP status: #{response.code} #{STATUS_CODES[response.code]}"
41
+ end
42
+
43
+ end
44
+ end
@@ -1,3 +1,3 @@
1
- class Soundcloud
2
- VERSION = '0.3.1'
1
+ module SoundCloud
2
+ VERSION = '0.3.5'
3
3
  end
data/lib/soundcloud.rb CHANGED
@@ -1,217 +1,31 @@
1
- require 'httmultiparty'
2
1
  require 'hashie'
2
+ require 'httmultiparty'
3
3
  require 'uri'
4
- require 'soundcloud/version'
5
-
6
- class Soundcloud
7
- class ResponseError < HTTParty::ResponseError
8
- def message
9
- error = response.parsed_response['error'] || response.parsed_response['errors']['error']
10
- "HTTP status: #{StatusCodes.interpret_status(response.code)} Error: #{error}"
11
- rescue
12
- "HTTP status: #{StatusCodes.interpret_status(response.code)}"
13
- end
14
-
15
- module StatusCodes
16
- STATUS_CODES = {
17
- 100 => "Continue",
18
- 101 => "Switching Protocols",
19
- 102 => "Processing",
20
-
21
- 200 => "OK",
22
- 201 => "Created",
23
- 202 => "Accepted",
24
- 203 => "Non-Authoritative Information",
25
- 204 => "No Content",
26
- 205 => "Reset Content",
27
- 206 => "Partial Content",
28
- 207 => "Multi-Status",
29
- 226 => "IM Used",
30
-
31
- 300 => "Multiple Choices",
32
- 301 => "Moved Permanently",
33
- 302 => "Found",
34
- 303 => "See Other",
35
- 304 => "Not Modified",
36
- 305 => "Use Proxy",
37
- 307 => "Temporary Redirect",
38
-
39
- 400 => "Bad Request",
40
- 401 => "Unauthorized",
41
- 402 => "Payment Required",
42
- 403 => "Forbidden",
43
- 404 => "Not Found",
44
- 405 => "Method Not Allowed",
45
- 406 => "Not Acceptable",
46
- 407 => "Proxy Authentication Required",
47
- 408 => "Request Timeout",
48
- 409 => "Conflict",
49
- 410 => "Gone",
50
- 411 => "Length Required",
51
- 412 => "Precondition Failed",
52
- 413 => "Request Entity Too Large",
53
- 414 => "Request-URI Too Long",
54
- 415 => "Unsupported Media Type",
55
- 416 => "Requested Range Not Satisfiable",
56
- 417 => "Expectation Failed",
57
- 422 => "Unprocessable Entity",
58
- 423 => "Locked",
59
- 424 => "Failed Dependency",
60
- 426 => "Upgrade Required",
61
-
62
- 500 => "Internal Server Error",
63
- 501 => "Not Implemented",
64
- 502 => "Bad Gateway",
65
- 503 => "Service Unavailable",
66
- 504 => "Gateway Timeout",
67
- 505 => "HTTP Version Not Supported",
68
- 507 => "Insufficient Storage",
69
- 510 => "Not Extended"
70
- }
71
-
72
- def self.interpret_status(status)
73
- "#{status} #{STATUS_CODES[status.to_i]}".strip
74
- end
75
- end
76
- end
77
-
78
- class UnauthorizedResponseError < ResponseError; end
79
- USER_AGENT = "SoundCloud Ruby Wrapper #{VERSION}"
80
-
81
- include HTTMultiParty
82
- CLIENT_ID_PARAM_NAME = :client_id
83
- API_SUBHOST = 'api'
84
- AUTHORIZE_PATH = '/connect'
85
- TOKEN_PATH = '/oauth2/token'
86
- DEFAULT_OPTIONS = {
87
- :site => 'soundcloud.com',
88
- :on_exchange_token => lambda {}
89
- }
90
-
91
- attr_accessor :options
92
- headers({"User-Agent" => USER_AGENT})
93
-
94
- def initialize(options={})
95
- store_options(options)
96
- if access_token.nil? && (options_for_refresh_flow_present? ||
97
- options_for_credentials_flow_present? || options_for_code_flow_present?)
98
- exchange_token
99
- end
100
-
101
- raise ArgumentError, "At least a client_id or an access_token must be present" if client_id.nil? && access_token.nil?
102
- end
103
-
104
- def get (path, query={}, options={}); handle_response { self.class.get( *construct_query_arguments(path, options.merge(:query => query)) ) } end
105
- def post (path, body={}, options={}); handle_response { self.class.post( *construct_query_arguments(path, options.merge(:body => body), :body) ) } end
106
- def put (path, body={}, options={}); handle_response { self.class.put( *construct_query_arguments(path, options.merge(:body => body), :body) ) } end
107
- def delete(path, query={}, options={}); handle_response { self.class.delete( *construct_query_arguments(path, options.merge(:query => query)) ) } end
108
- def head (path, query={}, options={}); handle_response { self.class.head( *construct_query_arguments(path, options.merge(:query => query)) ) } end
109
-
110
- # accessors for options
111
- def client_id; @options[:client_id]; end
112
- def client_secret; @options[:client_secret]; end
113
- def access_token; @options[:access_token]; end
114
- def refresh_token; @options[:refresh_token]; end
115
- def redirect_uri; @options[:redirect_uri]; end
116
- def expires_at; @options[:expires_at]; end
117
4
 
118
- def expired?
119
- (expires_at.nil? || expires_at < Time.now)
120
- end
121
-
122
- def use_ssl?;
123
- !! @options[:use_ssl?] || access_token
124
- end
125
-
126
- def site; @options[:site]; end
127
-
128
- def host; site; end
129
- def api_host; [API_SUBHOST, host].join('.'); end
130
-
131
- def authorize_url(options={})
132
- additional_params = [:display, :state, :scope].map do |param_name|
133
- value = options.delete(param_name)
134
- "#{param_name}=#{CGI.escape value}" unless value.nil?
135
- end.compact.join("&")
136
-
137
- store_options(options)
138
- "https://#{host}#{AUTHORIZE_PATH}?response_type=code_and_token&client_id=#{client_id}&redirect_uri=#{URI.escape redirect_uri}&#{additional_params}"
139
- end
5
+ require 'soundcloud/array_response_wrapper'
6
+ require 'soundcloud/client'
7
+ require 'soundcloud/hash_response_wrapper'
8
+ require 'soundcloud/response_error'
9
+ require 'soundcloud/version'
140
10
 
141
- def exchange_token(options={})
142
- store_options(options)
143
- raise ArgumentError, 'client_id and client_secret is required to retrieve an access_token' if client_id.nil? || client_secret.nil?
144
- client_params = {:client_id => client_id, :client_secret => client_secret}
145
- params = if options_for_refresh_flow_present?
146
- {:grant_type => 'refresh_token', :refresh_token => refresh_token}
147
- elsif options_for_credentials_flow_present?
148
- {:grant_type => 'password', :username => @options[:username], :password => @options[:password]}
149
- elsif options_for_code_flow_present?
150
- {:grant_type => 'authorization_code', :redirect_uri => @options[:redirect_uri], :code => @options[:code]}
151
- end
152
- params.merge!(client_params)
153
- response = handle_response(false) {
154
- self.class.post("https://#{api_host}#{TOKEN_PATH}", :query => params)
155
- }
156
- @options.merge!(:access_token => response.access_token, :refresh_token => response.refresh_token)
157
- @options[:expires_at] = Time.now + response.expires_in if response.expires_in
158
- @options[:on_exchange_token].call(*[(self if @options[:on_exchange_token].arity == 1)].compact)
159
- response
160
- end
11
+ module SoundCloud
161
12
 
162
- def on_exchange_token(&block)
163
- store_options(:on_exchange_token => block)
13
+ def new(options={})
14
+ Client.new(options)
164
15
  end
16
+ module_function :new
165
17
 
166
- private
167
- def handle_response(refreshing_enabled=true, &block)
168
- response = block.call
169
- if response && !response.success?
170
- if response.code == 401 && refreshing_enabled && options_for_refresh_flow_present?
171
- exchange_token
172
- # TODO it should return the original
173
- handle_response(false, &block)
174
- else
175
- raise ResponseError.new(response)
176
- end
177
- elsif response.is_a? Hash
178
- HashResponseWrapper.new(response)
179
- elsif response.is_a? Array
180
- ArrayResponseWrapper.new(response)
181
- elsif response && response.success?
182
- response
183
- end
18
+ def method_missing(method_name, *args, &block)
19
+ return super unless respond_to_missing?(method_name)
20
+ Client.send(method_name, *args, &block)
184
21
  end
22
+ module_function :method_missing
185
23
 
186
- def options_for_refresh_flow_present?; !! @options[:refresh_token]; end
187
- def options_for_credentials_flow_present?; !! @options[:username] && @options[:password]; end
188
- def options_for_code_flow_present?; !! @options[:code] && @options[:redirect_uri]; end
189
-
190
- def store_options(options={})
191
- @options ||= DEFAULT_OPTIONS.dup
192
- @options.merge! options
24
+ def respond_to_missing?(method_name, include_private=false)
25
+ Client.respond_to?(method_name, include_private)
193
26
  end
27
+ module_function :respond_to_missing?
194
28
 
195
-
196
- def construct_query_arguments(path_or_uri, options={}, body_or_query=:query)
197
- uri = URI.parse(path_or_uri)
198
- path = uri.path
199
-
200
- scheme = use_ssl? ? 'https' : 'http'
201
- options = options.dup
202
- options[body_or_query] ||= {}
203
- options[body_or_query][:format] = "json"
204
- if access_token
205
- options[body_or_query][:oauth_token] = access_token
206
- else
207
- options[body_or_query][CLIENT_ID_PARAM_NAME] = client_id
208
- end
209
- [
210
- "#{scheme}://#{api_host}#{path}#{uri.query ? "?#{uri.query}" : ""}",
211
- options
212
- ]
213
- end
214
29
  end
215
30
 
216
- require 'soundcloud/array_response_wrapper'
217
- require 'soundcloud/hash_response_wrapper'
31
+ Soundcloud = SoundCloud
metadata CHANGED
@@ -1,130 +1,94 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: soundcloud
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
5
- prerelease:
4
+ version: 0.3.5
6
5
  platform: ruby
7
6
  authors:
8
7
  - Johannes Wagener
9
- autorequire:
8
+ - Erik Michaels-Ober
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-04 00:00:00.000000000 Z
12
+ date: 2021-09-24 00:00:00.000000000 Z
13
13
  dependencies:
14
- - !ruby/object:Gem::Dependency
15
- name: httparty
16
- requirement: !ruby/object:Gem::Requirement
17
- none: false
18
- requirements:
19
- - - ! '>='
20
- - !ruby/object:Gem::Version
21
- version: 0.7.3
22
- type: :runtime
23
- prerelease: false
24
- version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
- requirements:
27
- - - ! '>='
28
- - !ruby/object:Gem::Version
29
- version: 0.7.3
30
14
  - !ruby/object:Gem::Dependency
31
15
  name: httmultiparty
32
16
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
17
  requirements:
35
- - - ! '>='
18
+ - - "~>"
36
19
  - !ruby/object:Gem::Version
37
- version: '0.3'
20
+ version: 0.3.0
38
21
  type: :runtime
39
22
  prerelease: false
40
23
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
24
  requirements:
43
- - - ! '>='
25
+ - - "~>"
44
26
  - !ruby/object:Gem::Version
45
- version: '0.3'
27
+ version: 0.3.0
46
28
  - !ruby/object:Gem::Dependency
47
29
  name: hashie
48
30
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
31
  requirements:
51
- - - ! '>='
32
+ - - ">="
52
33
  - !ruby/object:Gem::Version
53
34
  version: '0'
54
35
  type: :runtime
55
36
  prerelease: false
56
37
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
38
  requirements:
59
- - - ! '>='
39
+ - - ">="
60
40
  - !ruby/object:Gem::Version
61
41
  version: '0'
62
42
  - !ruby/object:Gem::Dependency
63
- name: rspec
64
- requirement: !ruby/object:Gem::Requirement
65
- none: false
66
- requirements:
67
- - - ~>
68
- - !ruby/object:Gem::Version
69
- version: 2.5.0
70
- type: :development
71
- prerelease: false
72
- version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
- requirements:
75
- - - ~>
76
- - !ruby/object:Gem::Version
77
- version: 2.5.0
78
- - !ruby/object:Gem::Dependency
79
- name: fakeweb
43
+ name: bundler
80
44
  requirement: !ruby/object:Gem::Requirement
81
- none: false
82
45
  requirements:
83
- - - ! '>='
46
+ - - "~>"
84
47
  - !ruby/object:Gem::Version
85
- version: '0'
48
+ version: '1.0'
86
49
  type: :development
87
50
  prerelease: false
88
51
  version_requirements: !ruby/object:Gem::Requirement
89
- none: false
90
52
  requirements:
91
- - - ! '>='
53
+ - - "~>"
92
54
  - !ruby/object:Gem::Version
93
- version: '0'
94
- description: A simple Soundcloud API wrapper based of httparty, multipart-post, httmultiparty
55
+ version: '1.0'
56
+ description: The official SoundCloud API wrapper. It provides simple methods to handle
57
+ authorization and to execute HTTP calls.
95
58
  email:
96
- - johannes@soundcloud.com
59
+ - api@soundcloud.com
97
60
  executables: []
98
61
  extensions: []
99
62
  extra_rdoc_files: []
100
63
  files:
64
+ - LICENSE.md
65
+ - README.md
66
+ - lib/soundcloud.rb
101
67
  - lib/soundcloud/array_response_wrapper.rb
68
+ - lib/soundcloud/client.rb
102
69
  - lib/soundcloud/hash_response_wrapper.rb
70
+ - lib/soundcloud/response_error.rb
103
71
  - lib/soundcloud/version.rb
104
- - lib/soundcloud.rb
105
- - README.md
106
- homepage: http://dev.soundcloud.com
72
+ homepage: https://dev.soundcloud.com
107
73
  licenses: []
108
- post_install_message:
74
+ metadata: {}
75
+ post_install_message:
109
76
  rdoc_options: []
110
77
  require_paths:
111
78
  - lib
112
79
  required_ruby_version: !ruby/object:Gem::Requirement
113
- none: false
114
80
  requirements:
115
- - - ! '>='
81
+ - - ">="
116
82
  - !ruby/object:Gem::Version
117
83
  version: '0'
118
84
  required_rubygems_version: !ruby/object:Gem::Requirement
119
- none: false
120
85
  requirements:
121
- - - ! '>='
86
+ - - ">="
122
87
  - !ruby/object:Gem::Version
123
- version: 1.3.6
88
+ version: 1.3.5
124
89
  requirements: []
125
- rubyforge_project: soundcloud
126
- rubygems_version: 1.8.24
127
- signing_key:
128
- specification_version: 3
129
- summary: A simple Soundcloud API wrapper
90
+ rubygems_version: 3.1.2
91
+ signing_key:
92
+ specification_version: 4
93
+ summary: The official SoundCloud API wrapper.
130
94
  test_files: []