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 +7 -0
- data/LICENSE.md +20 -0
- data/README.md +167 -133
- data/lib/soundcloud/array_response_wrapper.rb +11 -7
- data/lib/soundcloud/client.rb +211 -0
- data/lib/soundcloud/hash_response_wrapper.rb +7 -5
- data/lib/soundcloud/response_error.rb +44 -0
- data/lib/soundcloud/version.rb +2 -2
- data/lib/soundcloud.rb +18 -204
- metadata +33 -69
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
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
+
[](https://travis-ci.org/soundcloud/soundcloud-ruby)
|
|
5
11
|
|
|
6
|
-
##
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
17
|
+
```sh
|
|
18
|
+
gem install soundcloud
|
|
19
|
+
```
|
|
15
20
|
|
|
16
21
|
## Examples
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
####
|
|
122
|
-
Stores the passed options and call exchange_token in case options are passed
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
145
|
-
A Proc passed to on_exchange_token will be called each time a token was
|
|
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
|
-
|
|
148
|
-
Returns a date based on the expires_in attribute returned from a token
|
|
175
|
+
#### SoundCloud#expires_at
|
|
176
|
+
Returns a date based on the `expires_in` attribute returned from a token
|
|
177
|
+
exchange.
|
|
149
178
|
|
|
150
|
-
|
|
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
|
|
155
|
-
The original HTTParty response is available through
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
data/lib/soundcloud/version.rb
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
VERSION = '0.3.
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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
|
|
163
|
-
|
|
13
|
+
def new(options={})
|
|
14
|
+
Client.new(options)
|
|
164
15
|
end
|
|
16
|
+
module_function :new
|
|
165
17
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
|
187
|
-
|
|
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
|
-
|
|
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.
|
|
5
|
-
prerelease:
|
|
4
|
+
version: 0.3.5
|
|
6
5
|
platform: ruby
|
|
7
6
|
authors:
|
|
8
7
|
- Johannes Wagener
|
|
9
|
-
|
|
8
|
+
- Erik Michaels-Ober
|
|
9
|
+
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
-
|
|
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
|
-
|
|
105
|
-
- README.md
|
|
106
|
-
homepage: http://dev.soundcloud.com
|
|
72
|
+
homepage: https://dev.soundcloud.com
|
|
107
73
|
licenses: []
|
|
108
|
-
|
|
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.
|
|
88
|
+
version: 1.3.5
|
|
124
89
|
requirements: []
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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: []
|