hallon 0.14.0 → 0.15.0
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/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
|