spotify-api 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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