scrobbler 0.1.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/History.txt +1 -0
- data/Manifest.txt +58 -0
- data/README.txt +71 -0
- data/Rakefile +89 -0
- data/examples/album.rb +18 -0
- data/examples/artist.rb +14 -0
- data/examples/tag.rb +12 -0
- data/examples/track.rb +7 -0
- data/examples/user.rb +15 -0
- data/lib/scrobbler.rb +13 -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/rest.rb +47 -0
- data/lib/scrobbler/tag.rb +104 -0
- data/lib/scrobbler/track.rb +96 -0
- data/lib/scrobbler/user.rb +192 -0
- data/lib/scrobbler/version.rb +9 -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 +26 -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/tag_test.rb +65 -0
- data/test/unit/track_test.rb +42 -0
- data/test/unit/user_test.rb +291 -0
- metadata +120 -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
|
98
|
+
"/#{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)
|
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}/#{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,47 @@
|
|
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
|
+
# TODO: What about keys without value?
|
25
|
+
url.query = args.map { |k,v| "%s=%s" % [URI.encode(k), URI.encode(v)] }.join("&")
|
26
|
+
end
|
27
|
+
|
28
|
+
case method
|
29
|
+
when "get"
|
30
|
+
req = Net::HTTP::Get.new(url.request_uri)
|
31
|
+
when "post"
|
32
|
+
req = Net::HTTP::Post.new(url.request_uri)
|
33
|
+
end
|
34
|
+
|
35
|
+
if @username and @password
|
36
|
+
req.basic_auth(@username, @password)
|
37
|
+
end
|
38
|
+
|
39
|
+
http = Net::HTTP.new(url.host, url.port)
|
40
|
+
http.use_ssl = (url.port == 443)
|
41
|
+
|
42
|
+
res = http.start() { |conn| conn.request(req) }
|
43
|
+
res.body
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
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
|
89
|
+
"/#{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
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# Below is an example of how to get the top fans for a track.
|
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
|
36
|
+
module Scrobbler
|
37
|
+
class Track < Base
|
38
|
+
attr_accessor :artist, :artist_mbid, :name, :mbid, :playcount, :rank, :url, :reach
|
39
|
+
attr_accessor :streamable, :album, :album_mbid, :date, :date_uts
|
40
|
+
|
41
|
+
# only seems to be used on top tracks for tag
|
42
|
+
attr_accessor :count, :thumbnail, :image
|
43
|
+
|
44
|
+
# for weekly top tracks
|
45
|
+
attr_accessor :chartposition
|
46
|
+
|
47
|
+
class << self
|
48
|
+
def new_from_xml(xml, doc=nil)
|
49
|
+
artist = (xml).at(:artist)['name'] if (xml).at(:artist) && (xml).at(:artist)['name']
|
50
|
+
artist = (xml).at(:artist).inner_html if artist.nil? && (xml).at(:artist)
|
51
|
+
artist = doc.root['artist'] if artist.nil? && doc.root['artist']
|
52
|
+
name = (xml).at(:name).inner_html if (xml).at(:name)
|
53
|
+
name = xml['name'] if name.nil? && xml['name']
|
54
|
+
t = Track.new(artist, name)
|
55
|
+
t.artist_mbid = (xml).at(:artist)['mbid'] if (xml).at(:artist) && (xml).at(:artist)['mbid']
|
56
|
+
t.artist_mbid = (xml).at(:artist).at(:mbid).inner_html if t.artist_mbid.nil? && (xml).at(:artist) && (xml).at(:artist).at(:mbid)
|
57
|
+
t.mbid = (xml).at(:mbid).inner_html if (xml).at(:mbid)
|
58
|
+
t.playcount = (xml).at(:playcount).inner_html if (xml).at(:playcount)
|
59
|
+
t.chartposition = (xml).at(:chartposition).inner_html if (xml).at(:chartposition)
|
60
|
+
t.rank = (xml).at(:rank).inner_html if (xml).at(:rank)
|
61
|
+
t.url = (xml/:url).last.inner_html if (xml/:url).size > 1
|
62
|
+
t.url = (xml).at(:url).inner_html if t.url.nil? && (xml).at(:url)
|
63
|
+
t.streamable = (xml).at(:track)['streamable'] if (xml).at(:track) && (xml).at(:track)['streamable']
|
64
|
+
t.streamable = xml['streamable'] if t.streamable.nil? && xml['streamable']
|
65
|
+
t.count = xml['count'] if xml['count']
|
66
|
+
t.album = (xml).at(:album).inner_html if (xml).at(:album)
|
67
|
+
t.album_mbid = (xml).at(:album)['mbid'] if (xml).at(:album) && (xml).at(:album)['mbid']
|
68
|
+
t.date = Time.parse((xml).at(:date).inner_html) if (xml).at(:date)
|
69
|
+
t.date_uts = (xml).at(:date)['uts'] if (xml).at(:date) && (xml).at(:date)['uts']
|
70
|
+
t.thumbnail = (xml).at(:thumbnail).inner_html if (xml).at(:thumbnail)
|
71
|
+
t.image = (xml).at(:image).inner_html if (xml).at(:image)
|
72
|
+
t.reach = (xml).at(:reach).inner_html if (xml).at(:reach)
|
73
|
+
t
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def initialize(artist, name)
|
78
|
+
raise ArgumentError, "Artist is required" if artist.blank?
|
79
|
+
raise ArgumentError, "Name is required" if name.blank?
|
80
|
+
@artist = artist
|
81
|
+
@name = name
|
82
|
+
end
|
83
|
+
|
84
|
+
def api_path
|
85
|
+
"/#{API_VERSION}/track/#{CGI::escape(artist)}/#{CGI::escape(name)}"
|
86
|
+
end
|
87
|
+
|
88
|
+
def fans(force=false)
|
89
|
+
get_instance(:fans, :fans, :user, force)
|
90
|
+
end
|
91
|
+
|
92
|
+
def tags(force=false)
|
93
|
+
get_instance(:toptags, :tags, :tag, force)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
# Probably the most common use of this lib would be to get your most recent tracks or your top tracks. Below are some code samples.
|
2
|
+
# user = Scrobbler::User.new('jnunemaker')
|
3
|
+
#
|
4
|
+
# puts "#{user.username}'s Recent Tracks"
|
5
|
+
# puts "=" * (user.username.length + 16)
|
6
|
+
# user.recent_tracks.each { |t| puts t.name }
|
7
|
+
#
|
8
|
+
# puts
|
9
|
+
# puts
|
10
|
+
#
|
11
|
+
# puts "#{user.username}'s Top Tracks"
|
12
|
+
# puts "=" * (user.username.length + 13)
|
13
|
+
# user.top_tracks.each { |t| puts "(#{t.playcount}) #{t.name}" }
|
14
|
+
#
|
15
|
+
# Which would output something like:
|
16
|
+
#
|
17
|
+
# jnunemaker's Recent Tracks
|
18
|
+
# ==========================
|
19
|
+
# Everything You Want
|
20
|
+
# You're a God
|
21
|
+
# Bitter Sweet Symphony [Original Version]
|
22
|
+
# Lord I Guess I'll Never Know
|
23
|
+
# Country Song
|
24
|
+
# Bitter Sweet Symphony (Radio Edit)
|
25
|
+
#
|
26
|
+
#
|
27
|
+
# jnunemaker's Top Tracks
|
28
|
+
# =======================
|
29
|
+
# (62) Probably Wouldn't Be This Way
|
30
|
+
# (55) Not Ready To Make Nice
|
31
|
+
# (45) Easy Silence
|
32
|
+
# (43) Song 2
|
33
|
+
# (40) Everybody Knows
|
34
|
+
# (39) Before He Cheats
|
35
|
+
# (39) Something's Gotta Give
|
36
|
+
# (38) Hips Don't Lie (featuring Wyclef Jean)
|
37
|
+
# (37) Unwritten
|
38
|
+
# (37) Move Along
|
39
|
+
# (37) Dance, Dance
|
40
|
+
# (36) We Belong Together
|
41
|
+
# (36) Jesus, Take the Wheel
|
42
|
+
# (36) Black Horse and the Cherry Tree (radio version)
|
43
|
+
# (35) Photograph
|
44
|
+
# (35) You're Beautiful
|
45
|
+
# (35) Walk Away
|
46
|
+
# (34) Stickwitu
|
47
|
+
module Scrobbler
|
48
|
+
class User < Base
|
49
|
+
# attributes needed to initialize
|
50
|
+
attr_reader :username
|
51
|
+
|
52
|
+
# profile attributes
|
53
|
+
attr_accessor :id, :cluster, :url, :realname, :mbox_sha1sum, :registered
|
54
|
+
attr_accessor :registered_unixtime, :age, :gender, :country, :playcount, :avatar
|
55
|
+
|
56
|
+
# neighbor attributes
|
57
|
+
attr_accessor :match
|
58
|
+
|
59
|
+
# track fans attributes
|
60
|
+
attr_accessor :weight
|
61
|
+
|
62
|
+
class << self
|
63
|
+
def new_from_xml(xml, doc=nil)
|
64
|
+
u = User.new((xml)['username'])
|
65
|
+
u.url = (xml).at(:url).inner_html if (xml).at(:url)
|
66
|
+
u.avatar = (xml).at(:image).inner_html if (xml).at(:image)
|
67
|
+
u.weight = (xml).at(:weight).inner_html if (xml).at(:weight)
|
68
|
+
u.match = (xml).at(:match).inner_html if (xml).at(:match)
|
69
|
+
u
|
70
|
+
end
|
71
|
+
|
72
|
+
def find(*args)
|
73
|
+
options = {:include_profile => false}
|
74
|
+
options.merge!(args.pop) if args.last.is_a?(Hash)
|
75
|
+
users = args.flatten.inject([]) { |users, u| users << User.new(u, options); users }
|
76
|
+
users.length == 1 ? users.pop : users
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def initialize(username, o={})
|
81
|
+
options = {:include_profile => false}.merge(o)
|
82
|
+
raise ArgumentError if username.blank?
|
83
|
+
@username = username
|
84
|
+
load_profile() if options[:include_profile]
|
85
|
+
end
|
86
|
+
|
87
|
+
def api_path
|
88
|
+
"/#{API_VERSION}/user/#{CGI::escape(username)}"
|
89
|
+
end
|
90
|
+
|
91
|
+
def current_events(format=:ics)
|
92
|
+
format = :ics if format.to_s == 'ical'
|
93
|
+
raise ArgumentError unless ['ics', 'rss'].include?(format.to_s)
|
94
|
+
"#{API_URL.chop}#{api_path}/events.#{format}"
|
95
|
+
end
|
96
|
+
|
97
|
+
def friends_events(format=:ics)
|
98
|
+
format = :ics if format.to_s == 'ical'
|
99
|
+
raise ArgumentError unless ['ics', 'rss'].include?(format.to_s)
|
100
|
+
"#{API_URL.chop}#{api_path}/friendevents.#{format}"
|
101
|
+
end
|
102
|
+
|
103
|
+
def recommended_events(format=:ics)
|
104
|
+
format = :ics if format.to_s == 'ical'
|
105
|
+
raise ArgumentError unless ['ics', 'rss'].include?(format.to_s)
|
106
|
+
"#{API_URL.chop}#{api_path}/eventsysrecs.#{format}"
|
107
|
+
end
|
108
|
+
|
109
|
+
def load_profile
|
110
|
+
doc = self.class.fetch_and_parse("#{api_path}/profile.xml")
|
111
|
+
@id = (doc).at(:profile)['id']
|
112
|
+
@cluster = (doc).at(:profile)['cluster']
|
113
|
+
@url = (doc).at(:url).inner_html
|
114
|
+
@realname = (doc).at(:realname).inner_html
|
115
|
+
@mbox_sha1sum = (doc).at(:mbox_sha1sum).inner_html
|
116
|
+
@registered = (doc).at(:registered).inner_html
|
117
|
+
@registered_unixtime = (doc).at(:registered)['unixtime']
|
118
|
+
@age = (doc).at(:age).inner_html
|
119
|
+
@gender = (doc).at(:gender).inner_html
|
120
|
+
@country = (doc).at(:country).inner_html
|
121
|
+
@playcount = (doc).at(:playcount).inner_html
|
122
|
+
@avatar = (doc).at(:avatar).inner_html
|
123
|
+
end
|
124
|
+
|
125
|
+
def top_artists(force=false)
|
126
|
+
get_instance(:topartists, :top_artists, :artist, force)
|
127
|
+
end
|
128
|
+
|
129
|
+
def top_albums(force=false)
|
130
|
+
get_instance(:topalbums, :top_albums, :album, force)
|
131
|
+
end
|
132
|
+
|
133
|
+
def top_tracks(force=false)
|
134
|
+
get_instance(:toptracks, :top_tracks, :track, force)
|
135
|
+
end
|
136
|
+
|
137
|
+
def top_tags(force=false)
|
138
|
+
get_instance(:toptags, :top_tags, :tag, force)
|
139
|
+
end
|
140
|
+
|
141
|
+
def friends(force=false)
|
142
|
+
get_instance(:friends, :friends, :user, force)
|
143
|
+
end
|
144
|
+
|
145
|
+
def neighbours(force=false)
|
146
|
+
get_instance(:neighbours, :neighbours, :user, force)
|
147
|
+
end
|
148
|
+
|
149
|
+
def recent_tracks(force=false)
|
150
|
+
get_instance(:recenttracks, :recent_tracks, :track, force)
|
151
|
+
end
|
152
|
+
|
153
|
+
def recent_banned_tracks(force=false)
|
154
|
+
get_instance(:recentbannedtracks, :recent_banned_tracks, :track, force)
|
155
|
+
end
|
156
|
+
|
157
|
+
def recent_loved_tracks(force=false)
|
158
|
+
get_instance(:recentlovedtracks, :recent_loved_tracks, :track, force)
|
159
|
+
end
|
160
|
+
|
161
|
+
def recommendations(force=false)
|
162
|
+
get_instance(:systemrecs, :recommendations, :artist, force)
|
163
|
+
end
|
164
|
+
|
165
|
+
def charts(force=false)
|
166
|
+
get_instance(:weeklychartlist, :charts, :chart, force)
|
167
|
+
end
|
168
|
+
|
169
|
+
def weekly_artist_chart(from=nil, to=nil)
|
170
|
+
qs = create_query_string_from_timestamps(from, to)
|
171
|
+
doc = self.class.fetch_and_parse("#{api_path}/weeklyartistchart.xml#{qs}")
|
172
|
+
(doc/:artist).inject([]) { |elements, el| elements << Artist.new_from_xml(el); elements }
|
173
|
+
end
|
174
|
+
|
175
|
+
def weekly_album_chart(from=nil, to=nil)
|
176
|
+
qs = create_query_string_from_timestamps(from, to)
|
177
|
+
doc = self.class.fetch_and_parse("#{api_path}/weeklyalbumchart.xml#{qs}")
|
178
|
+
(doc/:album).inject([]) { |elements, el| elements << Album.new_from_xml(el); elements }
|
179
|
+
end
|
180
|
+
|
181
|
+
def weekly_track_chart(from=nil, to=nil)
|
182
|
+
qs = create_query_string_from_timestamps(from, to)
|
183
|
+
doc = self.class.fetch_and_parse("#{api_path}/weeklytrackchart.xml#{qs}")
|
184
|
+
(doc/:track).inject([]) { |elements, el| elements << Track.new_from_xml(el); elements }
|
185
|
+
end
|
186
|
+
|
187
|
+
private
|
188
|
+
def create_query_string_from_timestamps(from, to)
|
189
|
+
(from && to) ? "?from=#{from.to_i}&to=#{to.to_i}" : ''
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|