officialfm 0.1.2 → 0.2.0

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.
data/.gitignore CHANGED
@@ -17,10 +17,5 @@ tmtags
17
17
  coverage
18
18
  rdoc
19
19
  pkg
20
- *.gem
21
- .yardopts
22
- .yardoc
23
- .bundle
24
- Gemfile.lock
25
20
 
26
21
  ## PROJECT::SPECIFIC
data/Gemfile CHANGED
@@ -1,3 +1,9 @@
1
1
  source :rubygems
2
2
 
3
3
  gemspec
4
+
5
+ group :test do
6
+ gem 'simplecov', '~> 0.6.0', :require => false
7
+ gem 'simplecov-gem-adapter', '~> 1.0.1', :require => false
8
+ end
9
+
data/Gemfile.lock ADDED
@@ -0,0 +1,41 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ officialfm (0.2.0)
5
+ faraday (~> 0.8.1)
6
+ faraday_middleware (~> 0.8.8)
7
+ hashie (~> 1.2)
8
+
9
+ GEM
10
+ remote: http://rubygems.org/
11
+ specs:
12
+ faraday (0.8.1)
13
+ multipart-post (~> 1.1)
14
+ faraday_middleware (0.8.8)
15
+ faraday (>= 0.7.4, < 0.9)
16
+ hashie (1.2.0)
17
+ jnunemaker-matchy (0.4.0)
18
+ multi_json (1.3.6)
19
+ multipart-post (1.1.5)
20
+ rake (0.9.2.2)
21
+ shoulda (2.11.3)
22
+ simplecov (0.6.0)
23
+ multi_json (~> 1.0)
24
+ simplecov-html (~> 0.5.3)
25
+ simplecov-gem-adapter (1.0.1)
26
+ simplecov
27
+ simplecov-html (0.5.3)
28
+ test-unit (2.5.1)
29
+
30
+ PLATFORMS
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ bundler (~> 1.0)
35
+ jnunemaker-matchy (~> 0.4)
36
+ officialfm!
37
+ rake (~> 0.8)
38
+ shoulda (~> 2.11)
39
+ simplecov (~> 0.6.0)
40
+ simplecov-gem-adapter (~> 1.0.1)
41
+ test-unit (~> 2.1)
data/README.md CHANGED
@@ -1,69 +1,201 @@
1
1
  # official.fm
2
2
 
3
- Ruby wrapper for the [official.fm Simple API](http://official.fm/developers).
3
+ Ruby wrapper for the [official.fm v2 API](http://official.fm/developers).
4
4
 
5
5
  ## Installation
6
6
 
7
- sudo gem install officialfm
7
+ gem install officialfm
8
+
9
+ Alternatively put this in you `Gemfile`, then run `bundle install`:
10
+
11
+ ```ruby
12
+ gem 'officialfm'
13
+ ```
8
14
 
9
15
  ## Get your API key
10
16
 
11
- Be sure and get your API key: [http://official.fm/developers/manage](http://official.fm/developers/manage)
17
+ Coming soon.
12
18
 
13
- ## Usage
19
+ You can also access the API without an API key, but with a lower rate limit.
14
20
 
15
- ### Include the relevant files
21
+ ## Client instantiation and configuration
16
22
 
17
- require 'rubygems'
18
- require 'officialfm'
23
+ Include the relevant files
19
24
 
20
- ### Instantiate a client
21
-
22
- officialfm = OfficialFM::Client.new(:api_key => 'your_api_key')
25
+ ```ruby
26
+ require 'officialfm'
27
+ ```
23
28
 
24
- #### Examples
29
+ Instantiate a client
25
30
 
26
- user = officialfm.user('chab')
27
- puts user.name
31
+ ```ruby
32
+ officialfm = OfficialFM::Client.new
33
+ ```
28
34
 
29
- officialfm.tracks('Dare', {:limit => 10, :embed => false}).each |track| do
30
- puts "#{track.name} by #{track.artist_string}"
31
- end
32
-
33
- For a complete example of web-app using Sinatra in conjunction with
34
- this gem, see [ofmtweet](https://github.com/nddrylliog/ofmtweet).
35
+ With an API key:
35
36
 
36
- ## Additions to the original API
37
+ ```ruby
38
+ officialfm = OfficialFM::Client.new(api_key: 'your_api_key')
39
+ ```
37
40
 
38
- ### playlist.running\_time
41
+ You can also set a default configuration for all clients to use
39
42
 
40
- The original API has a `length` attribute in playlists, but unfortunately
41
- we can't use it from the Ruby side with Hashie, because it's already the number
42
- of fields of the hash (as I understand it).
43
+ ```ruby
44
+ OfficialFM.configure do |c|
45
+ c.api_key = YOUR_API_KEY
46
+ end
47
+ ```
43
48
 
44
- It can still be accessed with `playlist["length"]` but since it's not pretty,
45
- the gem allows you to access it like this:
49
+ ## Response format
46
50
 
47
- puts "#{playlist.running_time}s of pure bliss"
51
+ All methods return a
52
+ [Hashie:Mash](http://rdoc.info/github/intridea/hashie/Hashie/Mash). That means
53
+ you can access the response fields via method-like accessors. For example:
48
54
 
49
- ## Note on Patches/Pull Requests
55
+ ```ruby
56
+ search_results = officialfm.tracks('Wiz Khalifa')
57
+ track = search_results[3].track
58
+ puts "#{track.title} by #{track.artist}"
59
+ ```
50
60
 
51
- * Fork the project.
52
- * Make your feature addition or bug fix.
53
- * Add tests for it. This is important so I don't break it in a
54
- future version unintentionally.
55
- * Commit, do not mess with rakefile, version, or history.
56
- (if you want to have your own version, that is fine but
57
- bump version in a commit by itself I can ignore when I pull)
58
- * Send me a pull request. Bonus points for topic branches.
61
+ ## API response enhancements
59
62
 
60
- ## Copyright
63
+ The API wraps responses in a root element, e.g.:
64
+
65
+ ```json
66
+ {
67
+ "track": {
68
+ "title": "Some track"
69
+ ...
70
+ }
71
+ }
72
+ ```
73
+
74
+ The responses given by methods in the gem don't have a root and expose the
75
+ resource's properites directly instead (e.g. `officialfm.track('xxxx').title`).
76
+
77
+ Search results are also unwrapped. For example, the raw response of a track
78
+ search looks like:
79
+
80
+ ```json
81
+ {
82
+ "page": 1,
83
+ "total_entries": 2,
84
+ "total_pages": 1,
85
+ "tracks" : [
86
+ {
87
+ "track": {
88
+ // track properties
89
+ }
90
+ },
91
+ {
92
+ "track": {
93
+ // track properites
94
+ }
95
+ }
96
+ ]
97
+ }
98
+ ```
99
+
100
+ The gem removes the roots of the search result items, so you can access an item
101
+ directly through array access.
102
+
103
+ ```ruby
104
+ track = officialfm.tracks('foo').tracks[0]
105
+ puts track.duration
106
+ ```
107
+
108
+
109
+ ## Methods
110
+
111
+ ### Tracks
112
+ Search for a track:
113
+
114
+ ```ruby
115
+ officialfm.tracks('Nightcall')
116
+ ```
117
+
118
+ Search results being paged, you can request a specific page with the page parameter.
119
+
120
+ ```ruby
121
+ officialfm.tracks('Kids', page: 2)
122
+ ```
123
+
124
+ Get info about a specific track:
125
+
126
+ ```ruby
127
+ officialfm.track('1nnQ')
128
+ ```
129
+
130
+ ### Playlists
131
+
132
+ Search for a playlist (again you can pass an optional `page` parameter to get a
133
+ specific results page):
134
+
135
+ ```ruby
136
+ officialfm.playlists('AWOLNATION')
137
+ ```
61
138
 
62
- Copyright (c) 2011 Amos Wenger.
139
+ Get info about a specific playlist:
63
140
 
64
- This project is distributed under the New BSD License. See LICENSE for details.
141
+ ```ruby
142
+ officialfm.playlist('CbqY')
143
+ ```
144
+
145
+ Retrieve the tracks in a playlist:
146
+
147
+ ```ruby
148
+ playlists = officialfm.playlists('AWOLNATION')
149
+
150
+ # The tracks for the 3 playlist in the search results
151
+ tracks = playlists[2].tracks
152
+ ```
153
+
154
+ Note that the `tracks` method of a playlist object makes an extra request. If
155
+ you know the playlist ID in advance, you can retrieve the tracks in just one
156
+ request.
157
+
158
+ ```ruby
159
+ officialfm.playlist_tracks('CbqY')
160
+ ```
161
+
162
+ ### Projects
163
+
164
+ Search for a project (a project can be an artist or a collaboration between several artists)
165
+
166
+ ```ruby
167
+ officialfm.projects('Mac Miller x Pharrell')
168
+ ```
169
+
170
+ Similarly to playlists, you can get information about a specific project with
171
+
172
+ ```ruby
173
+ officialfm.project('edB6')
174
+ ```
175
+
176
+ Get a project's tracks or playlists when you know the project ID in advance.
177
+
178
+ ```ruby
179
+ tracks = officialfm.project_tracks('edB6')
180
+ playlists = officialfm.project_playlists('edB6')
181
+ puts artist.tracks
182
+ puts artist.playlists
183
+ ```
184
+
185
+ Of course you can get the playlists and tracks in a project even if you don't know its ID:
186
+
187
+ ```ruby
188
+ projects = officialfm.projects('Mac Miller')
189
+
190
+ puts projects[0].name # => 'Mac Miller'
191
+ puts projects[2].name # => 'Mac Miller x Pharrell'
192
+
193
+ tracks = projects[2].tracks
194
+
195
+ ```
196
+
197
+
198
+ ## Copyright
65
199
 
66
- Based on [@pengwynn's Gowalla API wrapper](https://github.com/pengwynn/gowalla)
200
+ Copyright (c) 2012 official.fm
67
201
 
68
- A huge load of thanks to pengwynn for releasing it open-source! It was wonderful
69
- to work from his extra-clean codebase.
data/Rakefile CHANGED
@@ -1,5 +1,4 @@
1
1
  require 'rake'
2
- require "shoulda/tasks"
3
2
  require "rake/testtask"
4
3
  require 'bundler'
5
4
 
@@ -1,83 +1,50 @@
1
- require 'forwardable'
2
- require 'faraday/request/officialfm_oauth'
3
- require 'officialfm/authentication'
4
1
  require 'officialfm/version'
5
2
 
6
3
  module OfficialFM
7
4
  class Client
8
- extend Forwardable
9
5
 
10
- include Users
11
6
  include Tracks
12
7
  include Playlists
13
- include Authentication
14
-
15
- attr_reader :api_key, :api_secret
8
+ include Projects
16
9
 
17
- def_delegators :web_server, :authorize_url, :access_token_url
10
+ attr_reader :api_key
18
11
 
19
12
  def initialize(options={})
20
13
  @api_key = options[:api_key] || OfficialFM.api_key
21
- @api_secret = options[:api_secret] || OfficialFM.api_secret
22
- @access_token = options[:access_token]
23
- @access_secret = options[:access_secret]
24
- # Note: Although the default of the API is to return XML, I think
25
- # json is more appropriate in the Ruby world
26
-
27
- @format = options[:format] || :json
28
- connection unless @access_token
14
+ connection(options)
29
15
  end
30
16
 
31
- # GET request arguments for simple API calls
32
- def simple_params (options = nil)
33
- params = {
34
- :key => @api_key,
35
- :format => @format,
36
- }
37
- if options
38
- options.each { |key, value|
39
- case key
40
- when 'limit'
41
- params['api_max_responses'] = value
42
- when 'embed'
43
- params['api_embed_codes'] = value
44
- else
45
- params[key] = value
46
- end
47
- }
48
- end
49
- params
50
- end
51
17
 
52
- # Raw HTTP connection, either Faraday::Connection
18
+ # Faraday::Connection used for all HTTP requests
53
19
  #
54
20
  # @return [Faraday::Connection]
55
- def connection
56
- params =
57
-
58
- options = {
59
- :url => api_url,
21
+ def connection(options={})
22
+ config = {
23
+ :url => api_url,
60
24
  :headers => default_headers
61
- }
62
-
63
- @connection ||= Faraday.new(options) do |builder|
64
- builder.use Faraday::Request::OfficialFMOAuth, authentication if authenticated?
65
- builder.use Faraday::Request::Multipart
66
- builder.use Faraday::Request::UrlEncoded
67
-
68
- builder.use Faraday::Response::Mashify
69
- builder.use Faraday::Response::ParseJson
25
+ }.merge(options)
26
+
27
+ @connection ||= Faraday.new(config) do |builder|
70
28
  builder.adapter Faraday.default_adapter
29
+
30
+ builder.request :url_encoded
31
+
32
+ builder.response :mashify
33
+ builder.response :json
71
34
  end
72
35
 
73
36
  end
74
-
37
+
75
38
  # @private
76
39
  def default_headers
77
40
  headers = {
78
- :accept => 'application/json',
79
- :user_agent => "officialfm ruby gem version #{OfficialFM::VERSION}"
41
+ 'X-API-Version' => '2.0',
42
+ :accept => 'application/json',
43
+ :user_agent => "officialfm v2 ruby gem version #{OfficialFM::VERSION}"
80
44
  }
45
+ headers['X-API-KEY'] = @api_key if @api_key
46
+
47
+ headers
81
48
  end
82
49
 
83
50
  # Provides the URL for accessing the API
@@ -86,5 +53,42 @@ module OfficialFM
86
53
  def api_url
87
54
  "http://api.official.fm"
88
55
  end
56
+
57
+ private
58
+
59
+ # Returns the url for a resource.
60
+ #
61
+ # - id: resource id or URL
62
+ # - options: extra params. Supports 'parent' and 'child' options.
63
+ #
64
+ # resource_url('http://api.official.fm/playlists/2BHH', :child => :tracks)
65
+ # # => http://api.official.fm/playlists/2BHH/tracks
66
+ #
67
+ # resource_url('2BHH', { :parent => 'playlists', :child => 'tracks'})
68
+ # # => /playlists/2BHH/tracks
69
+ #
70
+ # Note: if you supply a full resource URL, the parent option is ignored.
71
+ #
72
+ def resource_url(id, options={})
73
+ raise "Resource id cannot be nil" unless id
74
+
75
+ parent = options.fetch(:parent, nil)
76
+ child = options.fetch(:child, nil)
77
+
78
+ if id.start_with? 'http'
79
+ return [id, child].join '/'
80
+ end
81
+
82
+ url = [parent, id, child].join('/').chomp('/')
83
+ url = "/#{url}" unless url.start_with? '/'
84
+
85
+ url
86
+ end
87
+
88
+ def extend_response(response, mixin_module)
89
+ response.obj = self
90
+ response.extend(mixin_module)
91
+ end
92
+
89
93
  end
90
94
  end
@@ -1,145 +1,49 @@
1
- require 'cgi'
2
-
3
1
  module OfficialFM
4
2
  module Playlists
5
-
6
- # Search for playlists
7
- #
8
- # @param [String] search_param: a search parameter (eg. name of the playlist)
9
- # @param [Integer] limit (50) limit per page (optional)
10
- # @return [Hashie::Mash] Playlist list
3
+
4
+ # Search for a playlist
11
5
  def playlists(search_param, options={})
12
- response = connection.get do |req|
13
- req.url "/search/playlists/#{CGI::escape(search_param)}", simple_params(options)
14
- end
15
-
16
- response.body.map do |pl| improve(pl) end
17
- end
18
-
19
- # Retrieve information about a specific playlist
20
- #
21
- # @param [String] track_id: id
22
- # @param [Bool] embed (false) should embed codes be included in the response
23
- # @return [Hashie::Mash] Playlist
24
- def playlist(playlist_id, options={})
25
- response = connection.get do |req|
26
- req.url "/playlist/#{playlist_id}", simple_params(options)
27
- end
28
- improve(response.body[0])
29
- end
30
-
31
- # Retrieve users that have voted for this playlist
32
- #
33
- # @param [String] playlist_id: id
34
- # @param [Integer] limit (50) limit per page
35
- # @return [Hashie::Mash] User list
36
- def playlist_votes(playlist_id, options={})
37
- response = connection.get do |req|
38
- req.url "/playlist/#{playlist_id}/votes", simple_params(options)
6
+ response = connection.get '/playlists/search', options.merge(:q => search_param)
7
+
8
+ response.body.playlists.map! do |p|
9
+ # remove the unnecessary root
10
+ actual_playlist = p.playlist
11
+
12
+ extend_response(actual_playlist, PlaylistMethods)
13
+
14
+ actual_playlist
39
15
  end
16
+
40
17
  response.body
41
18
  end
42
-
43
- def improve(playlist)
44
- # the length field is already used. Note: running_time is in seconds
45
- playlist.running_time = playlist["length"]
46
- playlist
47
- end
48
-
49
- ####################################################################
50
- ######################### Advanced API methods #####################
51
- ####################################################################
52
19
 
53
- # Update information of a given playlist
54
- #
55
- # @param [String] playlist_id: id
56
- # @param [String] description (optional)
57
- # @param [String] name (optional)
58
- # @param [String] password (optional)
59
- # @param [String] private (optional)
60
- # @return [Hashie::Mash] Playlist
61
- def update_playlist! (playlist_id, data = {})
62
- check_auth :update_playlist
63
-
64
- response = connection.put do |req|
65
- req.url "/playlist/update/#{playlist_id}", data
66
- req.body = { :format => @format }
67
- end
68
- response
69
- end
70
-
71
- # Upload a picture for a given playlist
72
- #
73
- # @param [String] playlist_id: id
74
- # @param [String] path: path to a picture
75
- # @param [String] mime: the mime-type of the picture (e.g. image/jpeg, image/png, etc.)
76
- # @return [Hashie::Mash] Success or error message
77
- def playlist_picture! (playlist_id, path, mime)
78
- check_auth :playlist_picture
79
-
80
- response = connection.post do |req|
81
- req.url "/playlist/picture/#{playlist_id}"
82
- req.body = { :file => Faraday::UploadIO.new(path, mime), :format => @format }
83
- end
84
- response
85
- end
86
-
87
- # Remove a playlist
88
- #
89
- # @param [String] playlist_id: id
90
- # @return [Hashie::Mash] Success or error message
91
- def playlist_remove! (playlist_id)
92
- check_auth :playlist_remove
93
-
94
- response = connection.delete do |req|
95
- req.url "/playlist/remove/#{playlist_id}"
96
- req.body = { :format => @format }
97
- end
98
- response
99
- end
100
-
101
- # Create a playlist
102
- #
103
- # @param [String] name: playlist name
104
- # @param [String] track_id: first track id of the new playlist
105
- # @return [Hashie::Mash] Playlist object
106
- def playlist_create! (name, track_id)
107
- check_auth :playlist_create
108
-
109
- response = connection.post do |req|
110
- req.url "/playlist/create"
111
- req.body = { :format => @format, :name => name, :track_id => track_id }
112
- end
113
- response
20
+ # Retrieve information about a specific playlist
21
+ def playlist(id, options={})
22
+ url = resource_url(id, parent: 'playlists')
23
+
24
+ response = connection.get(url, options).body.playlist
25
+ extend_response(response, PlaylistMethods)
114
26
  end
115
-
116
- # Vote for a playlist
117
- #
118
- # @param [String] playlist_id: id
119
- # @return [Hashie::Mash] Success or error message
120
- def playlist_vote! (playlist_id)
121
- check_auth :playlist_vote
122
-
123
- response = connection.post do |req|
124
- req.url "/playlist/vote/#{playlist_id}"
125
- req.body = { :format => @format }
27
+
28
+ def playlist_tracks(id, options={})
29
+ url = resource_url(id, { parent: 'playlists', child: 'tracks' })
30
+
31
+ response = connection.get url, options
32
+
33
+ tracks = response.body.tracks.map do |t|
34
+ # remove the unnecessary root
35
+ t.track
126
36
  end
127
- response
37
+
38
+ tracks
128
39
  end
129
-
130
- # Remote vote for a playlist
131
- #
132
- # @param [String] playlist_id: id
133
- # @return [Hashie::Mash] Success or error message
134
- def playlist_unvote! (playlist_id)
135
- check_auth :playlist_unvote
136
-
137
- response = connection.post do |req|
138
- req.url "/playlist/unvote/#{playlist_id}"
139
- req.body = { :format => @format }
40
+
41
+ module PlaylistMethods
42
+
43
+ def tracks
44
+ obj.playlist_tracks(self.src)
140
45
  end
141
- response
142
46
  end
143
-
47
+
144
48
  end
145
49
  end