peteforde-scrobbler 0.2.3
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/History.txt +4 -0
- data/MIT-LICENSE +19 -0
- data/Manifest +67 -0
- data/README.rdoc +113 -0
- data/Rakefile +36 -0
- data/examples/album.rb +17 -0
- data/examples/artist.rb +13 -0
- data/examples/scrobble.rb +31 -0
- data/examples/tag.rb +11 -0
- data/examples/track.rb +10 -0
- data/examples/user.rb +14 -0
- data/lib/scrobbler.rb +20 -0
- data/lib/scrobbler/album.rb +143 -0
- data/lib/scrobbler/artist.rb +127 -0
- data/lib/scrobbler/base.rb +29 -0
- data/lib/scrobbler/chart.rb +31 -0
- data/lib/scrobbler/playing.rb +49 -0
- data/lib/scrobbler/rest.rb +51 -0
- data/lib/scrobbler/scrobble.rb +66 -0
- data/lib/scrobbler/simpleauth.rb +59 -0
- data/lib/scrobbler/tag.rb +104 -0
- data/lib/scrobbler/track.rb +98 -0
- data/lib/scrobbler/user.rb +192 -0
- data/lib/scrobbler/version.rb +3 -0
- data/scrobbler.gemspec +39 -0
- data/setup.rb +1585 -0
- data/test/fixtures/xml/album/info.xml +70 -0
- data/test/fixtures/xml/artist/fans.xml +18 -0
- data/test/fixtures/xml/artist/similar.xml +39 -0
- data/test/fixtures/xml/artist/topalbums.xml +36 -0
- data/test/fixtures/xml/artist/toptags.xml +18 -0
- data/test/fixtures/xml/artist/toptracks.xml +27 -0
- data/test/fixtures/xml/tag/topalbums.xml +39 -0
- data/test/fixtures/xml/tag/topartists.xml +39 -0
- data/test/fixtures/xml/tag/toptags.xml +252 -0
- data/test/fixtures/xml/tag/toptracks.xml +30 -0
- data/test/fixtures/xml/track/fans.xml +28 -0
- data/test/fixtures/xml/track/toptags.xml +33 -0
- data/test/fixtures/xml/user/friends.xml +21 -0
- data/test/fixtures/xml/user/neighbours.xml +18 -0
- data/test/fixtures/xml/user/profile.xml +12 -0
- data/test/fixtures/xml/user/recentbannedtracks.xml +24 -0
- data/test/fixtures/xml/user/recentlovedtracks.xml +24 -0
- data/test/fixtures/xml/user/recenttracks.xml +27 -0
- data/test/fixtures/xml/user/systemrecs.xml +18 -0
- data/test/fixtures/xml/user/topalbums.xml +42 -0
- data/test/fixtures/xml/user/topartists.xml +30 -0
- data/test/fixtures/xml/user/toptags.xml +18 -0
- data/test/fixtures/xml/user/toptracks.xml +27 -0
- data/test/fixtures/xml/user/weeklyalbumchart.xml +35 -0
- data/test/fixtures/xml/user/weeklyalbumchart_from_1138536002_to_1139140802.xml +35 -0
- data/test/fixtures/xml/user/weeklyartistchart.xml +38 -0
- data/test/fixtures/xml/user/weeklyartistchart_from_1138536002_to_1139140802.xml +59 -0
- data/test/fixtures/xml/user/weeklychartlist.xml +74 -0
- data/test/fixtures/xml/user/weeklytrackchart.xml +35 -0
- data/test/fixtures/xml/user/weeklytrackchart_from_1138536002_to_1139140802.xml +35 -0
- data/test/mocks/rest.rb +50 -0
- data/test/test_helper.rb +17 -0
- data/test/unit/album_test.rb +86 -0
- data/test/unit/artist_test.rb +82 -0
- data/test/unit/chart_test.rb +34 -0
- data/test/unit/playing_test.rb +53 -0
- data/test/unit/scrobble_test.rb +69 -0
- data/test/unit/simpleauth_test.rb +45 -0
- data/test/unit/tag_test.rb +65 -0
- data/test/unit/track_test.rb +42 -0
- data/test/unit/user_test.rb +291 -0
- metadata +177 -0
@@ -0,0 +1,127 @@
|
|
1
|
+
# Below are examples of how to find an artists top tracks and similar artists.
|
2
|
+
#
|
3
|
+
# artist = Scrobbler::Artist.new('Carrie Underwood')
|
4
|
+
#
|
5
|
+
# puts 'Top Tracks'
|
6
|
+
# puts "=" * 10
|
7
|
+
# artist.top_tracks.each { |t| puts "(#{t.reach}) #{t.name}" }
|
8
|
+
#
|
9
|
+
# puts
|
10
|
+
#
|
11
|
+
# puts 'Similar Artists'
|
12
|
+
# puts "=" * 15
|
13
|
+
# artist.similar.each { |a| puts "(#{a.match}%) #{a.name}" }
|
14
|
+
#
|
15
|
+
# Would output something similar to:
|
16
|
+
#
|
17
|
+
# Top Tracks
|
18
|
+
# ==========
|
19
|
+
# (8797) Before He Cheats
|
20
|
+
# (3574) Don't Forget to Remember Me
|
21
|
+
# (3569) Wasted
|
22
|
+
# (3246) Some Hearts
|
23
|
+
# (3142) Jesus, Take the Wheel
|
24
|
+
# (2600) Starts With Goodbye
|
25
|
+
# (2511) Jesus Take The Wheel
|
26
|
+
# (2423) Inside Your Heaven
|
27
|
+
# (2328) Lessons Learned
|
28
|
+
# (2040) I Just Can't Live a Lie
|
29
|
+
# (1899) Whenever You Remember
|
30
|
+
# (1882) We're Young and Beautiful
|
31
|
+
# (1854) That's Where It Is
|
32
|
+
# (1786) I Ain't in Checotah Anymore
|
33
|
+
# (1596) The Night Before (Life Goes On)
|
34
|
+
#
|
35
|
+
# Similar Artists
|
36
|
+
# ===============
|
37
|
+
# (100%) Rascal Flatts
|
38
|
+
# (84.985%) Keith Urban
|
39
|
+
# (84.007%) Kellie Pickler
|
40
|
+
# (82.694%) Katharine McPhee
|
41
|
+
# (81.213%) Martina McBride
|
42
|
+
# (79.397%) Faith Hill
|
43
|
+
# (77.121%) Tim McGraw
|
44
|
+
# (75.191%) Jessica Simpson
|
45
|
+
# (75.182%) Sara Evans
|
46
|
+
# (75.144%) The Wreckers
|
47
|
+
# (73.034%) Kenny Chesney
|
48
|
+
# (71.765%) Dixie Chicks
|
49
|
+
# (71.084%) Kelly Clarkson
|
50
|
+
# (69.535%) Miranda Lambert
|
51
|
+
# (66.952%) LeAnn Rimes
|
52
|
+
# (66.398%) Mandy Moore
|
53
|
+
# (65.817%) Bo Bice
|
54
|
+
# (65.279%) Diana DeGarmo
|
55
|
+
# (65.115%) Gretchen Wilson
|
56
|
+
# (62.982%) Clay Aiken
|
57
|
+
# (62.436%) Ashlee Simpson
|
58
|
+
# (62.160%) Christina Aguilera
|
59
|
+
module Scrobbler
|
60
|
+
class Artist < Base
|
61
|
+
attr_accessor :name, :mbid, :playcount, :rank, :url, :thumbnail, :image, :reach, :count, :streamable
|
62
|
+
attr_accessor :chartposition
|
63
|
+
|
64
|
+
# used for similar artists
|
65
|
+
attr_accessor :match
|
66
|
+
|
67
|
+
class << self
|
68
|
+
def new_from_xml(xml, doc=nil)
|
69
|
+
name = (xml).at(:name).inner_html if (xml).at(:name)
|
70
|
+
# occasionally name can be found in root of artist element (<artist name="">) rather than as an element (<name>)
|
71
|
+
name = xml['name'] if name.nil? && xml['name']
|
72
|
+
a = Artist.new(name)
|
73
|
+
a.mbid = (xml).at(:mbid).inner_html if (xml).at(:mbid)
|
74
|
+
a.playcount = (xml).at(:playcount).inner_html if (xml).at(:playcount)
|
75
|
+
a.rank = (xml).at(:rank).inner_html if (xml).at(:rank)
|
76
|
+
a.url = (xml).at(:url).inner_html if (xml).at(:url)
|
77
|
+
a.thumbnail = (xml).at(:thumbnail).inner_html if (xml).at(:thumbnail)
|
78
|
+
a.thumbnail = (xml).at(:image_small).inner_html if a.thumbnail.nil? && (xml).at(:image_small)
|
79
|
+
a.image = (xml).at(:image).inner_html if (xml).at(:image)
|
80
|
+
a.reach = (xml).at(:reach).inner_html if (xml).at(:reach)
|
81
|
+
a.match = (xml).at(:match).inner_html if (xml).at(:match)
|
82
|
+
a.chartposition = (xml).at(:chartposition).inner_html if (xml).at(:chartposition)
|
83
|
+
|
84
|
+
# in top artists for tag
|
85
|
+
a.count = xml['count'] if xml['count']
|
86
|
+
a.streamable = xml['streamable'] if xml['streamable']
|
87
|
+
a.streamable = (xml).at(:streamable).inner_html == '1' ? 'yes' : 'no' if a.streamable.nil? && (xml).at(:streamable)
|
88
|
+
a
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def initialize(name)
|
93
|
+
raise ArgumentError, "Name is required" if name.blank?
|
94
|
+
@name = name
|
95
|
+
end
|
96
|
+
|
97
|
+
def api_path(version=nil)
|
98
|
+
"/#{version || API_VERSION}/artist/#{CGI::escape(name)}"
|
99
|
+
end
|
100
|
+
|
101
|
+
def current_events(format=:ics)
|
102
|
+
format = :ics if format.to_s == 'ical'
|
103
|
+
raise ArgumentError unless ['ics', 'rss'].include?(format.to_s)
|
104
|
+
"#{API_URL.chop}#{api_path}/events.#{format}"
|
105
|
+
end
|
106
|
+
|
107
|
+
def similar(force=false)
|
108
|
+
get_instance(:similar, :similar, :artist, force)
|
109
|
+
end
|
110
|
+
|
111
|
+
def top_fans(force=false)
|
112
|
+
get_instance(:fans, :top_fans, :user, force)
|
113
|
+
end
|
114
|
+
|
115
|
+
def top_tracks(force=false)
|
116
|
+
get_instance(:toptracks, :top_tracks, :track, force)
|
117
|
+
end
|
118
|
+
|
119
|
+
def top_albums(force=false)
|
120
|
+
get_instance(:topalbums, :top_albums, :album, force)
|
121
|
+
end
|
122
|
+
|
123
|
+
def top_tags(force=false)
|
124
|
+
get_instance(:toptags, :top_tags, :tag, force)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Scrobbler
|
2
|
+
|
3
|
+
API_URL = 'http://ws.audioscrobbler.com/'
|
4
|
+
API_VERSION = '1.0'
|
5
|
+
|
6
|
+
class Base
|
7
|
+
class << self
|
8
|
+
def connection
|
9
|
+
@connection ||= REST::Connection.new(API_URL)
|
10
|
+
end
|
11
|
+
|
12
|
+
def fetch_and_parse(resource)
|
13
|
+
Hpricot::XML(connection.get(resource))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
# in order for subclass to use, it must have api_path method
|
19
|
+
def get_instance(api_method, instance_name, element, force=false, version=nil)
|
20
|
+
scrobbler_class = "scrobbler/#{element.to_s}".camelize.constantize
|
21
|
+
if instance_variable_get("@#{instance_name}").nil? || force
|
22
|
+
doc = self.class.fetch_and_parse("#{api_path(version)}/#{api_method}.xml")
|
23
|
+
elements = (doc/element).inject([]) { |elements, el| elements << scrobbler_class.new_from_xml(el, doc); elements }
|
24
|
+
instance_variable_set("@#{instance_name}", elements)
|
25
|
+
end
|
26
|
+
instance_variable_get("@#{instance_name}")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Scrobbler
|
2
|
+
class Chart < Base
|
3
|
+
class << self
|
4
|
+
def new_from_xml(xml, doc)
|
5
|
+
Chart.new(xml['from'], xml['to'])
|
6
|
+
end
|
7
|
+
end
|
8
|
+
def initialize(from, to)
|
9
|
+
raise ArgumentError, "From is required" if from.blank?
|
10
|
+
raise ArgumentError, "To is required" if to.blank?
|
11
|
+
@from = from
|
12
|
+
@to = to
|
13
|
+
end
|
14
|
+
|
15
|
+
def from=(value)
|
16
|
+
@from = value.to_i
|
17
|
+
end
|
18
|
+
|
19
|
+
def to=(value)
|
20
|
+
@to = value.to_i
|
21
|
+
end
|
22
|
+
|
23
|
+
def from
|
24
|
+
@from.to_i
|
25
|
+
end
|
26
|
+
|
27
|
+
def to
|
28
|
+
@to.to_i
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Scrobbler
|
2
|
+
class Playing
|
3
|
+
# you should read last.fm/api/submissions#np first!
|
4
|
+
|
5
|
+
attr_accessor :session_id, :now_playing_url, :artist, :track,
|
6
|
+
:album, :length, :track_number, :mb_track_id
|
7
|
+
attr_reader :status
|
8
|
+
|
9
|
+
def initialize(args = {})
|
10
|
+
@session_id = args[:session_id] # from Scrobbler::SimpleAuth
|
11
|
+
@now_playing_url = args[:now_playing_url] # from Scrobbler::SimpleAuth (can change)
|
12
|
+
@artist = args[:artist] # track artist
|
13
|
+
@track = args[:track] # track name
|
14
|
+
@album = args[:album] || '' # track album (optional)
|
15
|
+
@length = args[:length] || '' # track length in seconds (optional)
|
16
|
+
@track_number = args[:track_number] || '' # track number (optional)
|
17
|
+
@mb_track_id = args[:mb_track_id] || '' # MusicBrainz track ID (optional)
|
18
|
+
|
19
|
+
if [@session_id, @now_playing_url, @artist, @track].any?(&:blank?)
|
20
|
+
raise ArgumentError, 'Missing required argument'
|
21
|
+
elsif !@length.to_s.empty? && @length.to_i <= 30 # see last.fm/api
|
22
|
+
raise ArgumentError, 'Length must be greater than 30 seconds'
|
23
|
+
end
|
24
|
+
|
25
|
+
@connection = REST::Connection.new(@now_playing_url)
|
26
|
+
end
|
27
|
+
|
28
|
+
def submit!
|
29
|
+
query = { :s => @session_id,
|
30
|
+
:a => @artist,
|
31
|
+
:t => @track,
|
32
|
+
:b => @album,
|
33
|
+
:l => @length,
|
34
|
+
:n => @track_number,
|
35
|
+
:m => @mb_track_id }
|
36
|
+
|
37
|
+
@status = @connection.post('', query)
|
38
|
+
|
39
|
+
case @status
|
40
|
+
when /OK/
|
41
|
+
|
42
|
+
when /BADSESSION/
|
43
|
+
raise BadSessionError # rerun Scrobbler::SimpleAuth#handshake!
|
44
|
+
else
|
45
|
+
raise RequestFailedError
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
|
3
|
+
module Scrobbler
|
4
|
+
module REST
|
5
|
+
class Connection
|
6
|
+
def initialize(base_url, args = {})
|
7
|
+
@base_url = base_url
|
8
|
+
@username = args[:username]
|
9
|
+
@password = args[:password]
|
10
|
+
end
|
11
|
+
|
12
|
+
def get(resource, args = nil)
|
13
|
+
request(resource, "get", args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def post(resource, args = nil)
|
17
|
+
request(resource, "post", args)
|
18
|
+
end
|
19
|
+
|
20
|
+
def request(resource, method = "get", args = nil)
|
21
|
+
url = URI.join(@base_url, resource)
|
22
|
+
|
23
|
+
if args
|
24
|
+
url.query = args.map { |k,v| "%s=%s" % [escape(k.to_s), escape(v.to_s)] }.join("&")
|
25
|
+
end
|
26
|
+
|
27
|
+
case method
|
28
|
+
when "get"
|
29
|
+
req = Net::HTTP::Get.new(url.request_uri)
|
30
|
+
when "post"
|
31
|
+
req = Net::HTTP::Post.new(url.request_uri)
|
32
|
+
end
|
33
|
+
|
34
|
+
if @username and @password
|
35
|
+
req.basic_auth(@username, @password)
|
36
|
+
end
|
37
|
+
|
38
|
+
http = Net::HTTP.new(url.host, url.port)
|
39
|
+
http.use_ssl = (url.port == 443)
|
40
|
+
|
41
|
+
res = http.start() { |conn| conn.request(req) }
|
42
|
+
res.body
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
def escape(str)
|
47
|
+
URI.escape(str, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# exception definitions
|
2
|
+
class BadSessionError < StandardError; end
|
3
|
+
class RequestFailedError < StandardError; end
|
4
|
+
|
5
|
+
module Scrobbler
|
6
|
+
class Scrobble
|
7
|
+
# you need to read last.fm/api/submissions#subs first!
|
8
|
+
|
9
|
+
attr_accessor :session_id, :submission_url, :artist, :track, :time,
|
10
|
+
:source, :length, :album, :track_number, :mb_track_id
|
11
|
+
attr_reader :status
|
12
|
+
|
13
|
+
def initialize(args = {})
|
14
|
+
@session_id = args[:session_id] # from Scrobbler::SimpleAuth
|
15
|
+
@submission_url = args[:submission_url] # from Scrobbler::SimpleAuth (can change)
|
16
|
+
@artist = args[:artist] # track artist
|
17
|
+
@track = args[:track] # track name
|
18
|
+
@time = args[:time] # a Time object set to the time the track started playing
|
19
|
+
@source = args[:source] || 'P' # track source, see last.fm/api/submissions#subs
|
20
|
+
@length = args[:length].to_s || '' # track length in seconds
|
21
|
+
@album = args[:album] || '' # track album name (optional)
|
22
|
+
@track_number = args[:track_number] || '' # track number (optional)
|
23
|
+
@mb_track_id = args[:mb_track_id] || '' # MusicBrainz track ID (optional)
|
24
|
+
|
25
|
+
if [@session_id, @submission_url, @artist, @track].any?(&:blank?)
|
26
|
+
raise ArgumentError, 'Missing required argument'
|
27
|
+
elsif @time.class.to_s != 'Time'
|
28
|
+
raise ArgumentError, ":time must be a Time object"
|
29
|
+
elsif !['P','R','E','U'].include?(@source) # see last.fm/api/submissions#subs
|
30
|
+
raise ArgumentError, "Invalid source"
|
31
|
+
elsif @source == 'P' && @length.blank? # length is not optional if source is P
|
32
|
+
raise ArgumentError, 'Length must be set'
|
33
|
+
elsif !@length.blank? && @length.to_i <= 30 # see last.fm/api/submissions#subs
|
34
|
+
raise ArgumentError, 'Length must be greater than 30 seconds'
|
35
|
+
end
|
36
|
+
|
37
|
+
@connection = REST::Connection.new(@submission_url)
|
38
|
+
end
|
39
|
+
|
40
|
+
def submit!
|
41
|
+
query = { :s => @session_id,
|
42
|
+
'a[0]' => @artist,
|
43
|
+
't[0]' => @track,
|
44
|
+
'i[0]' => @time.utc.to_i,
|
45
|
+
'o[0]' => @source,
|
46
|
+
'r[0]' => '',
|
47
|
+
'l[0]' => @length,
|
48
|
+
'b[0]' => @album,
|
49
|
+
'n[0]' => @track_number,
|
50
|
+
'm[0]' => @mb_track_id }
|
51
|
+
|
52
|
+
@status = @connection.post('', query)
|
53
|
+
|
54
|
+
case @status
|
55
|
+
when /OK/
|
56
|
+
|
57
|
+
when /BADSESSION/
|
58
|
+
raise BadSessionError # rerun Scrobbler::SimpleAuth#handshake!
|
59
|
+
when /FAILED/
|
60
|
+
raise RequestFailedError, @status
|
61
|
+
else
|
62
|
+
raise RequestFailedError
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
# exception definitions
|
4
|
+
class BadAuthError < StandardError; end
|
5
|
+
class BannedError < StandardError; end
|
6
|
+
class BadTimeError < StandardError; end
|
7
|
+
module Scrobbler
|
8
|
+
AUTH_URL = 'http://post.audioscrobbler.com'
|
9
|
+
AUTH_VER = '1.2.1'
|
10
|
+
|
11
|
+
class SimpleAuth
|
12
|
+
# you should read last.fm/api/submissions#handshake
|
13
|
+
|
14
|
+
attr_accessor :user, :password, :client_id, :client_ver
|
15
|
+
attr_reader :status, :session_id, :now_playing_url, :submission_url
|
16
|
+
|
17
|
+
def initialize(args = {})
|
18
|
+
@user = args[:user] # last.fm username
|
19
|
+
@password = args[:password] # last.fm password
|
20
|
+
@client_id = 'rbs' # Client ID assigned by last.fm; Don't change this!
|
21
|
+
@client_ver = Scrobbler::Version
|
22
|
+
|
23
|
+
raise ArgumentError, 'Missing required argument' if @user.blank? || @password.blank?
|
24
|
+
|
25
|
+
@connection = REST::Connection.new(AUTH_URL)
|
26
|
+
end
|
27
|
+
|
28
|
+
def handshake!
|
29
|
+
password_hash = Digest::MD5.hexdigest(@password)
|
30
|
+
timestamp = Time.now.to_i.to_s
|
31
|
+
token = Digest::MD5.hexdigest(password_hash + timestamp)
|
32
|
+
|
33
|
+
query = { :hs => 'true',
|
34
|
+
:p => AUTH_VER,
|
35
|
+
:c => @client_id,
|
36
|
+
:v => @client_ver,
|
37
|
+
:u => @user,
|
38
|
+
:t => timestamp,
|
39
|
+
:a => token }
|
40
|
+
result = @connection.get('/', query)
|
41
|
+
|
42
|
+
@status = result.split(/\n/)[0]
|
43
|
+
case @status
|
44
|
+
when /OK/
|
45
|
+
@session_id, @now_playing_url, @submission_url = result.split(/\n/)[1,3]
|
46
|
+
when /BANNED/
|
47
|
+
raise BannedError # something is wrong with the gem, check for an update
|
48
|
+
when /BADAUTH/
|
49
|
+
raise BadAuthError # invalid user/password
|
50
|
+
when /FAILED/
|
51
|
+
raise RequestFailedError, @status
|
52
|
+
when /BADTIME/
|
53
|
+
raise BadTimeError # system time is way off
|
54
|
+
else
|
55
|
+
raise RequestFailedError
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# Below is code samples for how to find the top albums and tracks for a tag.
|
2
|
+
#
|
3
|
+
# tag = Scrobbler::Tag.new('country')
|
4
|
+
#
|
5
|
+
# puts 'Top Albums'
|
6
|
+
# tag.top_albums.each { |a| puts "(#{a.count}) #{a.name} by #{a.artist}" }
|
7
|
+
#
|
8
|
+
# puts
|
9
|
+
#
|
10
|
+
# puts 'Top Tracks'
|
11
|
+
# tag.top_tracks.each { |t| puts "(#{t.count}) #{t.name} by #{t.artist}" }
|
12
|
+
#
|
13
|
+
# Which would output something similar to:
|
14
|
+
#
|
15
|
+
# Top Albums
|
16
|
+
# (29) American IV: The Man Comes Around by Johnny Cash
|
17
|
+
# (14) Folks Pop In at the Waterhouse by Various Artists
|
18
|
+
# (13) Hapless by Flowers From The Man Who Shot Your Cousin
|
19
|
+
# (9) Taking The Long Way by Dixie Chicks
|
20
|
+
# (8) Unchained by Johnny Cash
|
21
|
+
# (8) American III: Solitary Man by Johnny Cash
|
22
|
+
# (8) Wide Open Spaces by Dixie Chicks
|
23
|
+
# (7) It's Now or Later by Tangled Star
|
24
|
+
# (7) Greatest Hits by Hank Williams
|
25
|
+
# (7) American Recordings by Johnny Cash
|
26
|
+
# (6) Forgotten Landscape by theNoLifeKing
|
27
|
+
# (6) At Folsom Prison by Johnny Cash
|
28
|
+
# (6) Fox Confessor Brings the Flood by Neko Case
|
29
|
+
# (6) Murder by Johnny Cash
|
30
|
+
# (5) Gloom by theNoLifeKing
|
31
|
+
# (5) Set This Circus Down by Tim McGraw
|
32
|
+
# (5) Blacklisted by Neko Case
|
33
|
+
# (5) Breathe by Faith Hill
|
34
|
+
# (5) Unearthed (disc 4: My Mother's Hymn Book) by Johnny Cash
|
35
|
+
# (4) Home by Dixie Chicks
|
36
|
+
#
|
37
|
+
# Top Tracks
|
38
|
+
# (221) Hurt by Johnny Cash
|
39
|
+
# (152) I Walk the Line by Johnny Cash
|
40
|
+
# (147) Ring of Fire by Johnny Cash
|
41
|
+
# (125) Folsom Prison Blues by Johnny Cash
|
42
|
+
# (77) The Man Comes Around by Johnny Cash
|
43
|
+
# (67) Personal Jesus by Johnny Cash
|
44
|
+
# (65) Not Ready To Make Nice by Dixie Chicks
|
45
|
+
# (63) Before He Cheats by Carrie Underwood
|
46
|
+
# (62) Give My Love to Rose by Johnny Cash
|
47
|
+
# (49) Jackson by Johnny Cash
|
48
|
+
# (49) What Hurts The Most by Rascal Flatts
|
49
|
+
# (48) Big River by Johnny Cash
|
50
|
+
# (46) Man in Black by Johnny Cash
|
51
|
+
# (46) Jolene by Dolly Parton
|
52
|
+
# (46) Friends in Low Places by Garth Brooks
|
53
|
+
# (46) One by Johnny Cash
|
54
|
+
# (44) Cocaine Blues by Johnny Cash
|
55
|
+
# (41) Get Rhythm by Johnny Cash
|
56
|
+
# (41) I Still Miss Someone by Johnny Cash
|
57
|
+
# (40) The Devil Went Down to Georgia by Charlie Daniels Band
|
58
|
+
module Scrobbler
|
59
|
+
class Tag < Base
|
60
|
+
attr_accessor :name, :count, :url
|
61
|
+
|
62
|
+
class << self
|
63
|
+
def new_from_xml(xml, doc=nil)
|
64
|
+
name = (xml).at(:name).inner_html
|
65
|
+
t = Tag.new(name)
|
66
|
+
t.count = (xml).at(:count).inner_html
|
67
|
+
t.url = (xml).at(:url).inner_html
|
68
|
+
t
|
69
|
+
end
|
70
|
+
|
71
|
+
def top_tags
|
72
|
+
doc = fetch_and_parse("/#{API_VERSION}/tag/toptags.xml")
|
73
|
+
@top_tags = (doc/:tag).inject([]) do |tags, tag|
|
74
|
+
t = Tag.new(tag['name'])
|
75
|
+
t.count = tag['count']
|
76
|
+
t.url = tag['url']
|
77
|
+
tags << t
|
78
|
+
tags
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def initialize(name)
|
84
|
+
raise ArgumentError, "Name is required" if name.blank?
|
85
|
+
@name = name
|
86
|
+
end
|
87
|
+
|
88
|
+
def api_path(version=nil)
|
89
|
+
"/#{version || API_VERSION}/tag/#{CGI::escape(name)}"
|
90
|
+
end
|
91
|
+
|
92
|
+
def top_artists(force=false)
|
93
|
+
get_instance(:topartists, :top_artists, :artist, force)
|
94
|
+
end
|
95
|
+
|
96
|
+
def top_albums(force=false)
|
97
|
+
get_instance(:topalbums, :top_albums, :album, force)
|
98
|
+
end
|
99
|
+
|
100
|
+
def top_tracks(force=false)
|
101
|
+
get_instance(:toptracks, :top_tracks, :track, force)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|