scrobbler-ng 2.0.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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))
|