soundcloud 0.1.3b

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.
@@ -0,0 +1,110 @@
1
+ # Soundcloud API Wrapper
2
+ ## Description
3
+ This is a thin wrapper around the Soundcloud API based of httparty.
4
+ It is providing simple methods to handle authorization and to execute HTTP calls.
5
+
6
+ ## Requirements
7
+ * httmultiparty
8
+ * httparty
9
+ * crack
10
+ * multipart-upload
11
+ * hashie
12
+
13
+ ## Installation
14
+ gem install soundcloud
15
+
16
+ ## 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
+
27
+ #### Do the 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
+ client = Soundcloud.new({
30
+ :client_id => 'YOUR_CLIENT_ID',
31
+ :client_secret => 'YOUR_CLIENT_SECRET',
32
+ :username => 'some@email.com',
33
+ :password => 'userpass'
34
+ })
35
+
36
+ # print logged in username
37
+ puts client.get('/me').username
38
+
39
+ #### Do the OAuth2 authorization code flow
40
+ sc = Soundcloud.new({
41
+ :client_id => 'YOUR_CLIENT_ID',
42
+ :client_secret => 'YOUR_CLIENT_SECRET',
43
+ })
44
+
45
+ sc.authorize_url(:redirect_uri => uri)
46
+ # => "https://soundcloud.com/connect?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=http://host/redirect"
47
+ sc.exchange_code(:redirect_uri => uri, :code => 'CODE')
48
+
49
+ #### Do the OAuth2 refresh token flow, upload a track and print its link
50
+ # register a new client which will exchange an existing refresh_token for an access_token
51
+ client = Soundcloud.new({
52
+ :client_id => 'YOUR_CLIENT_ID',
53
+ :client_secret => 'YOUR_CLIENT_SECRET',
54
+ :refresh_token => 'SOME_REFRESH_TOKEN'
55
+ })
56
+
57
+ # upload a new track with track.mp3 as audio and image.jpg as artwork
58
+ track = client.post('/tracks', {
59
+ :title => 'a new track',
60
+ :asset_data => File.new('track.mp3'),
61
+ :artwork_data => File.new('image.jpg')
62
+ })
63
+
64
+ # print new tracks link
65
+ puts track.permalink_url
66
+
67
+ #### Resolve a track url and print its id
68
+ # register the client
69
+ client = Soundcloud.new(:client_id => 'YOUR_CLIENT_ID')
70
+
71
+ # call the resolve endpoint with a track url
72
+ track = client.get('/resolve', :url => "http://soundcloud.com/forss/flickermood")
73
+
74
+ # print the track id
75
+ puts track.id
76
+
77
+ #### Register a client for http://sandbox-soundcloud.com with an existing access_token and start following a user
78
+ # register a client for http://sandbox-soundcloud.com with existing access_token
79
+ client = Soundcloud.new(:site => 'http://sandbox-soundcloud.com', :access_token => 'SOME_ACCESS_TOKEN')
80
+
81
+ # create a new following
82
+ user_id_to_follow = 123
83
+ client.put("/me/followings/#{user_id_to_follow}")
84
+
85
+ ## Details
86
+ #### Soundcloud.new(options={})
87
+ Will store the passed options and call exchange_token in case options are passed that allow an exchange of tokens.
88
+
89
+ #### Soundcloud#exchange_token(options={})
90
+ Will store the passed options and try to exchange tokens if no access_token is present and:
91
+ - refresh_token, client_id and client_secret is present.
92
+ - client_id, client_secret, username, password is present
93
+ - client_id, client_secret, redirect_uri, code is present
94
+
95
+ #### Soundcloud#authorize_url(options={})
96
+ Will store the passed options and return an authorize url.
97
+ The client_id and redirect_uri options need to present to generate the authorize url.
98
+
99
+ #### Soundcloud#get, Soundcloud#post, Soundcloud#put, Soundcloud#delete, Soundcloud#head
100
+ All available HTTP methods are exposed through these methods. They all share the signature (path_or_uri, query={}, options={}).
101
+ 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.
102
+ In case an access_token is expired and a refresh_token is present it will try to refresh the access_token and retry the call.
103
+ 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.
104
+
105
+ #### Soundcloud#client_id, client_secret, access_token, refresh_token, use_ssl?
106
+ These are accessor to the stored options.
107
+
108
+ #### Error Handling
109
+ In case a request was not successful a Soundcloud::ResponseError will be raise.
110
+ The original HTTParty response is available through Soundcloud::ResponseError#response.
@@ -0,0 +1,123 @@
1
+ gem 'httmultiparty'
2
+ gem 'mash'
3
+ require 'httmultiparty'
4
+ require 'hashie'
5
+ require 'uri'
6
+
7
+ class Soundcloud
8
+ class ResponseError < HTTParty::ResponseError; end
9
+ include HTTMultiParty
10
+ headers 'Accept' => 'application/json'
11
+
12
+ # TODO fix when api is ready for client_id
13
+ CLIENT_ID_PARAM_NAME = :consumer_key
14
+ API_SUBHOST = 'api'
15
+ AUTHORIZE_PATH = '/connect'
16
+ TOKEN_PATH = '/oauth2/token'
17
+ DEFAULT_OPTIONS = {
18
+ :site => 'soundcloud.com'
19
+ }
20
+
21
+
22
+ def initialize(options={})
23
+ store_options(options)
24
+ if access_token.nil? && (options_for_refresh_flow_present? ||
25
+ options_for_credentials_flow_present? || options_for_code_flow_present?)
26
+ exchange_token
27
+ end
28
+
29
+ raise ArgumentError, "At least a client_id or an access_token must be present" if client_id.nil? && access_token.nil?
30
+ end
31
+
32
+ def get (path, query={}, options={}); handle_response { self.class.get *construct_query_arguments(path, options.merge(:query => query)) } end
33
+ def post (path, query={}, options={}); handle_response { self.class.post *construct_query_arguments(path, options.merge(:query => query)) } end
34
+ def put (path, query={}, options={}); handle_response { self.class.put *construct_query_arguments(path, options.merge(:query => query)) } end
35
+ def delete(path, query={}, options={}); handle_response { self.class.delete *construct_query_arguments(path, options.merge(:query => query)) } end
36
+ def head (path, query={}, options={}); handle_response { self.class.head *construct_query_arguments(path, options.merge(:query => query)) } end
37
+
38
+ # accessors for options
39
+ def client_id; @options[:client_id]; end
40
+ def client_secret; @options[:client_secret]; end
41
+ def access_token; @options[:access_token]; end
42
+ def refresh_token; @options[:refresh_token]; end
43
+ def use_ssl?;
44
+ !! @options[:use_ssl?] || access_token
45
+ end
46
+
47
+ def site; @options[:site]; end
48
+
49
+ def host; site; end
50
+ def api_host; [API_SUBHOST, host].join('.'); end
51
+
52
+ def authorize_url(options={})
53
+ store_options(options)
54
+ "https://#{host}#{AUTHORIZE_PATH}?response_type=code&client_id=#{client_id}&redirect_uri=#{URI.escape redirect_uri}"
55
+ end
56
+
57
+ def exchange_token(options={})
58
+ store_options(options)
59
+ raise ArgumentError, 'client_id and client_secret is required to retrieve an access_token' if client_id.nil? || client_secret.nil?
60
+ client_params = {:client_id => client_id, :client_secret => client_secret}
61
+ params = if options_for_refresh_flow_present?
62
+ {:grant_type => 'refresh_token', :refresh_token => refresh_token}
63
+ elsif options_for_credentials_flow_present?
64
+ {:grant_type => 'password', :username => @options[:username], :password => @options[:password]}
65
+ elsif options_for_code_flow_present?
66
+ {:grant_type => 'authorization_code', :redirect_uri => @options[:redirect_uri], :code => @options[:code]}
67
+ end
68
+ params.merge!(client_params)
69
+ response = handle_response {
70
+ self.class.post("https://#{api_host}#{TOKEN_PATH}", :query => params)
71
+ }
72
+ @options.merge!(:access_token => response.access_token, :refresh_token => response.refresh_token)
73
+ response
74
+ end
75
+
76
+ private
77
+ def handle_response(refreshing_enabled=true, &block)
78
+ response = block.call
79
+ if response && !response.success?
80
+ if response.code == 401 && response["error"] == "invalid_grant" && refreshing_enabled
81
+ exchange_token
82
+ # TODO it should return the original
83
+ handle_response(false, &block)
84
+ else
85
+ raise ResponseError.new(response), "HTTP Status #{response.code}"
86
+ end
87
+ elsif response.is_a? Hash
88
+ HashResponseWrapper.new(response)
89
+ elsif response.is_a? Array
90
+ ArrayResponseWrapper.new(response)
91
+ end
92
+ end
93
+
94
+ def options_for_refresh_flow_present?; !! @options[:refresh_token]; end
95
+ def options_for_credentials_flow_present?; !! @options[:username] && @options[:password]; end
96
+ def options_for_code_flow_present?; !! @options[:code] && @options[:redirect_uri]; end
97
+
98
+ def store_options(options={})
99
+ @options ||= DEFAULT_OPTIONS.dup
100
+ @options.merge! options
101
+ end
102
+
103
+ def construct_query_arguments(path_or_uri, options={})
104
+ path = URI.parse(path_or_uri).path
105
+ scheme = use_ssl? ? 'https' : 'http'
106
+ options = options.dup
107
+ options[:query] ||= {}
108
+
109
+ if access_token
110
+ options[:query][:oauth_token] = access_token
111
+ else
112
+ options[:query][CLIENT_ID_PARAM_NAME] = client_id
113
+ end
114
+ [
115
+ "#{scheme}://#{api_host}#{path}",
116
+ options
117
+ ]
118
+ end
119
+ end
120
+
121
+ require 'soundcloud/array_response_wrapper'
122
+ require 'soundcloud/hash_response_wrapper'
123
+
@@ -0,0 +1,8 @@
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
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ class Soundcloud::HashResponseWrapper < Hashie::Mash
2
+ attr_reader :response
3
+ def initialize(response)
4
+ super(response)
5
+ @response = response
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ class Soundcloud
2
+ VERSION = '0.1.3b'
3
+ end
metadata ADDED
@@ -0,0 +1,146 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: soundcloud
3
+ version: !ruby/object:Gem::Version
4
+ hash: 1452983230
5
+ prerelease: true
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 3b
10
+ version: 0.1.3b
11
+ platform: ruby
12
+ authors:
13
+ - Johannes Wagener
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-01-26 00:00:00 -08:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: httparty
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 5
30
+ segments:
31
+ - 0
32
+ - 7
33
+ - 3
34
+ version: 0.7.3
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: httmultiparty
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 15
46
+ segments:
47
+ - 0
48
+ - 2
49
+ version: "0.2"
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: hashie
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ hash: 3
61
+ segments:
62
+ - 0
63
+ version: "0"
64
+ type: :runtime
65
+ version_requirements: *id003
66
+ - !ruby/object:Gem::Dependency
67
+ name: rspec
68
+ prerelease: false
69
+ requirement: &id004 !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ hash: 3
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ type: :development
79
+ version_requirements: *id004
80
+ - !ruby/object:Gem::Dependency
81
+ name: fakeweb
82
+ prerelease: false
83
+ requirement: &id005 !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ hash: 3
89
+ segments:
90
+ - 0
91
+ version: "0"
92
+ type: :development
93
+ version_requirements: *id005
94
+ description: A simple Soundcloud API wrapper based of httparty, multipart-post, httmultiparty
95
+ email:
96
+ - johannes@soundcloud.com
97
+ executables: []
98
+
99
+ extensions: []
100
+
101
+ extra_rdoc_files: []
102
+
103
+ files:
104
+ - lib/soundcloud/array_response_wrapper.rb
105
+ - lib/soundcloud/hash_response_wrapper.rb
106
+ - lib/soundcloud/version.rb
107
+ - lib/soundcloud.rb
108
+ - README.md
109
+ has_rdoc: true
110
+ homepage: http://dev.soundcloud.com
111
+ licenses: []
112
+
113
+ post_install_message:
114
+ rdoc_options: []
115
+
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ hash: 3
124
+ segments:
125
+ - 0
126
+ version: "0"
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ none: false
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ hash: 23
133
+ segments:
134
+ - 1
135
+ - 3
136
+ - 6
137
+ version: 1.3.6
138
+ requirements: []
139
+
140
+ rubyforge_project: soundcloud
141
+ rubygems_version: 1.3.7
142
+ signing_key:
143
+ specification_version: 3
144
+ summary: A simple Soundcloud API wrapper
145
+ test_files: []
146
+