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.
- 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')
|