titanous-scrobbler 0.2.2

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.
Files changed (68) hide show
  1. data/History.txt +3 -0
  2. data/MIT-LICENSE +19 -0
  3. data/Manifest +67 -0
  4. data/README.rdoc +107 -0
  5. data/Rakefile +36 -0
  6. data/examples/album.rb +17 -0
  7. data/examples/artist.rb +13 -0
  8. data/examples/scrobble.rb +31 -0
  9. data/examples/tag.rb +11 -0
  10. data/examples/track.rb +6 -0
  11. data/examples/user.rb +14 -0
  12. data/lib/scrobbler/album.rb +143 -0
  13. data/lib/scrobbler/artist.rb +127 -0
  14. data/lib/scrobbler/base.rb +29 -0
  15. data/lib/scrobbler/chart.rb +31 -0
  16. data/lib/scrobbler/playing.rb +49 -0
  17. data/lib/scrobbler/rest.rb +47 -0
  18. data/lib/scrobbler/scrobble.rb +66 -0
  19. data/lib/scrobbler/simpleauth.rb +59 -0
  20. data/lib/scrobbler/tag.rb +104 -0
  21. data/lib/scrobbler/track.rb +96 -0
  22. data/lib/scrobbler/user.rb +192 -0
  23. data/lib/scrobbler/version.rb +3 -0
  24. data/lib/scrobbler.rb +20 -0
  25. data/scrobbler.gemspec +41 -0
  26. data/setup.rb +1585 -0
  27. data/test/fixtures/xml/album/info.xml +70 -0
  28. data/test/fixtures/xml/artist/fans.xml +18 -0
  29. data/test/fixtures/xml/artist/similar.xml +39 -0
  30. data/test/fixtures/xml/artist/topalbums.xml +36 -0
  31. data/test/fixtures/xml/artist/toptags.xml +18 -0
  32. data/test/fixtures/xml/artist/toptracks.xml +27 -0
  33. data/test/fixtures/xml/tag/topalbums.xml +39 -0
  34. data/test/fixtures/xml/tag/topartists.xml +39 -0
  35. data/test/fixtures/xml/tag/toptags.xml +252 -0
  36. data/test/fixtures/xml/tag/toptracks.xml +30 -0
  37. data/test/fixtures/xml/track/fans.xml +28 -0
  38. data/test/fixtures/xml/track/toptags.xml +33 -0
  39. data/test/fixtures/xml/user/friends.xml +21 -0
  40. data/test/fixtures/xml/user/neighbours.xml +18 -0
  41. data/test/fixtures/xml/user/profile.xml +12 -0
  42. data/test/fixtures/xml/user/recentbannedtracks.xml +24 -0
  43. data/test/fixtures/xml/user/recentlovedtracks.xml +24 -0
  44. data/test/fixtures/xml/user/recenttracks.xml +27 -0
  45. data/test/fixtures/xml/user/systemrecs.xml +18 -0
  46. data/test/fixtures/xml/user/topalbums.xml +42 -0
  47. data/test/fixtures/xml/user/topartists.xml +30 -0
  48. data/test/fixtures/xml/user/toptags.xml +18 -0
  49. data/test/fixtures/xml/user/toptracks.xml +27 -0
  50. data/test/fixtures/xml/user/weeklyalbumchart.xml +35 -0
  51. data/test/fixtures/xml/user/weeklyalbumchart_from_1138536002_to_1139140802.xml +35 -0
  52. data/test/fixtures/xml/user/weeklyartistchart.xml +38 -0
  53. data/test/fixtures/xml/user/weeklyartistchart_from_1138536002_to_1139140802.xml +59 -0
  54. data/test/fixtures/xml/user/weeklychartlist.xml +74 -0
  55. data/test/fixtures/xml/user/weeklytrackchart.xml +35 -0
  56. data/test/fixtures/xml/user/weeklytrackchart_from_1138536002_to_1139140802.xml +35 -0
  57. data/test/mocks/rest.rb +50 -0
  58. data/test/test_helper.rb +17 -0
  59. data/test/unit/album_test.rb +86 -0
  60. data/test/unit/artist_test.rb +82 -0
  61. data/test/unit/chart_test.rb +34 -0
  62. data/test/unit/playing_test.rb +53 -0
  63. data/test/unit/scrobble_test.rb +69 -0
  64. data/test/unit/simpleauth_test.rb +45 -0
  65. data/test/unit/tag_test.rb +65 -0
  66. data/test/unit/track_test.rb +42 -0
  67. data/test/unit/user_test.rb +291 -0
  68. metadata +172 -0
@@ -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,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.to_s), URI.encode(v.to_s)] }.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,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
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
@@ -0,0 +1,3 @@
1
+ module Scrobbler
2
+ Version = '0.2.2'
3
+ end
data/lib/scrobbler.rb ADDED
@@ -0,0 +1,20 @@
1
+ %w{cgi rubygems hpricot active_support}.each { |x| require x }
2
+
3
+ $: << File.expand_path(File.dirname(__FILE__))
4
+
5
+ require 'scrobbler/base'
6
+ require 'scrobbler/version'
7
+
8
+
9
+ require 'scrobbler/album'
10
+ require 'scrobbler/artist'
11
+ require 'scrobbler/chart'
12
+ require 'scrobbler/user'
13
+ require 'scrobbler/tag'
14
+ require 'scrobbler/track'
15
+
16
+ require 'scrobbler/simpleauth'
17
+ require 'scrobbler/scrobble'
18
+ require 'scrobbler/playing'
19
+
20
+ require 'scrobbler/rest'