spotify-api 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/CHANGELOG +12 -0
- data/LICENSE +24 -0
- data/README.md +74 -0
- data/Rakefile +41 -0
- data/VERSION.yml +4 -0
- data/bin/spotify-api-server +40 -0
- data/examples/lastfm.rb +43 -0
- data/examples/lastfm2spotify.rb +20 -0
- data/examples/spotify.rb +86 -0
- data/lib/jars/jotify.jar +0 -0
- data/lib/jotify.rb +128 -0
- data/lib/jotify/api.rb +137 -0
- data/lib/jotify/media.rb +117 -0
- data/spec/integration_spec.rb +54 -0
- data/spec/jotify/api_spec.rb +222 -0
- data/spec/jotify/media_spec.rb +106 -0
- data/spec/jotify_spec.rb +59 -0
- data/spec/spec_helper.rb +36 -0
- data/spotify-api.gemspec +77 -0
- metadata +121 -0
data/.gitignore
ADDED
data/CHANGELOG
ADDED
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,74 @@
|
|
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
|
+
* update playlists [PUT /playlists/id]
|
18
|
+
|
19
|
+
## Installation
|
20
|
+
|
21
|
+
Prerequisites: *Java 6+*, JRuby 1.3.x.
|
22
|
+
|
23
|
+
$ jruby -S gem sources -a http://gems.github.com # (you only have to do this once)
|
24
|
+
$ jruby -S gem install jberkel-spotify-api
|
25
|
+
$ jruby -S spotify-api-server --account login:password
|
26
|
+
== Sinatra/0.9.4 has taken the stage on 3000 for development with backup from WEBrick
|
27
|
+
[2009-08-04 01:21:03] INFO WEBrick 1.3.1
|
28
|
+
[2009-08-04 01:21:03] INFO ruby 1.8.6 (2009-07-24) [java]
|
29
|
+
[2009-08-04 01:21:03] INFO WEBrick::HTTPServer#start: pid=12162 port=3000
|
30
|
+
|
31
|
+
$ curl http://localhost:3000/playlists | jsonpretty
|
32
|
+
{
|
33
|
+
"result": {
|
34
|
+
"playlists": [
|
35
|
+
{
|
36
|
+
"name": "my shiny playlist",
|
37
|
+
"author": "jberkel",
|
38
|
+
"url": "http:\/\/open.spotify.com\/user\/jberkel\/playlist\/5EXLGE7HPVPjvlxPmIfrDe",
|
39
|
+
"revision": 2,
|
40
|
+
"id": "b9fe3dcf88945d146ef18117faa61ab4",
|
41
|
+
"collaborative": false
|
42
|
+
}
|
43
|
+
]
|
44
|
+
},
|
45
|
+
"status": "OK"
|
46
|
+
}
|
47
|
+
|
48
|
+
See examples directory for usage.
|
49
|
+
|
50
|
+
## Credits
|
51
|
+
|
52
|
+
Contains code from the jotify project:
|
53
|
+
|
54
|
+
Copyright (c) 2009, Felix Bruns <felixbruns@web.de>
|
55
|
+
All rights reserved.
|
56
|
+
|
57
|
+
Redistribution and use in source and binary forms, with or without
|
58
|
+
modification, are permitted provided that the following conditions are met:
|
59
|
+
* Redistributions of source code must retain the above copyright
|
60
|
+
notice, this list of conditions and the following disclaimer.
|
61
|
+
* Redistributions in binary form must reproduce the above copyright
|
62
|
+
notice, this list of conditions and the following disclaimer in the
|
63
|
+
documentation and/or other materials provided with the distribution.
|
64
|
+
|
65
|
+
THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY
|
66
|
+
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
67
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
68
|
+
DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
69
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
70
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
71
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
72
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
73
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
74
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/Rakefile
ADDED
@@ -0,0 +1,41 @@
|
|
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
|
+
desc "runs specs with rcov"
|
30
|
+
Spec::Rake::SpecTask.new('spec:coverage') do |t|
|
31
|
+
t.rcov = true
|
32
|
+
t.spec_files = FileList["spec/**/*_spec.rb"].delete_if { |f| f =~ /integration/ }
|
33
|
+
t.libs << "./lib"
|
34
|
+
t.rcov_opts = ['--exclude', 'spec/.*rb,\(__.+__\)']
|
35
|
+
end
|
36
|
+
|
37
|
+
Spec::Rake::SpecTask.new(:integration) do |t|
|
38
|
+
t.rcov = false
|
39
|
+
t.spec_files = FileList["spec/**/*_spec.rb"].select { |f| f =~ /integration/ }
|
40
|
+
t.libs << "./lib"
|
41
|
+
end
|
data/VERSION.yml
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/usr/bin/env jruby #--jdb -J-sourcepath -J/Users/jan/projects/jotify/src
|
2
|
+
$LOAD_PATH.unshift(File.expand_path(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
|
+
while arg = ARGV.shift do
|
16
|
+
case arg
|
17
|
+
when '-p', '--port': port = ARGV.shift
|
18
|
+
when '--account':
|
19
|
+
login, password = ARGV.shift.to_s.split(':')
|
20
|
+
raise ArgumentError, "you need to specify both login and password!" unless login and password
|
21
|
+
Jotify.credentials = { :username=>login, :password=>password }
|
22
|
+
when '-h', '--help': usage
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Taken mostly from
|
27
|
+
# http://groups.google.com/group/sinatrarb/t/a5cfc2b77a013a86
|
28
|
+
class Sinatra::Reloader < Rack::Reloader
|
29
|
+
def safe_load(file, mtime, stderr = $stderr)
|
30
|
+
# ::Sinatra::Application.reset!
|
31
|
+
# stderr.puts "#{self.class}: reseting routes"
|
32
|
+
super
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
#Sinatra::Application.set :environment, :production
|
37
|
+
Sinatra::Application.configure(:development) do |app|
|
38
|
+
#app.use Sinatra::Reloader
|
39
|
+
end
|
40
|
+
Sinatra::Application.run! :port=> (port || DEFAULT_PORT).to_i
|
data/examples/lastfm.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'httparty'
|
5
|
+
|
6
|
+
#A demo Last.fm API client, implemented
|
7
|
+
#using httparty (http://github.com/jnunemaker/httparty/)
|
8
|
+
class Lastfm
|
9
|
+
include HTTParty
|
10
|
+
|
11
|
+
base_uri 'ws.audioscrobbler.com'
|
12
|
+
default_params :api_key => "PUT_API_KEY_HERE"
|
13
|
+
|
14
|
+
class <<self
|
15
|
+
def loved_tracks(user_id)
|
16
|
+
query('user.getLovedTracks', :user=>user_id, :limit=>10)['lovedtracks']['track'].map do |r|
|
17
|
+
{ 'artist' => r['artist']['name'], 'title'=>r['name'], 'mbid' => r['mbid'] }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def recent_tracks(user_id)
|
22
|
+
query('user.getRecentTracks', :user=>user_id, :limit=>100)['recenttracks']['track'].map do |r|
|
23
|
+
{ 'artist' => r['artist'], 'title'=>r['name'], 'mbid' => r['mbid'] }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def top_tracks(user_id, period='overall')
|
28
|
+
unless ['overall', '7day', '3month', '6month', '12month'].include?(period)
|
29
|
+
raise ArgumentError, "invalid period"
|
30
|
+
end
|
31
|
+
|
32
|
+
query('user.getTopTracks', :period=>period, :user=>user_id)['toptracks']['track'].map do |r|
|
33
|
+
{ 'artist' => r['artist']['name'], 'title'=>r['name'], 'mbid' => r['mbid'] }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def query(method, args={})
|
38
|
+
result = get("/2.0/", :query => { :method => method }.merge(args))
|
39
|
+
raise result['lfm']['error'] if result['lfm'] && result['lfm']['status'] == 'failed'
|
40
|
+
result['lfm']
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__), *%w[lastfm])
|
4
|
+
require File.join(File.dirname(__FILE__), *%w[spotify])
|
5
|
+
|
6
|
+
# a demo app which grabs tracks from last.fm and creates a spotify
|
7
|
+
# playlist
|
8
|
+
if __FILE__ == $0
|
9
|
+
username = ARGV.shift or raise "#{$0} <username> [period=overall|7day|3month|6month|12month]"
|
10
|
+
period = ARGV.shift || '7day'
|
11
|
+
|
12
|
+
puts "fetching last.fm tracks (period=#{period})"
|
13
|
+
tracks = Lastfm.top_tracks(username, period).map do |track|
|
14
|
+
Spotify.tracks(track["title"], track["artist"]).first
|
15
|
+
end.flatten.compact
|
16
|
+
|
17
|
+
#puts "found tracks: #{tracks.inspect}"
|
18
|
+
puts "creating playlist with #{tracks.size} tracks"
|
19
|
+
puts Spotify.create_playlist(username, tracks.map { |t| t['id'] })
|
20
|
+
end
|
data/examples/spotify.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'httparty'
|
5
|
+
require 'json'
|
6
|
+
require 'pp'
|
7
|
+
|
8
|
+
#A demo spotify API client, implemented
|
9
|
+
#using httparty (http://github.com/jnunemaker/httparty/)
|
10
|
+
class Spotify
|
11
|
+
include HTTParty
|
12
|
+
|
13
|
+
base_uri 'localhost:3000'
|
14
|
+
|
15
|
+
def self.get_or_bail(path, params={})
|
16
|
+
resp = get(path, :query=>params)
|
17
|
+
raise resp.inspect if resp["status"] != "OK"
|
18
|
+
resp["result"]
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.artists(name)
|
22
|
+
get_or_bail("/artists", {:name => name})
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.tracks(name, artist=nil)
|
26
|
+
q = { :name => name }
|
27
|
+
q.merge!(:artist=>artist) if artist
|
28
|
+
get_or_bail("/tracks", q)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.albums(name, artist=nil)
|
32
|
+
q = { :name => name }
|
33
|
+
q.merge!(:artist=>artist) if artist
|
34
|
+
get_or_bail("/albums", q)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.playlist(id)
|
38
|
+
get_or_bail("/playlists", :id=>id)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.playlists
|
42
|
+
get_or_bail("/playlists")
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.update_playlist(id, name=nil, track_ids=[])
|
46
|
+
data = {}
|
47
|
+
data["tracks"] = track_ids.map { |id| { 'id' => id } } unless tracks_ids.empty?
|
48
|
+
data["name"] = name if name
|
49
|
+
|
50
|
+
resp = put("/playlists/#{id}", :body => data.to_json)
|
51
|
+
raise resp.inspect if resp['status'] != 'OK'
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.create_playlist(name, track_ids=[])
|
55
|
+
resp = post("/playlists", :body => {
|
56
|
+
:name => name,
|
57
|
+
:tracks => track_ids.map { |id| { 'id' => id } }
|
58
|
+
}.to_json)
|
59
|
+
|
60
|
+
if resp.code == 201
|
61
|
+
location = resp.headers['location']
|
62
|
+
"201 created (#{location})"
|
63
|
+
else
|
64
|
+
raise resp.inspect
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
if __FILE__ == $0
|
70
|
+
if ARGV.empty?
|
71
|
+
STDERR.puts "#{$0} [list|create|search]"
|
72
|
+
exit(1)
|
73
|
+
end
|
74
|
+
|
75
|
+
pp case cmd = ARGV.shift
|
76
|
+
when "list"
|
77
|
+
Spotify.playlists
|
78
|
+
when "create"
|
79
|
+
raise ArgumentError, "#{$0} create <name>" unless name = ARGV.shift
|
80
|
+
Spotify.create_playlist(name)
|
81
|
+
when "search"
|
82
|
+
raise ArgumentError, "#{$0} search <what>" unless what = ARGV.shift
|
83
|
+
Spotify.albums(what)
|
84
|
+
else raise ArgumentError, "invalid command: #{cmd}"
|
85
|
+
end
|
86
|
+
end
|
data/lib/jars/jotify.jar
ADDED
Binary file
|
data/lib/jotify.rb
ADDED
@@ -0,0 +1,128 @@
|
|
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, resolve_tracks=false)
|
53
|
+
playlist = @jotify.playlist(Jotify.resolve_id(id))
|
54
|
+
if resolve_tracks && !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
|
+
raise ArgumentError, "need name" unless name
|
65
|
+
|
66
|
+
playlist = @jotify.playlistCreate(name, collaborative)
|
67
|
+
return nil unless playlist
|
68
|
+
add_playlist(playlist)
|
69
|
+
playlist
|
70
|
+
end
|
71
|
+
|
72
|
+
def add_playlist(id)
|
73
|
+
@jotify.playlistsAddPlaylist(@jotify.playlists, id.is_a?(Media::Playlist) ? id : playlist(id))
|
74
|
+
end
|
75
|
+
|
76
|
+
def rename_playlist(playlist, name)
|
77
|
+
@jotify.playlistRename(playlist, name)
|
78
|
+
end
|
79
|
+
|
80
|
+
def set_collaborative_flag(playlist, flag)
|
81
|
+
@jotify.playlistSetCollaborative(playlist, flag)
|
82
|
+
end
|
83
|
+
|
84
|
+
def set_tracks_on_playlist(playlist, track_ids)
|
85
|
+
#puts "playlist: checksum #{playlist.getChecksum()}"
|
86
|
+
tracks = Java::JavaUtil::ArrayList.new
|
87
|
+
track_ids.each { |id| tracks.add(Media::Track.new(Jotify.resolve_id(id))) }
|
88
|
+
|
89
|
+
# delete old tracks
|
90
|
+
if playlist.tracks.size > 0
|
91
|
+
raise "could not remove tracks" unless @jotify.playlistRemoveTracks(playlist, 0, playlist.tracks.size)
|
92
|
+
end
|
93
|
+
|
94
|
+
return true if track_ids.empty?
|
95
|
+
|
96
|
+
@jotify.playlistAddTracks(playlist, tracks, playlist.tracks.size)
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.resolve_id(id)
|
100
|
+
case id
|
101
|
+
when /\Ahttp:\/\/open\.spotify\.com/: SpotifyURI.to_hex(id[id.rindex('/')+1..-1])
|
102
|
+
when /spotify:/: SpotifyURI.to_hex(id[id.rindex(':')+1..-1])
|
103
|
+
when /\A[0-9a-f]{32}\Z/: id
|
104
|
+
when /\A[a-zA-Z0-9]{22}\Z/: SpotifyURI.to_hex(id)
|
105
|
+
else
|
106
|
+
raise "invalid id: #{id}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.credentials
|
111
|
+
prefs = JotifyPreferences.getInstance()
|
112
|
+
prefs.load()
|
113
|
+
{
|
114
|
+
:username => prefs.getString("login.username"),
|
115
|
+
:password => prefs.getString("login.password")
|
116
|
+
}
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.credentials=(creds)
|
120
|
+
prefs = JotifyPreferences.getInstance()
|
121
|
+
prefs.load()
|
122
|
+
prefs.setString("login.username", creds[:username])
|
123
|
+
prefs.setString("login.password", creds[:password])
|
124
|
+
prefs.save() or raise "could not save login details"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
require File.expand_path(File.dirname(__FILE__) + '/jotify/media')
|