scrobbler-ng 2.0.0 → 2.0.1
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/VERSION.yml +2 -1
- data/lib/scrobbler/album.rb +91 -113
- data/lib/scrobbler/artist.rb +174 -134
- data/lib/scrobbler/auth.rb +12 -0
- data/lib/scrobbler/base.rb +114 -64
- data/lib/scrobbler/basexml.rb +17 -0
- data/lib/scrobbler/event.rb +109 -74
- data/lib/scrobbler/geo.rb +3 -11
- data/lib/scrobbler/helper/image.rb +37 -12
- data/lib/scrobbler/helper/streamable.rb +11 -3
- data/lib/scrobbler/library.rb +49 -75
- data/lib/scrobbler/playlist.rb +74 -32
- data/lib/scrobbler/radio.rb +11 -0
- data/lib/scrobbler/shout.rb +40 -14
- data/lib/scrobbler/tag.rb +19 -2
- data/lib/scrobbler/track.rb +62 -67
- data/lib/scrobbler/user.rb +93 -111
- data/lib/scrobbler.rb +1 -2
- data/tasks/jeweler.rake +10 -6
- data/tasks/metric_fu.rake +27 -0
- data/tasks/reek.rake +13 -0
- data/tasks/roodi.rake +13 -0
- data/tasks/tests.rake +8 -0
- data/test/mocks/library.rb +35 -0
- data/test/mocks/rest.rb +91 -189
- data/test/test_helper.rb +2 -16
- data/test/unit/album_spec.rb +1 -1
- data/test/unit/artist_spec.rb +4 -2
- data/test/unit/event_spec.rb +2 -2
- data/test/unit/library_spec.rb +70 -70
- data/test/unit/playlist_spec.rb +1 -1
- data/test/unit/scrobble_spec.rb +1 -1
- data/test/unit/tag_spec.rb +3 -1
- data/test/unit/track_spec.rb +1 -1
- data/test/unit/user_spec.rb +1 -1
- metadata +38 -6
data/lib/scrobbler/library.rb
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require File.expand_path('base.rb', File.dirname(__FILE__))
|
4
|
+
|
1
5
|
module Scrobbler
|
2
6
|
# Class for invocation of library.* API functions.
|
3
7
|
class Library < Base
|
@@ -9,13 +13,13 @@ module Scrobbler
|
|
9
13
|
# is requested.
|
10
14
|
# @raise ArgumentError If a not supported type is given as user.
|
11
15
|
def initialize(user)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
super()
|
17
|
+
if user.kind_of?(Scrobbler::User)
|
18
|
+
@user = user
|
19
|
+
elsif user.kind_of?(String)
|
20
|
+
@user = Scrobbler::User.new(:name => user.to_s)
|
21
|
+
else
|
22
|
+
raise ArgumentError, "Invalid argument for user: #{user.class}"
|
19
23
|
end
|
20
24
|
end
|
21
25
|
|
@@ -37,85 +41,55 @@ module Scrobbler
|
|
37
41
|
# A list of all the albums in a user's library, with play counts and tag
|
38
42
|
# counts.
|
39
43
|
def albums(options={})
|
40
|
-
|
41
|
-
options[:user] = @user.name
|
42
|
-
albums = []
|
43
|
-
if options[:all]
|
44
|
-
doc = Base.request('library.getalbums', options)
|
45
|
-
root = nil
|
46
|
-
doc.root.children.each do |child|
|
47
|
-
next unless child.name == 'albums'
|
48
|
-
root = child
|
49
|
-
end
|
50
|
-
total_pages = root['totalPages'].to_i
|
51
|
-
root.children.each do |child|
|
52
|
-
next unless child.name == 'album'
|
53
|
-
albums << Scrobbler::Album.new_from_libxml(child)
|
54
|
-
end
|
55
|
-
for i in 2..total_pages do
|
56
|
-
options[:page] = i
|
57
|
-
albums.concat get_response('library.getalbums', :none, 'albums', 'album', options, true)
|
58
|
-
end
|
59
|
-
else
|
60
|
-
albums = get_response('library.getalbums', :get_albums, 'albums', 'album', options, true)
|
61
|
-
end
|
62
|
-
albums
|
44
|
+
request_library('library.getalbums', :albums, Album, options)
|
63
45
|
end
|
64
46
|
|
65
47
|
# A list of all the artists in a user's library, with play counts and tag
|
66
48
|
# counts.
|
49
|
+
#
|
50
|
+
# @param [Hash<Symbol>] options The options to configure this API call.
|
51
|
+
# @return [Array<Scrobbler::Artist>] The artists included in this library.
|
67
52
|
def artists(options={})
|
68
|
-
|
69
|
-
options[:user] = @user.name
|
70
|
-
artists = []
|
71
|
-
if options[:all]
|
72
|
-
doc = Base.request('library.getartists', options)
|
73
|
-
root = nil
|
74
|
-
doc.root.children.each do |child|
|
75
|
-
next unless child.name == 'artists'
|
76
|
-
root = child
|
77
|
-
end
|
78
|
-
total_pages = root['totalPages'].to_i
|
79
|
-
root.children.each do |child|
|
80
|
-
next unless child.name == 'artist'
|
81
|
-
artists << Scrobbler::Artist.new_from_libxml(child)
|
82
|
-
end
|
83
|
-
for i in 2..total_pages do
|
84
|
-
options[:page] = i
|
85
|
-
artists.concat get_response('library.getartists', :none, 'artists', 'artist', options, true)
|
86
|
-
end
|
87
|
-
else
|
88
|
-
artists = get_response('library.getartists', :get_albums, 'artists', 'artist', options, true)
|
89
|
-
end
|
90
|
-
artists
|
53
|
+
request_library('library.getartists', :artists, Artist, options)
|
91
54
|
end
|
92
55
|
|
93
56
|
# A list of all the tracks in a user's library, with play counts and tag
|
94
57
|
# counts.
|
95
58
|
def tracks(options={})
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
59
|
+
request_library('library.gettracks', :tracks, Track, options)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Generic request method for the most Library funtions
|
63
|
+
#
|
64
|
+
# @param [String,Symbol] api_method The method which shall be called.
|
65
|
+
# @param [Hash] options The parameters passed as URL params.
|
66
|
+
# @param [String,Symbol] parent the parent XML node to look for.
|
67
|
+
# @param [Class] element The xml node name which shall be converted
|
68
|
+
# into an object.
|
69
|
+
# @return [Array]
|
70
|
+
def request_library(method, parent, element, options={})
|
71
|
+
options = {:all => true, :user => @user.name}.merge options
|
72
|
+
result = []
|
73
|
+
if options.delete(:all)
|
74
|
+
doc = Base.request(method, options)
|
75
|
+
root = nil
|
76
|
+
doc.root.children.each do |child|
|
77
|
+
next unless child.name == parent.to_s
|
78
|
+
root = child
|
79
|
+
end
|
80
|
+
total_pages = root['totalPages'].to_i
|
81
|
+
root.children.each do |child|
|
82
|
+
next unless child.name == element.to_s.sub("Scrobbler::","").downcase
|
83
|
+
result << element.new_from_libxml(child)
|
117
84
|
end
|
118
|
-
|
85
|
+
(2..total_pages).each do |i|
|
86
|
+
options[:page] = i
|
87
|
+
result.concat call(method, parent, element, options)
|
88
|
+
end
|
89
|
+
else
|
90
|
+
result = call(method, parent, element, options)
|
91
|
+
end
|
92
|
+
result
|
119
93
|
end
|
120
94
|
|
121
95
|
end
|
data/lib/scrobbler/playlist.rb
CHANGED
@@ -1,45 +1,87 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require File.expand_path('basexml.rb', File.dirname(__FILE__))
|
4
|
+
|
1
5
|
module Scrobbler
|
2
|
-
|
3
|
-
class Playlist < Base
|
6
|
+
class Playlist < BaseXml
|
4
7
|
# Load Helper modules
|
5
8
|
include ImageObjectFuncs
|
6
|
-
|
9
|
+
include Scrobbler::StreamableObjectFuncs
|
7
10
|
|
8
11
|
attr_reader :url, :id, :title, :date, :creator
|
9
12
|
attr_reader :description, :size, :duration, :streamable
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
maybe_image_node(data, child)
|
19
|
-
data[:date] = Time.parse(child.content) if child.name == 'date'
|
13
|
+
|
14
|
+
# Alias for Playlist.new(:xml => xml)
|
15
|
+
#
|
16
|
+
# @deprecated
|
17
|
+
def self.new_from_libxml(xml)
|
18
|
+
Playlist.new(:xml => xml)
|
19
|
+
end
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
def self.create
|
22
|
+
# This function require authentication, but SimpleAuth is not yet 2.0
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
end
|
35
|
-
Playlist.new(data[:url],data)
|
36
|
-
end
|
26
|
+
def initialize(data={})
|
27
|
+
raise ArgumentError unless data.kind_of?(Hash)
|
28
|
+
super(data)
|
29
|
+
data = {:include_info => false}.merge(data)
|
30
|
+
# Load data given as method-parameter
|
31
|
+
load_info() if data.delete(:include_info)
|
32
|
+
populate_data(data)
|
33
|
+
|
34
|
+
raise ArgumentError, "Url is required" if @url.empty?
|
37
35
|
end
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
36
|
+
|
37
|
+
# Load the data for this object out of a XML-Node
|
38
|
+
#
|
39
|
+
# @param [LibXML::XML::Node] node The XML node containing the information
|
40
|
+
# @return [nil]
|
41
|
+
def load_from_xml(node)
|
42
|
+
# Get all information from the root's children nodes
|
43
|
+
node.children.each do |child|
|
44
|
+
case child.name.to_s
|
45
|
+
when 'id'
|
46
|
+
@id = child.content.to_i
|
47
|
+
when 'title'
|
48
|
+
@title = child.content
|
49
|
+
when 'image'
|
50
|
+
check_image_node(child)
|
51
|
+
when 'date'
|
52
|
+
@date = Time.parse(child.content)
|
53
|
+
when 'streamable'
|
54
|
+
check_streamable_node(child)
|
55
|
+
when 'size'
|
56
|
+
@size = child.content.to_i
|
57
|
+
when 'description'
|
58
|
+
@description = child.content
|
59
|
+
when 'duration'
|
60
|
+
@duration = child.content.to_i
|
61
|
+
when 'creator'
|
62
|
+
@creator = child.content
|
63
|
+
when 'url'
|
64
|
+
@url = child.content
|
65
|
+
when 'text'
|
66
|
+
# ignore, these are only blanks
|
67
|
+
when '#text'
|
68
|
+
# libxml-jruby version of blanks
|
69
|
+
else
|
70
|
+
raise NotImplementedError, "Field '#{child.name}' not known (#{child.content})"
|
71
|
+
end #^ case
|
72
|
+
end #^ do |child|
|
73
|
+
end
|
74
|
+
|
75
|
+
def add_track
|
76
|
+
# This function require authentication, but SimpleAuth is not yet 2.0
|
77
|
+
raise NotImplementedError
|
42
78
|
end
|
79
|
+
|
80
|
+
def fetch
|
81
|
+
# This function require authentication, but SimpleAuth is not yet 2.0
|
82
|
+
raise NotImplementedError
|
83
|
+
end
|
84
|
+
|
43
85
|
end
|
44
86
|
end
|
45
87
|
|
data/lib/scrobbler/radio.rb
CHANGED
@@ -6,6 +6,17 @@ module Scrobbler
|
|
6
6
|
def initialize(station)
|
7
7
|
@station = station
|
8
8
|
end
|
9
|
+
|
10
|
+
def tune
|
11
|
+
# This function require authentication, but SimpleAuth is not yet 2.0
|
12
|
+
raise NotImplementedError
|
13
|
+
end
|
14
|
+
|
15
|
+
def playlist
|
16
|
+
# This function require authentication, but SimpleAuth is not yet 2.0
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
|
9
20
|
|
10
21
|
end
|
11
22
|
end
|
data/lib/scrobbler/shout.rb
CHANGED
@@ -1,21 +1,47 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require File.expand_path('basexml.rb', File.dirname(__FILE__))
|
4
|
+
|
1
5
|
module Scrobbler
|
2
|
-
class Shout <
|
6
|
+
class Shout < BaseXml
|
3
7
|
attr_reader :author, :date, :body
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
data[:author] = Scrobbler::User.new(child.content) if child.name == 'author'
|
11
|
-
data[:date] = Time.parse(child.content) if child.name == 'date'
|
12
|
-
end
|
13
|
-
Shout.new(data)
|
14
|
-
end
|
8
|
+
|
9
|
+
# Alias for Shout.new(:xml => xml)
|
10
|
+
#
|
11
|
+
# @deprecated
|
12
|
+
def self.new_from_libxml(xml)
|
13
|
+
Shout.new(:xml => xml)
|
15
14
|
end
|
16
15
|
|
17
|
-
def initialize(
|
18
|
-
|
16
|
+
def initialize(data={})
|
17
|
+
raise ArgumentError unless data.kind_of?(Hash)
|
18
|
+
super(data)
|
19
|
+
# Load data given as method-parameter
|
20
|
+
populate_data(data)
|
19
21
|
end
|
22
|
+
|
23
|
+
# Load the data for this object out of a XML-Node
|
24
|
+
#
|
25
|
+
# @param [LibXML::XML::Node] node The XML node containing the information
|
26
|
+
# @return [nil]
|
27
|
+
def load_from_xml(node)
|
28
|
+
# Get all information from the root's children nodes
|
29
|
+
node.children.each do |child|
|
30
|
+
case child.name
|
31
|
+
when 'body'
|
32
|
+
@body = child.content
|
33
|
+
when 'author'
|
34
|
+
@author = Scrobbler::User.new(:name => child.content)
|
35
|
+
when 'date'
|
36
|
+
@date = Time.parse(child.content)
|
37
|
+
when 'text'
|
38
|
+
# ignore, these are only blanks
|
39
|
+
when '#text'
|
40
|
+
# libxml-jruby version of blanks
|
41
|
+
else
|
42
|
+
raise NotImplementedError, "Field '#{child.name}' not known (#{child.content})"
|
43
|
+
end #^ case
|
44
|
+
end #^ do |child|
|
45
|
+
end #^ load_from_xml
|
20
46
|
end
|
21
47
|
end
|
data/lib/scrobbler/tag.rb
CHANGED
@@ -99,15 +99,32 @@ module Scrobbler
|
|
99
99
|
get_response('tag.gettoptracks', :top_tracks, 'toptracks', 'track', {'tag'=>@name}, force)
|
100
100
|
end
|
101
101
|
|
102
|
-
def
|
103
|
-
|
102
|
+
def self.top_tags
|
103
|
+
Base.get('tag.gettoptags', :toptags, :tag)
|
104
104
|
end
|
105
105
|
|
106
|
+
def self.search
|
107
|
+
# This function require authentication, but SimpleAuth is not yet 2.0
|
108
|
+
raise NotImplementedError
|
109
|
+
end
|
110
|
+
|
111
|
+
|
106
112
|
# Search for tags similar to this one. Returns tags ranked by similarity,
|
107
113
|
# based on listening data.
|
108
114
|
def similar(force=false)
|
109
115
|
params = {:tag => @name}
|
110
116
|
get_response('tag.getsimilar', :similar, 'similartags', 'tag', params, force)
|
111
117
|
end
|
118
|
+
|
119
|
+
def weekly_artist_chart
|
120
|
+
# This function require authentication, but SimpleAuth is not yet 2.0
|
121
|
+
raise NotImplementedError
|
122
|
+
end
|
123
|
+
|
124
|
+
def weekly_chart_list
|
125
|
+
# This function require authentication, but SimpleAuth is not yet 2.0
|
126
|
+
raise NotImplementedError
|
127
|
+
end
|
128
|
+
|
112
129
|
end
|
113
130
|
end
|
data/lib/scrobbler/track.rb
CHANGED
@@ -1,78 +1,43 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
# track = Scrobbler::Track.new('Carrie Underwood', 'Before He Cheats')
|
4
|
-
# puts 'Fans'
|
5
|
-
# puts "=" * 4
|
6
|
-
# track.fans.each { |u| puts u.username }
|
7
|
-
#
|
8
|
-
# Which would output something like:
|
9
|
-
#
|
10
|
-
# track = Scrobbler::Track.new('Carrie Underwood', 'Before He Cheats')
|
11
|
-
# puts 'Fans'
|
12
|
-
# puts "=" * 4
|
13
|
-
# track.fans.each { |u| puts "(#{u.weight}) #{u.username}" }
|
14
|
-
#
|
15
|
-
# Fans
|
16
|
-
# ====
|
17
|
-
# (69163) PimpinRose
|
18
|
-
# (7225) selene204
|
19
|
-
# (7000) CelestiaLegends
|
20
|
-
# (6817) muehllr
|
21
|
-
# (5387) Mudley
|
22
|
-
# (5368) ilovejohnny1984
|
23
|
-
# (5232) MeganIAD
|
24
|
-
# (5132) Veric
|
25
|
-
# (5097) aeVnar
|
26
|
-
# (3390) kristaaan
|
27
|
-
# (3239) kelseaowns
|
28
|
-
# (2780) syndication
|
29
|
-
# (2735) mkumm
|
30
|
-
# (2706) Kimmybeebee
|
31
|
-
# (2648) skorpcroze
|
32
|
-
# (2549) mistergreg
|
33
|
-
# (2449) mlmjcace
|
34
|
-
# (2302) tiNEey
|
35
|
-
# (2169) ajsbabiegirl
|
1
|
+
# encoding: utf-8
|
2
|
+
|
36
3
|
module Scrobbler
|
37
4
|
class Track < Base
|
38
5
|
# Load Helper modules
|
39
6
|
include ImageObjectFuncs
|
40
7
|
extend ImageClassFuncs
|
41
8
|
|
42
|
-
|
43
|
-
|
44
|
-
|
9
|
+
attr_reader :artist, :name, :mbid, :playcount, :rank, :url, :id, :count
|
10
|
+
attr_reader :streamable, :album, :date, :now_playing, :tagcount
|
11
|
+
attr_reader :duration, :listeners
|
45
12
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
if
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
data[:streamable] = false
|
64
|
-
end
|
13
|
+
def self.new_from_libxml(xml)
|
14
|
+
data = {}
|
15
|
+
xml.children.each do |child|
|
16
|
+
data[:name] = child.content if child.name == 'name'
|
17
|
+
data[:mbid] = child.content if child.name == 'mbid'
|
18
|
+
data[:url] = child.content if child.name == 'url'
|
19
|
+
data[:date] = Time.parse(child.content) if child.name == 'date'
|
20
|
+
data[:artist] = Artist.new(:xml => child) if child.name == 'artist'
|
21
|
+
data[:album] = Album.new(:xml => child) if child.name == 'album'
|
22
|
+
data[:playcount] = child.content.to_i if child.name == 'playcount'
|
23
|
+
data[:tagcount] = child.content.to_i if child.name == 'tagcount'
|
24
|
+
maybe_image_node(data, child)
|
25
|
+
if child.name == 'streamable'
|
26
|
+
if ['1', 'true'].include?(child.content)
|
27
|
+
data[:streamable] = true
|
28
|
+
else
|
29
|
+
data[:streamable] = false
|
65
30
|
end
|
66
31
|
end
|
67
|
-
|
68
|
-
|
69
|
-
data[:rank] = xml['rank'].to_i if xml['rank']
|
70
|
-
data[:now_playing] = true if xml['nowplaying'] && xml['nowplaying'] == 'true'
|
71
|
-
|
72
|
-
data[:now_playing] = false if data[:now_playing].nil?
|
73
|
-
|
74
|
-
Track.new(data[:artist], data[:name], data)
|
75
32
|
end
|
33
|
+
|
34
|
+
|
35
|
+
data[:rank] = xml['rank'].to_i if xml['rank']
|
36
|
+
data[:now_playing] = true if xml['nowplaying'] && xml['nowplaying'] == 'true'
|
37
|
+
|
38
|
+
data[:now_playing] = false if data[:now_playing].nil?
|
39
|
+
|
40
|
+
Track.new(data[:artist], data[:name], data)
|
76
41
|
end
|
77
42
|
|
78
43
|
def initialize(artist, name, data={})
|
@@ -121,13 +86,43 @@ module Scrobbler
|
|
121
86
|
end
|
122
87
|
|
123
88
|
def top_fans(force=false)
|
124
|
-
|
89
|
+
call('track.gettopfans', :topfans, User, {:artist => @artist.name, :track => @name})
|
125
90
|
end
|
126
91
|
|
127
92
|
def top_tags(force=false)
|
128
|
-
|
93
|
+
call('track.gettoptags', :toptags, Tag, {:artist => @artist.name, :track => @name})
|
94
|
+
end
|
95
|
+
|
96
|
+
def similar
|
97
|
+
# This function require authentication, but SimpleAuth is not yet 2.0
|
98
|
+
raise NotImplementedError
|
99
|
+
end
|
100
|
+
|
101
|
+
def tags
|
102
|
+
# This function require authentication, but SimpleAuth is not yet 2.0
|
103
|
+
raise NotImplementedError
|
104
|
+
end
|
105
|
+
|
106
|
+
def love
|
107
|
+
# This function require authentication, but SimpleAuth is not yet 2.0
|
108
|
+
raise NotImplementedError
|
129
109
|
end
|
130
110
|
|
111
|
+
def remove_tag
|
112
|
+
# This function require authentication, but SimpleAuth is not yet 2.0
|
113
|
+
raise NotImplementedError
|
114
|
+
end
|
115
|
+
|
116
|
+
def search
|
117
|
+
# This function require authentication, but SimpleAuth is not yet 2.0
|
118
|
+
raise NotImplementedError
|
119
|
+
end
|
120
|
+
|
121
|
+
def share
|
122
|
+
# This function require authentication, but SimpleAuth is not yet 2.0
|
123
|
+
raise NotImplementedError
|
124
|
+
end
|
125
|
+
|
131
126
|
def ==(otherTrack)
|
132
127
|
if otherTrack.is_a?(Scrobbler::Track)
|
133
128
|
return ((@name == otherTrack.name) && (@artist == otherTrack.artist))
|