jberkel-spotify-api 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/LICENSE +24 -0
- data/README.md +91 -0
- data/Rakefile +33 -0
- data/VERSION.yml +4 -0
- data/bin/spotify-api-server +25 -0
- data/examples/create_playlist.rb +24 -0
- data/examples/list_playlists.rb +16 -0
- data/examples/search.rb +16 -0
- data/lib/jars/jotify.jar +0 -0
- data/lib/jotify.rb +109 -0
- data/lib/jotify/api.rb +114 -0
- data/lib/jotify/media.rb +99 -0
- data/spec/api_spec.rb +179 -0
- data/spec/integration_spec.rb +33 -0
- data/spec/jotify_spec.rb +32 -0
- data/spec/media_spec.rb +77 -0
- data/spec/spec_helper.rb +11 -0
- data/spotify-api.gemspec +76 -0
- metadata +119 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pkg
|
data/LICENSE
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Copyright (c) 2009, Jan Berkel
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
* Redistributions of source code must retain the above copyright
|
7
|
+
notice, this list of conditions and the following disclaimer.
|
8
|
+
* Redistributions in binary form must reproduce the above copyright
|
9
|
+
notice, this list of conditions and the following disclaimer in the
|
10
|
+
documentation and/or other materials provided with the distribution.
|
11
|
+
* Neither the name of spotify-api nor the
|
12
|
+
names of its contributors may be used to endorse or promote products
|
13
|
+
derived from this software without specific prior written permission.
|
14
|
+
|
15
|
+
THIS SOFTWARE IS PROVIDED BY Jan Berkel 'AS IS'' AND ANY
|
16
|
+
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
17
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL David R. MacIver BE LIABLE FOR ANY
|
19
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
20
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
21
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
22
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
23
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
24
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
|
2
|
+
# Spotify-API
|
3
|
+
|
4
|
+
I got tired of waiting for Spotify to release their API (if they release it), so decided to roll my own based on the awesome
|
5
|
+
work done by [#hack.se](http://despotify.se) (despotify) and Felix Bruns who created a Java port of despotify called
|
6
|
+
[jotify](http://jotify.felixbruns.de/). Add JRuby, Sinatra and some glue to the mix and you (almost) have an API.
|
7
|
+
|
8
|
+
*Important*: in order to use this API you'll need a premium spotify account! This API is as unofficial as it gets and *NOT* supported
|
9
|
+
by Spotify in any way.
|
10
|
+
|
11
|
+
At the moment the following features are implemented:
|
12
|
+
|
13
|
+
* searching [GET /(albums|tracks|artists)?name=Foo]
|
14
|
+
* list user's playlists [GET /playlists]
|
15
|
+
* get shared playlist [GET /playlist/id]
|
16
|
+
* create new playlist [POST /playlists]
|
17
|
+
* add tracks to playlist [PUT /playlists/id]
|
18
|
+
|
19
|
+
## Installation
|
20
|
+
|
21
|
+
Prerequisites: *Java 6+*, JRuby 1.3.x.
|
22
|
+
|
23
|
+
$ jruby -S gem install jberkel-spotify-api --source http://gems.github.com
|
24
|
+
$ jruby -S spotify-api-server --account login:password
|
25
|
+
== Sinatra/0.9.4 has taken the stage on 3000 for development with backup from WEBrick
|
26
|
+
[2009-08-04 01:21:03] INFO WEBrick 1.3.1
|
27
|
+
[2009-08-04 01:21:03] INFO ruby 1.8.6 (2009-07-24) [java]
|
28
|
+
[2009-08-04 01:21:03] INFO WEBrick::HTTPServer#start: pid=12162 port=3000
|
29
|
+
|
30
|
+
$ curl http://localhost:3000/playlists | jsonpretty
|
31
|
+
{
|
32
|
+
"result": {
|
33
|
+
"playlists": [
|
34
|
+
{
|
35
|
+
"name": "my shiny playlist",
|
36
|
+
"author": "jberkel",
|
37
|
+
"url": "http:\/\/open.spotify.com\/user\/jberkel\/playlist\/5EXLGE7HPVPjvlxPmIfrDe",
|
38
|
+
"revision": 2,
|
39
|
+
"id": "b9fe3dcf88945d146ef18117faa61ab4",
|
40
|
+
"tracks": [
|
41
|
+
{
|
42
|
+
"artist": "Elmore Judd",
|
43
|
+
"title": "Disco In 4 Pieces",
|
44
|
+
"url": "http:\/\/open.spotify.com\/track\/1VaucR6Bsks5Q9bYBsXvuF",
|
45
|
+
"id": "3f2752a98dd947c5855278a88159d7b1",
|
46
|
+
"album": "Insect Funk",
|
47
|
+
"popularity": 0.325379997491837
|
48
|
+
},
|
49
|
+
{
|
50
|
+
"artist": "40winks",
|
51
|
+
"title": "Goodmorning (intro)",
|
52
|
+
"url": "http:\/\/open.spotify.com\/track\/6qHiOf1BFCQIzAjJsRbMfY",
|
53
|
+
"id": "d34a3a6daeed4f93983068e4e8c26cd6",
|
54
|
+
"album": "Sound Puzzle",
|
55
|
+
"popularity": 0.170609995722771
|
56
|
+
}
|
57
|
+
],
|
58
|
+
"collaborative": false
|
59
|
+
}
|
60
|
+
]
|
61
|
+
},
|
62
|
+
"status": "OK"
|
63
|
+
}
|
64
|
+
|
65
|
+
See examples directory for usage.
|
66
|
+
|
67
|
+
## Credits
|
68
|
+
|
69
|
+
Contains code from the jotify project:
|
70
|
+
|
71
|
+
Copyright (c) 2009, Felix Bruns <felixbruns@web.de>
|
72
|
+
All rights reserved.
|
73
|
+
|
74
|
+
Redistribution and use in source and binary forms, with or without
|
75
|
+
modification, are permitted provided that the following conditions are met:
|
76
|
+
* Redistributions of source code must retain the above copyright
|
77
|
+
notice, this list of conditions and the following disclaimer.
|
78
|
+
* Redistributions in binary form must reproduce the above copyright
|
79
|
+
notice, this list of conditions and the following disclaimer in the
|
80
|
+
documentation and/or other materials provided with the distribution.
|
81
|
+
|
82
|
+
THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY
|
83
|
+
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
84
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
85
|
+
DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
86
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
87
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
88
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
89
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
90
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
91
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'jeweler'
|
7
|
+
Jeweler::Tasks.new do |gem|
|
8
|
+
gem.name = "spotify-api"
|
9
|
+
gem.summary = "an api for spotify, based on jotify"
|
10
|
+
gem.email = "jan.berkel@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/jberkel/spotify-api"
|
12
|
+
gem.description = "an api for spotify, based on jotify"
|
13
|
+
gem.authors = ["Jan Berkel"]
|
14
|
+
gem.add_dependency "rack"
|
15
|
+
gem.add_dependency "rack-test"
|
16
|
+
gem.add_dependency "sinatra"
|
17
|
+
gem.add_dependency "json-jruby"
|
18
|
+
end
|
19
|
+
rescue LoadError
|
20
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
21
|
+
end
|
22
|
+
|
23
|
+
Spec::Rake::SpecTask.new do |t|
|
24
|
+
t.rcov = false
|
25
|
+
t.spec_files = FileList["spec/**/*_spec.rb"].delete_if { |f| f =~ /integration/ }
|
26
|
+
t.libs << "./lib"
|
27
|
+
end
|
28
|
+
|
29
|
+
Spec::Rake::SpecTask.new(:integration) do |t|
|
30
|
+
t.rcov = false
|
31
|
+
t.spec_files = FileList["spec/**/*_spec.rb"].select { |f| f =~ /integration/ }
|
32
|
+
t.libs << "./lib"
|
33
|
+
end
|
data/VERSION.yml
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env jruby
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'jotify'
|
6
|
+
require 'jotify/api'
|
7
|
+
|
8
|
+
DEFAULT_PORT = port = 3000
|
9
|
+
|
10
|
+
def usage
|
11
|
+
STDERR.puts "#{File.basename($0)} [-p <port>] [--account <login>:<password>]"
|
12
|
+
exit(1)
|
13
|
+
end
|
14
|
+
|
15
|
+
case ARGV.shift
|
16
|
+
when '-p', '--port': port = ARGV.shift
|
17
|
+
when '--account':
|
18
|
+
login, password = ARGV.shift.to_s.split(':')
|
19
|
+
raise ArgumentError, "you need to specify both login and password!" unless login and password
|
20
|
+
Jotify.credentials = { :username=>login, :password=>password }
|
21
|
+
when '-h', '--help': usage
|
22
|
+
end
|
23
|
+
|
24
|
+
#Sinatra::Application.set :environment, :production
|
25
|
+
Sinatra::Application.run! :port=> (port || DEFAULT_PORT).to_i
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'rest_client'
|
5
|
+
require 'json'
|
6
|
+
require 'pp'
|
7
|
+
|
8
|
+
puts "creating a new playlist"
|
9
|
+
resp = RestClient.post 'http://localhost:3000/playlists', :name=>'my shiny playlist'
|
10
|
+
if resp.code == 201
|
11
|
+
location = resp.headers[:location]
|
12
|
+
puts "201 created (#{location})"
|
13
|
+
spotify_id = location[location.rindex('/')+1..-1]
|
14
|
+
|
15
|
+
puts "adding some tracks to it"
|
16
|
+
resp = RestClient.put "http://localhost:3000/playlists/#{spotify_id}", {
|
17
|
+
"tracks" => [ {'id'=>'6qHiOf1BFCQIzAjJsRbMfY'}, {'id'=>'1VaucR6Bsks5Q9bYBsXvuF'} ]
|
18
|
+
}.to_json
|
19
|
+
puts resp
|
20
|
+
else
|
21
|
+
raise resp.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'rest_client'
|
5
|
+
require 'json'
|
6
|
+
require 'pp'
|
7
|
+
|
8
|
+
puts "getting all playlists"
|
9
|
+
resp = RestClient.get 'http://localhost:3000/playlists'
|
10
|
+
playlists = JSON.parse(resp)
|
11
|
+
pp playlists
|
12
|
+
|
13
|
+
playlists['result']['playlists'].each do |p|
|
14
|
+
puts "retrieving details for playlist #{p['name']}:"
|
15
|
+
pp JSON.parse(RestClient.get "http://localhost:3000/playlists/#{p['id']}")
|
16
|
+
end
|
data/examples/search.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'rest_client'
|
5
|
+
require 'json'
|
6
|
+
require 'pp'
|
7
|
+
|
8
|
+
puts "searching for album: Moon Safari"
|
9
|
+
pp JSON.parse(RestClient.get('http://localhost:3000/albums?name=Moon+Safari'))
|
10
|
+
|
11
|
+
puts "searching for track: The End"
|
12
|
+
pp JSON.parse(RestClient.get('http://localhost:3000/tracks?name=The+End'))
|
13
|
+
|
14
|
+
puts "searching for artist: The Doors"
|
15
|
+
pp JSON.parse(RestClient.get('http://localhost:3000/artists?name=The+Doors'))
|
16
|
+
|
data/lib/jars/jotify.jar
ADDED
Binary file
|
data/lib/jotify.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'java'
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/jars/jotify.jar')
|
3
|
+
|
4
|
+
class Jotify
|
5
|
+
|
6
|
+
module Media
|
7
|
+
import 'de.felixbruns.jotify.media.Playlist'
|
8
|
+
import 'de.felixbruns.jotify.media.PlaylistContainer'
|
9
|
+
import 'de.felixbruns.jotify.media.Result'
|
10
|
+
import 'de.felixbruns.jotify.media.Track'
|
11
|
+
import 'de.felixbruns.jotify.media.Artist'
|
12
|
+
import 'de.felixbruns.jotify.media.Album'
|
13
|
+
end
|
14
|
+
|
15
|
+
import 'de.felixbruns.jotify.gui.util.JotifyPreferences'
|
16
|
+
import 'de.felixbruns.jotify.util.SpotifyURI'
|
17
|
+
|
18
|
+
ByPopularity = Proc.new { |a,b| b.popularity <=> a.popularity }
|
19
|
+
|
20
|
+
[:close, :search].each do |m|
|
21
|
+
define_method(m) do |*args|
|
22
|
+
@jotify.send(m, *args)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(jotify_impl=Java::DeFelixbrunsJotify::JotifyPool.new(4))
|
27
|
+
@jotify = jotify_impl
|
28
|
+
|
29
|
+
credentials = Jotify.credentials
|
30
|
+
@jotify.login(credentials[:username], credentials[:password])
|
31
|
+
|
32
|
+
at_exit do
|
33
|
+
begin
|
34
|
+
@jotify.close
|
35
|
+
rescue Exception => e
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
if block_given?
|
40
|
+
begin
|
41
|
+
yield self
|
42
|
+
ensure
|
43
|
+
close
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def playlists
|
49
|
+
@jotify.playlists.map { |p| playlist(p.getId()) }
|
50
|
+
end
|
51
|
+
|
52
|
+
def playlist(id)
|
53
|
+
playlist = @jotify.playlist(Jotify.resolve_id(id))
|
54
|
+
unless playlist.tracks.empty?
|
55
|
+
res = @jotify.browse(playlist.tracks)
|
56
|
+
res.tracks.each_with_index do |t,i|
|
57
|
+
playlist.tracks.set(i, t)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
playlist
|
61
|
+
end
|
62
|
+
|
63
|
+
def create_playlist(name, collaborative=false)
|
64
|
+
playlist = @jotify.playlistCreate(name, collaborative)
|
65
|
+
return nil unless playlist
|
66
|
+
add_playlist(playlist)
|
67
|
+
playlist
|
68
|
+
end
|
69
|
+
|
70
|
+
def add_playlist(id)
|
71
|
+
@jotify.playlistsAddPlaylist(@jotify.playlists, id.is_a?(Media::Playlist) ? id : playlist(id))
|
72
|
+
end
|
73
|
+
|
74
|
+
def add_tracks_to_playlist(playlist, track_ids)
|
75
|
+
tracks = Java::JavaUtil::ArrayList.new
|
76
|
+
track_ids.each { |id| tracks.add(Media::Track.new(Jotify.resolve_id(id))) }
|
77
|
+
@jotify.playlistAddTracks(playlist, tracks, 0)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.resolve_id(id)
|
81
|
+
case id
|
82
|
+
when /\Ahttp:\/\/open\.spotify\.com/: SpotifyURI.to_hex(id[id.rindex('/')+1..-1])
|
83
|
+
when /spotify:/: SpotifyURI.to_hex(id[id.rindex(':')+1..-1])
|
84
|
+
when /\A[0-9a-f]{32}\Z/: id
|
85
|
+
when /\A[a-zA-Z0-9]{22}\Z/: SpotifyURI.to_hex(id)
|
86
|
+
else
|
87
|
+
raise "invalid id: #{id}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.credentials
|
92
|
+
prefs = JotifyPreferences.getInstance()
|
93
|
+
prefs.load()
|
94
|
+
{
|
95
|
+
:username => prefs.getString("login.username"),
|
96
|
+
:password => prefs.getString("login.password")
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.credentials=(creds)
|
101
|
+
prefs = JotifyPreferences.getInstance()
|
102
|
+
prefs.load()
|
103
|
+
prefs.setString("login.username", creds[:username])
|
104
|
+
prefs.setString("login.password", creds[:password])
|
105
|
+
prefs.save() or raise "could not save login details"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
require File.expand_path(File.dirname(__FILE__) + '/jotify/media')
|
data/lib/jotify/api.rb
ADDED
@@ -0,0 +1,114 @@
|
|
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
|
+
#playlists = jotify.playlists
|
65
|
+
{
|
66
|
+
'status'=>'OK',
|
67
|
+
'result'=> { 'playlists' => jotify.playlists.map { |p| p.to_h } }
|
68
|
+
}.to_json
|
69
|
+
end
|
70
|
+
|
71
|
+
Sinatra::Application.get('/playlists/:id') do
|
72
|
+
content_type :json
|
73
|
+
if playlist = jotify.playlist(params[:id])
|
74
|
+
{
|
75
|
+
'status'=>'OK',
|
76
|
+
'result'=>playlist.to_h
|
77
|
+
}.to_json
|
78
|
+
else
|
79
|
+
return 404, { 'status' => 'ERROR', 'message' => 'playlist not found' }.to_json
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
Sinatra::Application.post('/playlists') do
|
84
|
+
content_type :json
|
85
|
+
name, collaborative = params[:name], params[:collaborative] == 'true'
|
86
|
+
playlist = jotify.create_playlist(name, collaborative)
|
87
|
+
if playlist
|
88
|
+
redirect playlist.link, 201 # created
|
89
|
+
else
|
90
|
+
return 500, { 'status' => 'ERROR', 'message' => 'playlist could not be created' }.to_json
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
Sinatra::Application.put('/playlists/:id') do
|
95
|
+
content_type :json
|
96
|
+
playlist = jotify.playlist(params[:id])
|
97
|
+
return 404, { 'status' => 'ERROR', 'message' => 'playlist not found' }.to_json unless playlist
|
98
|
+
body = request.body.read
|
99
|
+
data = JSON.parse(body)
|
100
|
+
raise ArgumentError, "invalid format" unless data.is_a?(Hash) && data['tracks'].is_a?(Array)
|
101
|
+
ids = data['tracks'].map { |t| t['id'] }
|
102
|
+
return 200, { 'status' => 'OK', 'message' => 'not modified' }.to_json if ids.empty?
|
103
|
+
|
104
|
+
if jotify.add_tracks_to_playlist(playlist, ids)
|
105
|
+
return 200, { 'status' => 'OK', 'message' => "successfully added #{ids.size} tracks" }.to_json
|
106
|
+
else
|
107
|
+
return 500, { 'status' => 'ERROR', 'message' => 'could not add to playlist' }.to_json
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
if __FILE__ == $0
|
113
|
+
Sinatra::Application.run!
|
114
|
+
end
|
data/lib/jotify/media.rb
ADDED
@@ -0,0 +1,99 @@
|
|
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 inspect
|
27
|
+
"[Playlist: #{self.getId()} #{getTracks.to_a}]"
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_h
|
31
|
+
{
|
32
|
+
:id => getId(),
|
33
|
+
:name=> name,
|
34
|
+
:url => link,
|
35
|
+
:tracks => tracks.map { |t| t.to_h },
|
36
|
+
:author => author,
|
37
|
+
:revision => revision,
|
38
|
+
:collaborative => collaborative
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class PlaylistContainer
|
44
|
+
include Enumerable
|
45
|
+
|
46
|
+
def each(&block)
|
47
|
+
playlists.each(&block)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class Result
|
52
|
+
include Enumerable
|
53
|
+
|
54
|
+
def each(&block)
|
55
|
+
artists.each(&block)
|
56
|
+
tracks.each(&block)
|
57
|
+
albums.each(&block)
|
58
|
+
end
|
59
|
+
|
60
|
+
def inspect
|
61
|
+
{ :artists=>self.artists.to_a, :albums=>self.albums.to_a, :tracks=>self.tracks.to_a }.inspect
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_h
|
65
|
+
{
|
66
|
+
:artists => self.artists.map { |a| a.to_h },
|
67
|
+
:albums => self.albums.map { |a| a.to_h },
|
68
|
+
:tracks => self.tracks.map { |t| t.to_h }
|
69
|
+
}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class Track
|
74
|
+
def to_h
|
75
|
+
super.merge(:title=>title, :artist=>artist ? artist.name : nil, :album=>album ? album.name : nil)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class Artist
|
80
|
+
def to_h
|
81
|
+
super.merge(:name=>name)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class Album
|
86
|
+
include Enumerable
|
87
|
+
|
88
|
+
def each(&block)
|
89
|
+
tracks.each(&block)
|
90
|
+
end
|
91
|
+
|
92
|
+
def to_h
|
93
|
+
super.merge(:name=>name, :artist=>artist ? artist.name : nil, :year=>year, :type=>type)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
|
data/spec/api_spec.rb
ADDED
@@ -0,0 +1,179 @@
|
|
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
|
+
end
|
88
|
+
|
89
|
+
describe "get" do
|
90
|
+
|
91
|
+
it "should get all playlists created by the user" do
|
92
|
+
@jotify.should_receive(:playlists).and_return( [@playlist] )
|
93
|
+
get '/playlists'
|
94
|
+
last_response.should be_ok
|
95
|
+
json_response.should == {"status"=>"OK",
|
96
|
+
"result"=> {
|
97
|
+
"playlists" => [
|
98
|
+
"id"=>"4d921ebcdd8c80f32ce1ed5acafbb9c8",
|
99
|
+
"url"=>"http://open.spotify.com/user/test/playlist/2mnbxTkghYtlHMdX3jdP9C",
|
100
|
+
"name"=>"my shiny playlist",
|
101
|
+
"tracks"=>[], "author"=>"test", "revision"=>-1, "collaborative"=>false
|
102
|
+
]
|
103
|
+
}
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
it "should retrieve a playlist by id" do
|
109
|
+
@jotify.should_receive(:playlist).with("4d921ebcdd8c80f32ce1ed5acafbb9c8").and_return(@playlist)
|
110
|
+
get '/playlists/4d921ebcdd8c80f32ce1ed5acafbb9c8'
|
111
|
+
last_response.should be_ok
|
112
|
+
json_response.should == {"status"=>"OK",
|
113
|
+
"result"=>{
|
114
|
+
"id"=>"4d921ebcdd8c80f32ce1ed5acafbb9c8",
|
115
|
+
"url"=>"http://open.spotify.com/user/test/playlist/2mnbxTkghYtlHMdX3jdP9C",
|
116
|
+
"name"=>"my shiny playlist",
|
117
|
+
"tracks"=>[], "author"=>"test", "revision"=>-1, "collaborative"=>false}
|
118
|
+
}
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should except a base62 string as id" do
|
122
|
+
@jotify.should_receive(:playlist).with("2mnbxTkghYtlHMdX3jdP9C").and_return(@playlist)
|
123
|
+
get '/playlists/2mnbxTkghYtlHMdX3jdP9C'
|
124
|
+
last_response.should be_ok
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should return 404 for non-existing playlist" do
|
128
|
+
@jotify.should_receive(:playlist).with("unknown").and_return(nil)
|
129
|
+
get '/playlists/unknown'
|
130
|
+
last_response.status.should == 404
|
131
|
+
json_response.should == {"status"=>"ERROR", "message"=>"playlist not found"}
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
describe "create/update" do
|
137
|
+
it "should create a playlist when posting to /playlists" do
|
138
|
+
@jotify.should_receive(:create_playlist).with('my shiny playlist', true).and_return(@playlist)
|
139
|
+
post '/playlists', :name => 'my shiny playlist', :collaborative => 'true'
|
140
|
+
last_response.status.should == 201
|
141
|
+
last_response.headers['Location'].should == 'http://open.spotify.com/user/test/playlist/2mnbxTkghYtlHMdX3jdP9C'
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should update playlist when putting to /playlists/id" do
|
145
|
+
@jotify.should_receive(:playlist).with("foo").and_return(@playlist)
|
146
|
+
@jotify.should_receive(:add_tracks_to_playlist).with(@playlist, ['1','2']).and_return(true)
|
147
|
+
put '/playlists/foo', { 'tracks' => [ {'id'=>'1' }, { 'id'=>'2' } ] }.to_json
|
148
|
+
last_response.should be_ok
|
149
|
+
json_response.should == {'status'=>'OK', 'message'=>'successfully added 2 tracks'}
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should return 404 if playlist to update cannot be found" do
|
153
|
+
@jotify.should_receive(:playlist).with("foo").and_return(nil)
|
154
|
+
put '/playlists/foo', { 'tracks' => [ {'id'=>'1' }, { 'id'=>'2' } ] }.to_json
|
155
|
+
last_response.status.should == 404
|
156
|
+
json_response.should == {"status"=>"ERROR", "message"=>"playlist not found"}
|
157
|
+
end
|
158
|
+
|
159
|
+
it "should return 500 if playlist could not be updated" do
|
160
|
+
@jotify.should_receive(:playlist).with("foo").and_return(@playlist)
|
161
|
+
@jotify.should_receive(:add_tracks_to_playlist).with(@playlist, ['1','2']).and_return(false)
|
162
|
+
put '/playlists/foo', { 'tracks' => [ {'id'=>'1' }, { 'id'=>'2' } ] }.to_json
|
163
|
+
last_response.status.should == 500
|
164
|
+
json_response.should == {"status"=>"ERROR", "message"=>"could not add to playlist"}
|
165
|
+
end
|
166
|
+
|
167
|
+
it "should return 403 if invalid data is supplied" do
|
168
|
+
@jotify.should_receive(:playlist).with("foo").and_return(@playlist)
|
169
|
+
lambda { put '/playlists/foo', { 'foo' => 'bar' }.to_json }.should raise_error(ArgumentError)
|
170
|
+
end
|
171
|
+
|
172
|
+
it "shouldn't do anything if no track ids supplied" do
|
173
|
+
@jotify.should_receive(:playlist).with("foo").and_return(@playlist)
|
174
|
+
put '/playlists/foo', { 'tracks' => [] }.to_json
|
175
|
+
last_response.should be_ok
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,33 @@
|
|
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
|
+
end
|
data/spec/jotify_spec.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env jruby -S spec
|
2
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
3
|
+
require 'jotify'
|
4
|
+
|
5
|
+
describe Jotify do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
@jotify_impl = mock('JotifyImpl')
|
9
|
+
@jotify_impl.stub!(:login)
|
10
|
+
@jotify = Jotify.new(@jotify_impl)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should resolve ids" do
|
14
|
+
{ "spotify:user:flumix:playlist:2mnbxTkghYtlHMdX3jdP9C" => "4d921ebcdd8c80f32ce1ed5acafbb9c8",
|
15
|
+
"http://open.spotify.com/user/flumix/playlist/2mnbxTkghYtlHMdX3jdP9C" => "4d921ebcdd8c80f32ce1ed5acafbb9c8",
|
16
|
+
"2mnbxTkghYtlHMdX3jdP9C" => "4d921ebcdd8c80f32ce1ed5acafbb9c8",
|
17
|
+
"4d921ebcdd8c80f32ce1ed5acafbb9c8" => "4d921ebcdd8c80f32ce1ed5acafbb9c8"
|
18
|
+
}.each { |id, expected| Jotify.resolve_id(id).should == expected }
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should add tracks to playlist" do
|
22
|
+
@playlist = Jotify::Media::Playlist.new
|
23
|
+
@jotify_impl.should_receive(:playlistAddTracks) do |playlist, tracks, pos|
|
24
|
+
playlist.should be_a(Jotify::Media::Playlist)
|
25
|
+
#playlist.should == @playlist
|
26
|
+
pos.should == 0
|
27
|
+
tracks.should be_an(Java::JavaUtil::List)
|
28
|
+
tracks.size.should == 1
|
29
|
+
end
|
30
|
+
@jotify.add_tracks_to_playlist(@playlist, ['4d921ebcdd8c80f32ce1ed5acafbb9c8'])
|
31
|
+
end
|
32
|
+
end
|
data/spec/media_spec.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
#!/usr/bin/env jruby -S spec
|
2
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
3
|
+
require 'jotify'
|
4
|
+
|
5
|
+
describe Jotify::Media do
|
6
|
+
|
7
|
+
describe "an artist" do
|
8
|
+
before do
|
9
|
+
@artist = Jotify::Media::Artist.new
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should implement to_h" do
|
13
|
+
@artist.respond_to?(:to_h).should be_true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "a track" do
|
18
|
+
before do
|
19
|
+
@track = Jotify::Media::Track.new
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should implement to_h" do
|
23
|
+
@track.respond_to?(:to_h).should be_true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "an album" do
|
28
|
+
before do
|
29
|
+
@album = Jotify::Media::Album.new
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should implement to_h" do
|
33
|
+
@album.respond_to?(:to_h).should be_true
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should implement enumerable" do
|
37
|
+
@album.class.included_modules.should include(Enumerable)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "a result" do
|
42
|
+
before do
|
43
|
+
@result = Jotify::Media::Result.new
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should implement to_h" do
|
47
|
+
@result.respond_to?(:to_h).should be_true
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should implement enumerable" do
|
51
|
+
@result.class.included_modules.should include(Enumerable)
|
52
|
+
@result.respond_to?(:each).should be_true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "playlists" do
|
57
|
+
it "should implement enumerable" do
|
58
|
+
p = Jotify::Media::Playlist.new
|
59
|
+
p.class.included_modules.should include(Enumerable)
|
60
|
+
p.respond_to?(:each).should be_true
|
61
|
+
10.times { p.tracks.add(Jotify::Media::Track.new) }
|
62
|
+
p.to_a.size.should == 10
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "playlist container" do
|
67
|
+
before do
|
68
|
+
@container = Jotify::Media::PlaylistContainer.new
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should implement enumerable" do
|
72
|
+
@container.respond_to?(:each).should be_true
|
73
|
+
@container.class.included_modules.should include(Enumerable)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/spotify-api.gemspec
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{spotify-api}
|
5
|
+
s.version = "0.0.1"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Jan Berkel"]
|
9
|
+
s.date = %q{2009-08-04}
|
10
|
+
s.default_executable = %q{spotify-api-server}
|
11
|
+
s.description = %q{an api for spotify, based on jotify}
|
12
|
+
s.email = %q{jan.berkel@gmail.com}
|
13
|
+
s.executables = ["spotify-api-server"]
|
14
|
+
s.extra_rdoc_files = [
|
15
|
+
"LICENSE",
|
16
|
+
"README.md"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".gitignore",
|
20
|
+
"LICENSE",
|
21
|
+
"README.md",
|
22
|
+
"Rakefile",
|
23
|
+
"VERSION.yml",
|
24
|
+
"bin/spotify-api-server",
|
25
|
+
"examples/create_playlist.rb",
|
26
|
+
"examples/list_playlists.rb",
|
27
|
+
"examples/search.rb",
|
28
|
+
"lib/jars/jotify.jar",
|
29
|
+
"lib/jotify.rb",
|
30
|
+
"lib/jotify/api.rb",
|
31
|
+
"lib/jotify/media.rb",
|
32
|
+
"spec/api_spec.rb",
|
33
|
+
"spec/integration_spec.rb",
|
34
|
+
"spec/jotify_spec.rb",
|
35
|
+
"spec/media_spec.rb",
|
36
|
+
"spec/spec_helper.rb",
|
37
|
+
"spotify-api.gemspec"
|
38
|
+
]
|
39
|
+
s.homepage = %q{http://github.com/jberkel/spotify-api}
|
40
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
41
|
+
s.require_paths = ["lib"]
|
42
|
+
s.rubygems_version = %q{1.3.5}
|
43
|
+
s.summary = %q{an api for spotify, based on jotify}
|
44
|
+
s.test_files = [
|
45
|
+
"spec/api_spec.rb",
|
46
|
+
"spec/integration_spec.rb",
|
47
|
+
"spec/jotify_spec.rb",
|
48
|
+
"spec/media_spec.rb",
|
49
|
+
"spec/spec_helper.rb",
|
50
|
+
"examples/create_playlist.rb",
|
51
|
+
"examples/list_playlists.rb",
|
52
|
+
"examples/search.rb"
|
53
|
+
]
|
54
|
+
|
55
|
+
if s.respond_to? :specification_version then
|
56
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
57
|
+
s.specification_version = 3
|
58
|
+
|
59
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
60
|
+
s.add_runtime_dependency(%q<rack>, [">= 0"])
|
61
|
+
s.add_runtime_dependency(%q<rack-test>, [">= 0"])
|
62
|
+
s.add_runtime_dependency(%q<sinatra>, [">= 0"])
|
63
|
+
s.add_runtime_dependency(%q<json-jruby>, [">= 0"])
|
64
|
+
else
|
65
|
+
s.add_dependency(%q<rack>, [">= 0"])
|
66
|
+
s.add_dependency(%q<rack-test>, [">= 0"])
|
67
|
+
s.add_dependency(%q<sinatra>, [">= 0"])
|
68
|
+
s.add_dependency(%q<json-jruby>, [">= 0"])
|
69
|
+
end
|
70
|
+
else
|
71
|
+
s.add_dependency(%q<rack>, [">= 0"])
|
72
|
+
s.add_dependency(%q<rack-test>, [">= 0"])
|
73
|
+
s.add_dependency(%q<sinatra>, [">= 0"])
|
74
|
+
s.add_dependency(%q<json-jruby>, [">= 0"])
|
75
|
+
end
|
76
|
+
end
|
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jberkel-spotify-api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jan Berkel
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-08-04 00:00:00 -07:00
|
13
|
+
default_executable: spotify-api-server
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rack
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rack-test
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: sinatra
|
37
|
+
type: :runtime
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
version:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: json-jruby
|
47
|
+
type: :runtime
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
description: an api for spotify, based on jotify
|
56
|
+
email: jan.berkel@gmail.com
|
57
|
+
executables:
|
58
|
+
- spotify-api-server
|
59
|
+
extensions: []
|
60
|
+
|
61
|
+
extra_rdoc_files:
|
62
|
+
- LICENSE
|
63
|
+
- README.md
|
64
|
+
files:
|
65
|
+
- .gitignore
|
66
|
+
- LICENSE
|
67
|
+
- README.md
|
68
|
+
- Rakefile
|
69
|
+
- VERSION.yml
|
70
|
+
- bin/spotify-api-server
|
71
|
+
- examples/create_playlist.rb
|
72
|
+
- examples/list_playlists.rb
|
73
|
+
- examples/search.rb
|
74
|
+
- lib/jars/jotify.jar
|
75
|
+
- lib/jotify.rb
|
76
|
+
- lib/jotify/api.rb
|
77
|
+
- lib/jotify/media.rb
|
78
|
+
- spec/api_spec.rb
|
79
|
+
- spec/integration_spec.rb
|
80
|
+
- spec/jotify_spec.rb
|
81
|
+
- spec/media_spec.rb
|
82
|
+
- spec/spec_helper.rb
|
83
|
+
- spotify-api.gemspec
|
84
|
+
has_rdoc: false
|
85
|
+
homepage: http://github.com/jberkel/spotify-api
|
86
|
+
licenses:
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options:
|
89
|
+
- --charset=UTF-8
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: "0"
|
97
|
+
version:
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: "0"
|
103
|
+
version:
|
104
|
+
requirements: []
|
105
|
+
|
106
|
+
rubyforge_project:
|
107
|
+
rubygems_version: 1.3.5
|
108
|
+
signing_key:
|
109
|
+
specification_version: 3
|
110
|
+
summary: an api for spotify, based on jotify
|
111
|
+
test_files:
|
112
|
+
- spec/api_spec.rb
|
113
|
+
- spec/integration_spec.rb
|
114
|
+
- spec/jotify_spec.rb
|
115
|
+
- spec/media_spec.rb
|
116
|
+
- spec/spec_helper.rb
|
117
|
+
- examples/create_playlist.rb
|
118
|
+
- examples/list_playlists.rb
|
119
|
+
- examples/search.rb
|