soundcloud 0.3.1 → 0.3.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.
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: []