hallon 0.14.0 → 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +21 -1
- data/README.markdown +64 -10
- data/dev/login.rb +2 -13
- data/examples/adding_tracks_to_playlist.rb +32 -30
- data/examples/example_support.rb +100 -0
- data/examples/playing_audio.rb +11 -47
- data/examples/show_published_playlists_of_user.rb +18 -46
- data/hallon.gemspec +2 -1
- data/lib/hallon.rb +5 -4
- data/lib/hallon/album.rb +1 -1
- data/lib/hallon/artist.rb +1 -1
- data/lib/hallon/base.rb +3 -3
- data/lib/hallon/ext/spotify.rb +1 -1
- data/lib/hallon/image.rb +1 -1
- data/lib/hallon/link.rb +3 -1
- data/lib/hallon/linkable.rb +103 -106
- data/lib/hallon/loadable.rb +1 -0
- data/lib/hallon/observable.rb +4 -2
- data/lib/hallon/observable/session.rb +13 -0
- data/lib/hallon/playlist.rb +21 -1
- data/lib/hallon/search.rb +28 -39
- data/lib/hallon/session.rb +7 -6
- data/lib/hallon/track.rb +1 -1
- data/lib/hallon/user.rb +1 -1
- data/lib/hallon/version.rb +1 -1
- data/spec/hallon/album_spec.rb +1 -1
- data/spec/hallon/hallon_spec.rb +4 -6
- data/spec/hallon/link_spec.rb +4 -0
- data/spec/hallon/linkable_spec.rb +1 -1
- data/spec/hallon/observable_spec.rb +9 -0
- data/spec/hallon/player_spec.rb +4 -3
- data/spec/hallon/playlist_spec.rb +11 -0
- data/spec/hallon/search_spec.rb +19 -39
- data/spec/hallon/session_spec.rb +7 -0
- data/spec/hallon/user_spec.rb +1 -1
- data/spec/mockspotify.rb +6 -4
- data/spec/spec_helper.rb +3 -0
- data/spec/support/common_objects.rb +18 -10
- metadata +29 -17
- data/examples/logging_in.rb +0 -16
- data/examples/printing_link_information.rb +0 -30
@@ -1,62 +1,34 @@
|
|
1
1
|
# coding: utf-8
|
2
|
-
#
|
3
|
-
# DISCLAIMER:
|
4
|
-
# This file was written without extensive testing, and is merely an
|
5
|
-
# example. Before using this yourself, I advice you to look through
|
6
|
-
# the code carefully.
|
7
|
-
#
|
8
2
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
require './spec/support/config'
|
13
|
-
|
14
|
-
# Utility
|
15
|
-
def prompt(str)
|
16
|
-
print str
|
17
|
-
gets.chomp
|
18
|
-
end
|
19
|
-
|
20
|
-
session = Hallon::Session.initialize IO.read(ENV['HALLON_APPKEY']) do
|
21
|
-
on(:log_message) do |message|
|
22
|
-
puts "[LOG] #{message}"
|
23
|
-
end
|
24
|
-
|
25
|
-
on(:connection_error) do |error|
|
26
|
-
Hallon::Error.maybe_raise(error)
|
27
|
-
end
|
28
|
-
|
29
|
-
on(:logged_out) do
|
30
|
-
abort "[FAIL] Logged out!"
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
session.login!(ENV['HALLON_USERNAME'], ENV['HALLON_PASSWORD'])
|
35
|
-
|
36
|
-
puts "Successfully logged in!"
|
3
|
+
# Require support code, used by all the examples.
|
4
|
+
require_relative 'example_support'
|
5
|
+
session = Hallon::Session.instance
|
37
6
|
|
38
7
|
while username = prompt("Enter a Spotify username: ")
|
39
8
|
begin
|
40
|
-
puts "
|
41
|
-
|
42
|
-
session.wait_for { published.loaded? }
|
9
|
+
puts "Loading #{username}."
|
10
|
+
user = Hallon::User.new(username)
|
43
11
|
|
44
|
-
puts "
|
45
|
-
published.
|
46
|
-
next if playlist.nil? # folder or somesuch
|
12
|
+
puts "Fetching published playlists for #{username}..."
|
13
|
+
published = user.published.load
|
47
14
|
|
48
|
-
|
15
|
+
puts "Loading #{published.size} playlists."
|
16
|
+
all_playlists = published.contents.find_all do |playlist|
|
17
|
+
playlist.is_a?(Hallon::Playlist) # ignore folders
|
18
|
+
end
|
49
19
|
|
50
|
-
|
51
|
-
puts playlist.name << ": "
|
20
|
+
all_playlists.each(&:load)
|
52
21
|
|
53
|
-
|
54
|
-
|
22
|
+
all_playlists.each do |playlist|
|
23
|
+
puts
|
24
|
+
puts "Listing tracks for #{playlist.name} (#{playlist.to_str}):"
|
55
25
|
|
26
|
+
tracks = playlist.tracks.to_a.map(&:load)
|
27
|
+
tracks.each_with_index do |track, i|
|
56
28
|
puts "\t (#{i+1}/#{playlist.size}) #{track.name}"
|
57
29
|
end
|
58
30
|
end
|
59
31
|
rescue Interrupt
|
60
|
-
|
32
|
+
puts "Interrupted!"
|
61
33
|
end
|
62
34
|
end
|
data/hallon.gemspec
CHANGED
@@ -22,9 +22,10 @@ Gem::Specification.new do |gem|
|
|
22
22
|
gem.required_ruby_version = '>= 1.9'
|
23
23
|
|
24
24
|
gem.add_dependency 'ref', '~> 1.0'
|
25
|
-
gem.add_dependency 'spotify', '~>
|
25
|
+
gem.add_dependency 'spotify', '~> 11.0.2'
|
26
26
|
gem.add_development_dependency 'rake', '~> 0.8'
|
27
27
|
gem.add_development_dependency 'rspec', '~> 2'
|
28
28
|
gem.add_development_dependency 'yard'
|
29
|
+
gem.add_development_dependency 'bundler'
|
29
30
|
gem.add_development_dependency 'rdiscount'
|
30
31
|
end
|
data/lib/hallon.rb
CHANGED
@@ -59,14 +59,15 @@ module Hallon
|
|
59
59
|
# @example
|
60
60
|
# Hallon::URI === "spotify:user:burgestrand" # => true
|
61
61
|
URI = /(spotify:(?:
|
62
|
-
(?:artist|album|
|
63
|
-
|
|
62
|
+
(?:artist|album|user:[^:]+:playlist):[a-zA-Z0-9]{22}
|
63
|
+
|track:[a-zA-Z0-9]{22}(?:\#\d{1,2}:\d{1,2})?
|
64
|
+
|user:[^:]+(?::starred)?
|
64
65
|
|search:(?:[-\w$\.+!*'(),]+|%[a-fA-F0-9]{2})+
|
65
66
|
|image:[a-fA-F0-9]{40}
|
66
67
|
))
|
67
68
|
/x
|
68
69
|
|
69
|
-
# Thrown by {Loadable#load} on failure.
|
70
|
+
# Thrown by {Loadable#load} and {Playlist#update} on failure.
|
70
71
|
TimeoutError = Class.new(Hallon::Error)
|
71
72
|
|
72
73
|
class << self
|
@@ -83,5 +84,5 @@ module Hallon
|
|
83
84
|
end
|
84
85
|
end
|
85
86
|
|
86
|
-
self.load_timeout =
|
87
|
+
self.load_timeout = 31.4159 # seconds
|
87
88
|
end
|
data/lib/hallon/album.rb
CHANGED
data/lib/hallon/artist.rb
CHANGED
data/lib/hallon/base.rb
CHANGED
@@ -1,6 +1,4 @@
|
|
1
1
|
# coding: utf-8
|
2
|
-
require 'timeout'
|
3
|
-
|
4
2
|
module Hallon
|
5
3
|
# All objects in Hallon are mere representations of Spotify objects.
|
6
4
|
# Hallon::Base covers basic functionality shared by all of these.
|
@@ -41,6 +39,7 @@ module Hallon
|
|
41
39
|
end
|
42
40
|
|
43
41
|
private
|
42
|
+
|
44
43
|
# @macro [attach] to_link
|
45
44
|
# @method to_link
|
46
45
|
# @scope instance
|
@@ -72,7 +71,8 @@ module Hallon
|
|
72
71
|
#
|
73
72
|
# @param [Spotify::Pointer, String, Link] resource
|
74
73
|
# @return [Spotify::Pointer]
|
75
|
-
# @raise [TypeError] when pointer
|
74
|
+
# @raise [TypeError] when the pointer is of the wrong type
|
75
|
+
# @raise [ArgumentError] when pointer could not be created, or null
|
76
76
|
def to_pointer(resource, type, *args)
|
77
77
|
if resource.is_a?(FFI::Pointer) and not resource.is_a?(Spotify::Pointer)
|
78
78
|
raise TypeError, "Hallon does not support raw FFI::Pointers, wrap it in a Spotify::Pointer"
|
data/lib/hallon/ext/spotify.rb
CHANGED
@@ -23,6 +23,7 @@ module Spotify
|
|
23
23
|
# @raise [NoMethodError] if `function` is not defined
|
24
24
|
# @see Spotify::Pointer
|
25
25
|
def self.wrap_function(function, return_type)
|
26
|
+
method(function)
|
26
27
|
define_singleton_method("#{function}!") do |*args|
|
27
28
|
pointer = public_send(function, *args)
|
28
29
|
Spotify::Pointer.new(pointer, return_type, function !~ /create/)
|
@@ -81,7 +82,6 @@ module Spotify
|
|
81
82
|
wrap_function :link_create_from_user, :link
|
82
83
|
|
83
84
|
wrap_function :search_create, :search
|
84
|
-
wrap_function :radio_search_create, :search
|
85
85
|
wrap_function :search_track, :track
|
86
86
|
wrap_function :search_album, :album
|
87
87
|
wrap_function :search_artist, :artist
|
data/lib/hallon/image.rb
CHANGED
data/lib/hallon/link.rb
CHANGED
@@ -11,6 +11,8 @@ module Hallon
|
|
11
11
|
def self.valid?(spotify_uri)
|
12
12
|
if spotify_uri.is_a?(Link)
|
13
13
|
return true
|
14
|
+
elsif spotify_uri.to_s["\x00"] # image ids
|
15
|
+
return false
|
14
16
|
end
|
15
17
|
|
16
18
|
link = Spotify.link_create_from_string!(spotify_uri.to_s)
|
@@ -54,7 +56,7 @@ module Hallon
|
|
54
56
|
def to_str(length = length)
|
55
57
|
FFI::Buffer.alloc_out(length + 1) do |b|
|
56
58
|
Spotify.link_as_string(pointer, b, b.size)
|
57
|
-
return b.get_string(0)
|
59
|
+
return b.get_string(0).force_encoding("UTF-8")
|
58
60
|
end
|
59
61
|
end
|
60
62
|
|
data/lib/hallon/linkable.rb
CHANGED
@@ -8,123 +8,120 @@ module Hallon
|
|
8
8
|
#
|
9
9
|
# @private
|
10
10
|
module Linkable
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
11
|
+
module ClassMethods
|
12
|
+
# Defines `#from_link`, used in converting a link to a pointer. You
|
13
|
+
# can either pass it a `method_name`, or a `type` and a block.
|
14
|
+
#
|
15
|
+
# @overload from_link(method_name)
|
16
|
+
# Define `#from_link` simply by giving the name of the method,
|
17
|
+
# minus the `link_` prefix.
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# class Album
|
21
|
+
# include Linkable
|
22
|
+
#
|
23
|
+
# from_link :as_album # => Spotify.link_as_album(pointer, *args)
|
24
|
+
# # ^ is roughly equivalent to:
|
25
|
+
# def from_link(link, *args)
|
26
|
+
# unless Spotify::Pointer.typechecks?(link, :link)
|
27
|
+
# link = Link.new(link).pointer(:album)
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# Spotify.link_as_album!(link)
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# @param [Symbol] method_name
|
35
|
+
#
|
36
|
+
# @overload from_link(type) { |*args| … }
|
37
|
+
# Define `#from_link` to use the given block to convert an object
|
38
|
+
# from a link. The link is converted to a pointer and typechecked
|
39
|
+
# to be of the same type as `type` before given to the block.
|
40
|
+
#
|
41
|
+
# @example
|
42
|
+
# class User
|
43
|
+
# include Linkable
|
44
|
+
#
|
45
|
+
# from_link :profile do |pointer|
|
46
|
+
# Spotify.link_as_user!(pointer)
|
47
|
+
# end
|
48
|
+
# # ^ is roughly equivalent to:
|
49
|
+
# def from_link(link, *args)
|
50
|
+
# unless Spotify::Pointer.typechecks?(link, :link)
|
51
|
+
# link = Link.new(link).pointer(:profile)
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# Spotify.link_as_user!(link)
|
55
|
+
# end
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# @param [#to_s] type link type
|
59
|
+
# @yield [link, *args] called when conversion is needed from Link pointer
|
60
|
+
# @yieldparam [Spotify::Pointer] link
|
61
|
+
# @yieldparam *args any extra arguments given to `#from_link`
|
62
|
+
#
|
63
|
+
# @note Private API. You probably do not need to care about this method.
|
64
|
+
def from_link(as_object, &block)
|
65
|
+
block ||= Spotify.method(:"link_#{as_object}!")
|
66
|
+
type = as_object.to_s[/^(as_)?([^_]+)/, 2].to_sym
|
67
|
+
|
68
|
+
define_method(:from_link) do |link, *args|
|
69
|
+
unless Spotify::Pointer.typechecks?(link, :link)
|
70
|
+
link = Link.new(link).pointer(type)
|
71
|
+
end
|
66
72
|
|
67
|
-
|
68
|
-
unless Spotify::Pointer.typechecks?(link, :link)
|
69
|
-
link = Link.new(link).pointer(type)
|
73
|
+
instance_exec(link, *args, &block)
|
70
74
|
end
|
71
75
|
|
72
|
-
|
76
|
+
private :from_link
|
73
77
|
end
|
74
78
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
link = Spotify.__send__(:"link_create_#{cmethod}!", pointer, *args)
|
97
|
-
Link.from(link)
|
79
|
+
# Defines `#to_link` method, used in converting the object to a {Link}.
|
80
|
+
#
|
81
|
+
# @example
|
82
|
+
# class Artist
|
83
|
+
# include Linkable
|
84
|
+
#
|
85
|
+
# to_link :from_artist
|
86
|
+
# # ^ is the same as:
|
87
|
+
# def to_link(*args)
|
88
|
+
# link = Spotify.link_create_from_artist!(pointer, *args)
|
89
|
+
# Link.new(link)
|
90
|
+
# end
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# @param [Symbol] cmethod name of the C method, say `from_artist` in `Spotify.link_create_from_artist`.
|
94
|
+
# @return [Link, nil]
|
95
|
+
def to_link(cmethod)
|
96
|
+
define_method(:to_link) do |*args|
|
97
|
+
link = Spotify.__send__(:"link_create_#{cmethod}!", pointer, *args)
|
98
|
+
Link.from(link)
|
99
|
+
end
|
98
100
|
end
|
99
101
|
end
|
100
102
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
def self.extended(other)
|
105
|
-
other.send(:include, InstanceMethods)
|
103
|
+
def self.included(other)
|
104
|
+
other.extend ClassMethods
|
106
105
|
end
|
107
106
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
end
|
107
|
+
# Converts the Linkable first to a Link, and then that link to a String.
|
108
|
+
#
|
109
|
+
# @note Returns an empty string if the #to_link call fails.
|
110
|
+
# @return [String]
|
111
|
+
def to_str
|
112
|
+
link = to_link
|
113
|
+
link &&= link.to_str
|
114
|
+
link.to_s
|
115
|
+
end
|
118
116
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
end
|
117
|
+
# Compare the Linkable to other. If other is a Linkable, also
|
118
|
+
# compare their `to_link` if necessary.
|
119
|
+
#
|
120
|
+
# @param [Object] other
|
121
|
+
# @return [Boolean]
|
122
|
+
def ===(other)
|
123
|
+
super or if other.respond_to?(:to_link)
|
124
|
+
to_link == other.to_link
|
128
125
|
end
|
129
126
|
end
|
130
127
|
end
|
data/lib/hallon/loadable.rb
CHANGED
data/lib/hallon/observable.rb
CHANGED
@@ -101,12 +101,14 @@ module Hallon
|
|
101
101
|
# Defines a handler for the given event.
|
102
102
|
#
|
103
103
|
# @param [#to_s] event name of event to handle
|
104
|
-
# @return [Proc] the
|
104
|
+
# @return [Proc] the previous handler
|
105
105
|
# @yield (*args) event handler block
|
106
106
|
def on(event, &block)
|
107
107
|
raise ArgumentError, "no block given" unless block
|
108
108
|
raise NameError, "no such callback: #{event}" unless has_callback?(event)
|
109
|
-
handlers[event.to_s]
|
109
|
+
handlers[event.to_s].tap do
|
110
|
+
handlers[event.to_s] = block
|
111
|
+
end
|
110
112
|
end
|
111
113
|
|
112
114
|
# @param [#to_s] name
|