spotify-api 0.0.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.
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env jruby
2
+ require 'rubygems'
3
+ require 'json'
4
+ require 'sinatra/base'
5
+ require File.expand_path(File.dirname(__FILE__) + '/../jotify') unless defined?(Jotify)
6
+
7
+ class Sinatra::Application
8
+ Lock = Mutex.new
9
+
10
+ def jotify
11
+ Lock.synchronize do
12
+ @@jotify ||= Jotify.new
13
+ end
14
+ end
15
+
16
+ def query(what, p=what)
17
+ params[p] ? "#{what}:" + params[p] : ''
18
+ end
19
+ end
20
+
21
+ Sinatra::Application.error ArgumentError do
22
+ content_type :json
23
+ {
24
+ 'status' => 'ERROR',
25
+ 'message' => request.env['sinatra.error'].message
26
+ }.to_json
27
+ end
28
+
29
+ Sinatra::Application.get('/tracks') do
30
+ content_type :json
31
+ raise ArgumentError, "need name" unless params[:name]
32
+
33
+ res = jotify.search([query(:track, :name), query(:artist), query(:album)].join(' '))
34
+ {
35
+ 'status'=>'OK',
36
+ 'result'=> res.tracks.map { |t| t.to_h }
37
+ }.to_json
38
+ end
39
+
40
+ Sinatra::Application.get('/albums') do
41
+ content_type :json
42
+ raise ArgumentError, "need name" unless params[:name]
43
+
44
+ res = jotify.search([query(:album, :name), query(:artist)].join(' ') )
45
+ {
46
+ 'status'=>'OK',
47
+ 'result'=> res.albums.map { |a| a.to_h }
48
+ }.to_json
49
+ end
50
+
51
+ Sinatra::Application.get('/artists') do
52
+ content_type :json
53
+ raise ArgumentError, "need name" unless params[:name]
54
+
55
+ res = jotify.search(query(:artist, :name))
56
+ {
57
+ 'status'=>'OK',
58
+ 'result'=> res.artists.map { |a| a.to_h }
59
+ }.to_json
60
+ end
61
+
62
+ Sinatra::Application.get('/playlists') do
63
+ content_type :json
64
+ {
65
+ 'status'=>'OK',
66
+ 'result'=> { 'playlists' => jotify.playlists.map do |p|
67
+ p.to_h
68
+ end.each { |h| h.delete(:tracks) }
69
+ }
70
+ }.to_json
71
+ end
72
+
73
+ Sinatra::Application.get('/playlists/:id') do
74
+ content_type :json
75
+ if playlist = jotify.playlist(params[:id])
76
+ {
77
+ 'status'=>'OK',
78
+ 'result'=>playlist.to_h
79
+ }.to_json
80
+ else
81
+ return 404, { 'status' => 'ERROR', 'message' => 'playlist not found' }.to_json
82
+ end
83
+ end
84
+
85
+ Sinatra::Application.post('/playlists') do
86
+ content_type :json
87
+ body = request.body.read
88
+ data = JSON.parse(body)
89
+ playlist = jotify.create_playlist(data['name'], !!data['collaborative'])
90
+ if playlist
91
+ if data['tracks']
92
+ ids = data['tracks'].map { |t| t['id'] }
93
+ unless jotify.set_tracks_on_playlist(playlist, ids)
94
+ return 500, 'status' => 'ERROR', 'message' => 'playlist created but tracks could not be added'
95
+ end
96
+ end
97
+ redirect playlist.link, 201 # created
98
+ else
99
+ return 500, { 'status' => 'ERROR', 'message' => 'playlist could not be created' }.to_json
100
+ end
101
+ end
102
+
103
+ Sinatra::Application.put('/playlists/:id') do
104
+ content_type :json
105
+ playlist = jotify.playlist(params[:id])
106
+
107
+ return 404, { 'status' => 'ERROR', 'message' => 'playlist not found' }.to_json unless playlist
108
+ body = request.body.read
109
+ data = JSON.parse(body)
110
+
111
+ raise ArgumentError, "invalid format" unless data.is_a?(Hash)
112
+
113
+ if data.has_key?('name') && data['name'] != playlist.name
114
+ unless jotify.rename_playlist(playlist, data['name'])
115
+ return 500, { 'status' => 'ERROR', 'message' => 'could rename playlist' }.to_json
116
+ end
117
+ end
118
+
119
+ if data.has_key?('collaborative') && data['collaborative'] != playlist.collaborative?
120
+ unless jotify.set_collaborative_flag(playlist, data['collaborative'])
121
+ return 500, { 'status' => 'ERROR', 'message' => 'could not change collaborative flag' }.to_json
122
+ end
123
+ end
124
+
125
+ if data['tracks'].is_a?(Array)
126
+ ids = data['tracks'].map { |t| t['id'] }
127
+ unless jotify.set_tracks_on_playlist(playlist, ids)
128
+ return 500, { 'status' => 'ERROR', 'message' => 'could update tracks' }.to_json
129
+ end
130
+ end
131
+ return 200, { 'status' => 'OK', 'message' => "update successful" }.to_json
132
+ end
133
+
134
+
135
+ if __FILE__ == $0
136
+ Sinatra::Application.run!
137
+ end
@@ -0,0 +1,117 @@
1
+ require 'java'
2
+ require File.expand_path(File.dirname(__FILE__) + '/../jars/jotify.jar')
3
+
4
+ #Make Jotify's native Java classes a bit more rubyish by implementing some mehods / modules
5
+ module Java
6
+ module DeFelixbrunsJotifyMedia
7
+ class Media
8
+ def inspect
9
+ self.to_s
10
+ end
11
+
12
+ def to_h
13
+ h = { :id=>self.getId(), :popularity=> popularity.nan? ? 0.0 : popularity.to_f }
14
+ h[:url] = self.link if self.respond_to?(:link)
15
+ h
16
+ end
17
+ end
18
+
19
+ class Playlist
20
+ include Enumerable
21
+
22
+ def each(&block)
23
+ tracks.each(&block)
24
+ end
25
+
26
+ def size
27
+ tracks.size
28
+ end
29
+
30
+ def collaborative?
31
+ isCollaborative()
32
+ end
33
+
34
+ def inspect
35
+ "[Playlist: #{self.getId()} #{getTracks.to_a}]"
36
+ end
37
+
38
+ def to_h
39
+ {
40
+ :id => getId(),
41
+ :name=> name,
42
+ :url => link,
43
+ :tracks => tracks.map { |t| t.to_h },
44
+ :author => author,
45
+ :revision => revision,
46
+ :collaborative => collaborative
47
+ }
48
+ end
49
+
50
+ def <<(track)
51
+ tracks.add(track) if track.is_a?(Track)
52
+ end
53
+ end
54
+
55
+ class PlaylistContainer
56
+ include Enumerable
57
+
58
+ def each(&block)
59
+ playlists.each(&block)
60
+ end
61
+
62
+ def size() playlists.size end
63
+
64
+ def <<(pl)
65
+ playlists.add(pl) if pl.is_a?(Playlist)
66
+ end
67
+ end
68
+
69
+ class Result
70
+ include Enumerable
71
+
72
+ def each(&block)
73
+ artists.each(&block)
74
+ tracks.each(&block)
75
+ albums.each(&block)
76
+ end
77
+
78
+ def inspect
79
+ { :artists=>self.artists.to_a, :albums=>self.albums.to_a, :tracks=>self.tracks.to_a }.inspect
80
+ end
81
+
82
+ def to_h
83
+ {
84
+ :artists => self.artists.map { |a| a.to_h },
85
+ :albums => self.albums.map { |a| a.to_h },
86
+ :tracks => self.tracks.map { |t| t.to_h }
87
+ }
88
+ end
89
+ end
90
+
91
+ class Track
92
+ def to_h
93
+ super.merge(:title=>title, :artist=>artist ? artist.name : nil, :album=>album ? album.name : nil)
94
+ end
95
+ end
96
+
97
+ class Artist
98
+ def to_h
99
+ super.merge(:name=>name)
100
+ end
101
+ end
102
+
103
+ class Album
104
+ include Enumerable
105
+
106
+ def each(&block)
107
+ tracks.each(&block)
108
+ end
109
+
110
+ def to_h
111
+ super.merge(:name=>name, :artist=>artist ? artist.name : nil, :year=>year, :type=>type)
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env jruby -S spec
2
+ require File.join(File.dirname(__FILE__), 'spec_helper')
3
+ require 'jotify'
4
+
5
+ # WARNING: these specs need a valid spotify (premium) account, otherwise they will fail
6
+ describe "Integration" do
7
+
8
+ before(:all) do
9
+ @jotify = Jotify.new
10
+ end
11
+
12
+ after(:all) do
13
+ @jotify.close rescue nil
14
+ end
15
+
16
+ describe "searching" do
17
+ it "should return a result for a search" do
18
+ result = @jotify.search("artist:Air")
19
+ result.should be_a(Jotify::Media::Result)
20
+
21
+ result.artists.should_not be_empty
22
+ most_popular = result.artists.to_a.sort(&Jotify::ByPopularity).first
23
+ most_popular.name.should == 'Air'
24
+
25
+ result = @jotify.search("album:Moon Safari")
26
+ result.albums.should_not be_empty
27
+ most_popular = result.albums.to_a.sort(&Jotify::ByPopularity).first
28
+ most_popular.name.should == 'Moon Safari'
29
+ most_popular.artist.name.should == 'Air'
30
+ end
31
+ end
32
+
33
+
34
+ describe "playlists" do
35
+
36
+ before do
37
+ @playlists = @jotify.playlists
38
+ @playlists.should_not be_empty
39
+ end
40
+
41
+ it "should return name and id, not tracks (GH-3)" do
42
+ @playlists.each do |pl|
43
+ puts pl.inspect
44
+ pl.name.should_not be_nil
45
+
46
+ pl.tracks.each do |t|
47
+ t.getId().should_not be_nil
48
+ t.getName().should be_nil
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ end
@@ -0,0 +1,222 @@
1
+ #!/usr/bin/env jruby -S spec
2
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
3
+
4
+ require 'jotify/api'
5
+ require 'rack/test'
6
+ require 'sinatra'
7
+
8
+ set :environment, :test
9
+
10
+ describe 'Api' do
11
+
12
+ def app() Sinatra::Application end
13
+
14
+ def json_response
15
+ last_response.content_type.should == 'application/json'
16
+ JSON.parse(last_response.body)
17
+ end
18
+
19
+
20
+ before do
21
+ @jotify = jotify = mock('Jotify')
22
+ Sinatra::Application.send(:define_method, :jotify) { jotify }
23
+ end
24
+
25
+ describe "/artists" do
26
+ it "searches by artist name" do
27
+ res = Jotify::Media::Result.new
28
+ res.artists.add(Jotify::Media::Artist.new('4d921ebcdd8c80f32ce1ed5acafbb9c8', 'The Kinks'))
29
+ @jotify.stub!(:search).and_return(res)
30
+ get '/artists', :name=>'The Kinks'
31
+ last_response.should be_ok
32
+ json_response.should == {"status"=>"OK", "result"=>
33
+ [{"id"=>"4d921ebcdd8c80f32ce1ed5acafbb9c8", "popularity"=>0.0, "url"=>"http://open.spotify.com/artist/2mnbxTkghYtlHMdX3jdP9C", "name"=>"The Kinks"}]
34
+ }
35
+ end
36
+
37
+ it "should return an error message if no name is specified" do
38
+ lambda { get '/artists' }.should raise_error(ArgumentError)
39
+ end
40
+ end
41
+
42
+ describe "/tracks" do
43
+ it "searches by track name" do
44
+ res = Jotify::Media::Result.new
45
+ res.tracks.add(Jotify::Media::Track.new('4d921ebcdd8c80f32ce1ed5acafbb9c8'))
46
+ @jotify.stub!(:search).and_return(res)
47
+ get '/tracks', :name=>'Waterloo Sunset'
48
+ last_response.should be_ok
49
+ json_response.should == {"status"=>"OK", "result"=>
50
+ [{"id"=>"4d921ebcdd8c80f32ce1ed5acafbb9c8", "popularity"=>0.0, "url"=>"http://open.spotify.com/track/2mnbxTkghYtlHMdX3jdP9C", "title"=>nil, "artist"=>nil, "album"=>nil}]
51
+ }
52
+ end
53
+
54
+ it "should return an error message if no name is specified" do
55
+ lambda { get '/tracks' }.should raise_error(ArgumentError)
56
+ end
57
+ end
58
+
59
+ describe "/albums" do
60
+ it "searches by album name" do
61
+ res = Jotify::Media::Result.new
62
+ res.albums.add(Jotify::Media::Album.new('4d921ebcdd8c80f32ce1ed5acafbb9c8',
63
+ 'Something Else',
64
+ Jotify::Media::Artist.new('4d921ebcdd8c80f32ce1ed5acafbb9c8', 'The Kinks')))
65
+
66
+ @jotify.stub!(:search).and_return(res)
67
+ get '/albums', :name=>'Something Else'
68
+ last_response.should be_ok
69
+ json_response.should == {"status"=>"OK", "result"=>
70
+ [{"id"=>"4d921ebcdd8c80f32ce1ed5acafbb9c8",
71
+ "popularity"=>0.0,
72
+ "url"=>"http://open.spotify.com/album/2mnbxTkghYtlHMdX3jdP9C",
73
+ "name"=>"Something Else",
74
+ "artist"=>"The Kinks", "year"=>-1, "type"=>nil}]
75
+ }
76
+ end
77
+
78
+ it "should return an error message if no name is specified" do
79
+ lambda { get '/albums' }.should raise_error(ArgumentError)
80
+ end
81
+ end
82
+
83
+ describe "/playlists" do
84
+ before do
85
+ #String id, String name, String author, boolean collaborative
86
+ @playlist = Jotify::Media::Playlist.new("4d921ebcdd8c80f32ce1ed5acafbb9c8", "my shiny playlist", "test", false)
87
+
88
+ end
89
+
90
+ describe "get" do
91
+
92
+ it "should get all playlists created by the user" do
93
+ @jotify.should_receive(:playlists).and_return( [@playlist] )
94
+ get '/playlists'
95
+ last_response.should be_ok
96
+ json_response.should == {"status"=>"OK",
97
+ "result"=> {
98
+ "playlists" => [
99
+ "id"=>"4d921ebcdd8c80f32ce1ed5acafbb9c8",
100
+ "url"=>"http://open.spotify.com/user/test/playlist/2mnbxTkghYtlHMdX3jdP9C",
101
+ "name"=>"my shiny playlist",
102
+ "author"=>"test", "revision"=>-1, "collaborative"=>false
103
+ ]
104
+ }
105
+ }
106
+ end
107
+
108
+
109
+ it "should retrieve a playlist by id" do
110
+ @jotify.should_receive(:playlist).with("4d921ebcdd8c80f32ce1ed5acafbb9c8").and_return(@playlist)
111
+ get '/playlists/4d921ebcdd8c80f32ce1ed5acafbb9c8'
112
+ last_response.should be_ok
113
+ json_response.should == {"status"=>"OK",
114
+ "result"=>{
115
+ "id"=>"4d921ebcdd8c80f32ce1ed5acafbb9c8",
116
+ "url"=>"http://open.spotify.com/user/test/playlist/2mnbxTkghYtlHMdX3jdP9C",
117
+ "name"=>"my shiny playlist",
118
+ "tracks"=>[], "author"=>"test", "revision"=>-1, "collaborative"=>false}
119
+ }
120
+ end
121
+
122
+ it "should except a base62 string as id" do
123
+ @jotify.should_receive(:playlist).with("2mnbxTkghYtlHMdX3jdP9C").and_return(@playlist)
124
+ get '/playlists/2mnbxTkghYtlHMdX3jdP9C'
125
+ last_response.should be_ok
126
+ end
127
+
128
+ it "should return 404 for non-existing playlist" do
129
+ @jotify.should_receive(:playlist).with("unknown").and_return(nil)
130
+ get '/playlists/unknown'
131
+ last_response.status.should == 404
132
+ json_response.should == {"status"=>"ERROR", "message"=>"playlist not found"}
133
+ end
134
+ end
135
+
136
+
137
+ describe "create" do
138
+ it "should create a playlist when posting to /playlists" do
139
+ @jotify.should_receive(:create_playlist).with('my shiny playlist', true).and_return(@playlist)
140
+ post '/playlists', { 'name' => 'my shiny playlist', 'collaborative' => true }.to_json
141
+ last_response.status.should == 201
142
+ last_response.headers['Location'].should == 'http://open.spotify.com/user/test/playlist/2mnbxTkghYtlHMdX3jdP9C'
143
+ end
144
+
145
+ it "should create a playlist and adding tracks when posting to /playlists" do
146
+ @jotify.should_receive(:create_playlist).with('my shiny playlist', true).and_return(@playlist)
147
+ @jotify.should_receive(:set_tracks_on_playlist).with(@playlist, ['1','2']).and_return(true)
148
+
149
+ post '/playlists', { 'name' => 'my shiny playlist',
150
+ 'collaborative' => true,
151
+ 'tracks' => [ {'id'=>'1' }, { 'id'=>'2' } ]
152
+ }.to_json
153
+ last_response.status.should == 201
154
+ last_response.headers['Location'].should == 'http://open.spotify.com/user/test/playlist/2mnbxTkghYtlHMdX3jdP9C'
155
+ end
156
+
157
+
158
+ it "should return 500 if playlist could not be created" do
159
+ @jotify.should_receive(:create_playlist).with('my shiny playlist', true).and_return(nil)
160
+ post '/playlists', { :name => 'my shiny playlist', :collaborative =>true }.to_json
161
+ last_response.status.should == 500
162
+ json_response.should == {"status"=>"ERROR", "message"=>"playlist could not be created"}
163
+ end
164
+
165
+ end
166
+
167
+ describe "update" do
168
+ it "should update playlist when putting to /playlists/id" do
169
+ @jotify.should_receive(:playlist).with("foo").and_return(@playlist)
170
+ @jotify.should_receive(:set_tracks_on_playlist).with(@playlist, ['1','2']).and_return(true)
171
+ put '/playlists/foo', { 'tracks' => [ {'id'=>'1' }, { 'id'=>'2' } ] }.to_json
172
+ last_response.should be_ok
173
+ json_response.should == {'status'=>'OK', 'message'=>'update successful'}
174
+ end
175
+
176
+ it "should change the name of the playlist" do
177
+ @jotify.should_receive(:playlist).with("foo").and_return(@playlist)
178
+ @jotify.should_receive(:rename_playlist).with(@playlist, 'new').and_return(true)
179
+
180
+ put '/playlists/foo', { 'name' => 'new' }.to_json
181
+ last_response.should be_ok
182
+ json_response.should == {'status'=>'OK', 'message'=>'update successful'}
183
+ end
184
+
185
+ it "should change the collaborative flag of the playlist" do
186
+ @jotify.should_receive(:playlist).with("foo").and_return(@playlist)
187
+ @jotify.should_receive(:set_collaborative_flag).with(@playlist, true).and_return(true)
188
+
189
+ put '/playlists/foo', { 'collaborative' => true }.to_json
190
+ last_response.should be_ok
191
+ json_response.should == {'status'=>'OK', 'message'=>'update successful'}
192
+ end
193
+
194
+
195
+ it "should return 404 if playlist to update cannot be found" do
196
+ @jotify.should_receive(:playlist).with("foo").and_return(nil)
197
+ put '/playlists/foo', { 'tracks' => [ {'id'=>'1' }, { 'id'=>'2' } ] }.to_json
198
+ last_response.status.should == 404
199
+ json_response.should == {"status"=>"ERROR", "message"=>"playlist not found"}
200
+ end
201
+
202
+ it "should return 500 if playlist could not be updated" do
203
+ @jotify.should_receive(:playlist).with("foo").and_return(@playlist)
204
+ @jotify.should_receive(:set_tracks_on_playlist).with(@playlist, ['1','2']).and_return(false)
205
+ put '/playlists/foo', { 'tracks' => [ {'id'=>'1' }, { 'id'=>'2' } ] }.to_json
206
+ last_response.status.should == 500
207
+ json_response.should == {"status"=>"ERROR", "message"=>"could update tracks"}
208
+ end
209
+
210
+ # it "should return 403 if invalid data is supplied" do
211
+ # @jotify.should_receive(:playlist).with("foo").and_return(@playlist)
212
+ # lambda { put '/playlists/foo', { 'foo' => 'bar' }.to_json }.should raise_error(ArgumentError)
213
+ # end
214
+
215
+ it "shouldn't do anything if no data supplied" do
216
+ @jotify.should_receive(:playlist).with("foo").and_return(@playlist)
217
+ put '/playlists/foo', {}.to_json
218
+ last_response.should be_ok
219
+ end
220
+ end
221
+ end
222
+ end