scrobbler-ng 2.0.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.
Files changed (156) hide show
  1. data/.gitignore +11 -0
  2. data/MIT-LICENSE +19 -0
  3. data/README.rdoc +104 -0
  4. data/Rakefile +11 -0
  5. data/VERSION.yml +4 -0
  6. data/examples/album.rb +19 -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 +165 -0
  13. data/lib/scrobbler/artist.rb +169 -0
  14. data/lib/scrobbler/auth.rb +47 -0
  15. data/lib/scrobbler/base.rb +124 -0
  16. data/lib/scrobbler/event.rb +105 -0
  17. data/lib/scrobbler/geo.rb +27 -0
  18. data/lib/scrobbler/helper/image.rb +52 -0
  19. data/lib/scrobbler/helper/streamable.rb +40 -0
  20. data/lib/scrobbler/library.rb +123 -0
  21. data/lib/scrobbler/playing.rb +49 -0
  22. data/lib/scrobbler/playlist.rb +45 -0
  23. data/lib/scrobbler/radio.rb +12 -0
  24. data/lib/scrobbler/rest.rb +47 -0
  25. data/lib/scrobbler/scrobble.rb +116 -0
  26. data/lib/scrobbler/session.rb +9 -0
  27. data/lib/scrobbler/shout.rb +21 -0
  28. data/lib/scrobbler/simpleauth.rb +60 -0
  29. data/lib/scrobbler/tag.rb +113 -0
  30. data/lib/scrobbler/track.rb +138 -0
  31. data/lib/scrobbler/user.rb +222 -0
  32. data/lib/scrobbler/venue.rb +67 -0
  33. data/lib/scrobbler.rb +34 -0
  34. data/tasks/jeweler.rake +18 -0
  35. data/tasks/rdoc.rake +7 -0
  36. data/tasks/tests.rake +13 -0
  37. data/tasks/yardoc.rake +8 -0
  38. data/test/fixtures/xml/album/info.xml +43 -0
  39. data/test/fixtures/xml/artist/fans.xml +52 -0
  40. data/test/fixtures/xml/artist/info.xml +58 -0
  41. data/test/fixtures/xml/artist/similar.xml +1004 -0
  42. data/test/fixtures/xml/artist/topalbums.xml +61 -0
  43. data/test/fixtures/xml/artist/toptags.xml +19 -0
  44. data/test/fixtures/xml/artist/toptracks.xml +62 -0
  45. data/test/fixtures/xml/auth/session.xml +7 -0
  46. data/test/fixtures/xml/auth/token.xml +3 -0
  47. data/test/fixtures/xml/event/attend.xml +3 -0
  48. data/test/fixtures/xml/event/attendees.xml +53 -0
  49. data/test/fixtures/xml/event/event.xml +35 -0
  50. data/test/fixtures/xml/event/shouts.xml +35 -0
  51. data/test/fixtures/xml/geo/events-distance-p1.xml +151 -0
  52. data/test/fixtures/xml/geo/events-lat-long.xml +167 -0
  53. data/test/fixtures/xml/geo/events-p1.xml +152 -0
  54. data/test/fixtures/xml/geo/events-p2.xml +380 -0
  55. data/test/fixtures/xml/geo/events-p3.xml +427 -0
  56. data/test/fixtures/xml/geo/top_artists-p1.xml +76 -0
  57. data/test/fixtures/xml/geo/top_tracks-p1.xml +77 -0
  58. data/test/fixtures/xml/library/albums-f30.xml +454 -0
  59. data/test/fixtures/xml/library/albums-p1.xml +754 -0
  60. data/test/fixtures/xml/library/albums-p2.xml +754 -0
  61. data/test/fixtures/xml/library/albums-p3.xml +754 -0
  62. data/test/fixtures/xml/library/albums-p4.xml +739 -0
  63. data/test/fixtures/xml/library/albums-p5.xml +754 -0
  64. data/test/fixtures/xml/library/albums-p6.xml +754 -0
  65. data/test/fixtures/xml/library/albums-p7.xml +739 -0
  66. data/test/fixtures/xml/library/albums-p8.xml +724 -0
  67. data/test/fixtures/xml/library/artists-f30.xml +334 -0
  68. data/test/fixtures/xml/library/artists-p1.xml +554 -0
  69. data/test/fixtures/xml/library/artists-p2.xml +554 -0
  70. data/test/fixtures/xml/library/artists-p3.xml +554 -0
  71. data/test/fixtures/xml/library/artists-p4.xml +554 -0
  72. data/test/fixtures/xml/library/artists-p5.xml +554 -0
  73. data/test/fixtures/xml/library/artists-p6.xml +554 -0
  74. data/test/fixtures/xml/library/artists-p7.xml +444 -0
  75. data/test/fixtures/xml/library/tracks-f30.xml +472 -0
  76. data/test/fixtures/xml/library/tracks-p1.xml +789 -0
  77. data/test/fixtures/xml/library/tracks-p10.xml +771 -0
  78. data/test/fixtures/xml/library/tracks-p11.xml +792 -0
  79. data/test/fixtures/xml/library/tracks-p12.xml +777 -0
  80. data/test/fixtures/xml/library/tracks-p13.xml +762 -0
  81. data/test/fixtures/xml/library/tracks-p14.xml +759 -0
  82. data/test/fixtures/xml/library/tracks-p15.xml +762 -0
  83. data/test/fixtures/xml/library/tracks-p16.xml +756 -0
  84. data/test/fixtures/xml/library/tracks-p17.xml +780 -0
  85. data/test/fixtures/xml/library/tracks-p18.xml +756 -0
  86. data/test/fixtures/xml/library/tracks-p19.xml +774 -0
  87. data/test/fixtures/xml/library/tracks-p2.xml +765 -0
  88. data/test/fixtures/xml/library/tracks-p20.xml +777 -0
  89. data/test/fixtures/xml/library/tracks-p21.xml +777 -0
  90. data/test/fixtures/xml/library/tracks-p22.xml +771 -0
  91. data/test/fixtures/xml/library/tracks-p23.xml +765 -0
  92. data/test/fixtures/xml/library/tracks-p24.xml +783 -0
  93. data/test/fixtures/xml/library/tracks-p25.xml +777 -0
  94. data/test/fixtures/xml/library/tracks-p26.xml +777 -0
  95. data/test/fixtures/xml/library/tracks-p27.xml +756 -0
  96. data/test/fixtures/xml/library/tracks-p28.xml +771 -0
  97. data/test/fixtures/xml/library/tracks-p29.xml +753 -0
  98. data/test/fixtures/xml/library/tracks-p3.xml +771 -0
  99. data/test/fixtures/xml/library/tracks-p30.xml +780 -0
  100. data/test/fixtures/xml/library/tracks-p31.xml +753 -0
  101. data/test/fixtures/xml/library/tracks-p32.xml +771 -0
  102. data/test/fixtures/xml/library/tracks-p33.xml +762 -0
  103. data/test/fixtures/xml/library/tracks-p34.xml +538 -0
  104. data/test/fixtures/xml/library/tracks-p4.xml +792 -0
  105. data/test/fixtures/xml/library/tracks-p5.xml +780 -0
  106. data/test/fixtures/xml/library/tracks-p6.xml +789 -0
  107. data/test/fixtures/xml/library/tracks-p7.xml +789 -0
  108. data/test/fixtures/xml/library/tracks-p8.xml +780 -0
  109. data/test/fixtures/xml/library/tracks-p9.xml +774 -0
  110. data/test/fixtures/xml/tag/similar.xml +254 -0
  111. data/test/fixtures/xml/tag/topalbums.xml +805 -0
  112. data/test/fixtures/xml/tag/topartists.xml +605 -0
  113. data/test/fixtures/xml/tag/toptags.xml +1254 -0
  114. data/test/fixtures/xml/tag/toptracks.xml +852 -0
  115. data/test/fixtures/xml/track/fans.xml +34 -0
  116. data/test/fixtures/xml/track/info.xml +53 -0
  117. data/test/fixtures/xml/track/toptags.xml +504 -0
  118. data/test/fixtures/xml/user/events.xml +401 -0
  119. data/test/fixtures/xml/user/friends.xml +30 -0
  120. data/test/fixtures/xml/user/lovedtracks.xml +678 -0
  121. data/test/fixtures/xml/user/neighbours.xml +23 -0
  122. data/test/fixtures/xml/user/playlists.xml +61 -0
  123. data/test/fixtures/xml/user/profile.xml +12 -0
  124. data/test/fixtures/xml/user/recentbannedtracks.xml +24 -0
  125. data/test/fixtures/xml/user/recentlovedtracks.xml +24 -0
  126. data/test/fixtures/xml/user/recenttracks.xml +124 -0
  127. data/test/fixtures/xml/user/systemrecs.xml +18 -0
  128. data/test/fixtures/xml/user/topalbums.xml +61 -0
  129. data/test/fixtures/xml/user/topartists.xml +41 -0
  130. data/test/fixtures/xml/user/toptags.xml +44 -0
  131. data/test/fixtures/xml/user/toptracks.xml +65 -0
  132. data/test/fixtures/xml/user/weeklyalbumchart.xml +256 -0
  133. data/test/fixtures/xml/user/weeklyartistchart.xml +220 -0
  134. data/test/fixtures/xml/user/weeklytrackchart.xml +746 -0
  135. data/test/fixtures/xml/venue/events.xml +151 -0
  136. data/test/fixtures/xml/venue/venue.xml +16 -0
  137. data/test/mocks/rest.rb +212 -0
  138. data/test/spec_helper.rb +7 -0
  139. data/test/test_helper.rb +20 -0
  140. data/test/unit/album_spec.rb +51 -0
  141. data/test/unit/artist_spec.rb +130 -0
  142. data/test/unit/auth_spec.rb +36 -0
  143. data/test/unit/event_spec.rb +109 -0
  144. data/test/unit/geo_spec.rb +148 -0
  145. data/test/unit/library_spec.rb +133 -0
  146. data/test/unit/playing_test.rb +53 -0
  147. data/test/unit/playlist_spec.rb +25 -0
  148. data/test/unit/radio_spec.rb +22 -0
  149. data/test/unit/scrobble_spec.rb +55 -0
  150. data/test/unit/scrobble_test.rb +69 -0
  151. data/test/unit/simpleauth_test.rb +45 -0
  152. data/test/unit/tag_spec.rb +101 -0
  153. data/test/unit/track_spec.rb +95 -0
  154. data/test/unit/user_spec.rb +264 -0
  155. data/test/unit/venue_spec.rb +104 -0
  156. metadata +253 -0
@@ -0,0 +1,116 @@
1
+ # exception definitions
2
+ class BadAuthError < StandardError; end
3
+ class BadSessionError < StandardError; end
4
+ class BadTimeError < StandardError; end
5
+ class ClientBannedError < StandardError; end
6
+ class RequestFailedError < StandardError; end
7
+
8
+ module Scrobbler
9
+ class Scrobble
10
+ SUBMISSION_HOST = 'http://post.audioscrobbler.com:80/'
11
+ SUBMISSION_PORT = 80
12
+ SUBMISSION_VERSION = '1.2.1'
13
+
14
+ @@client_id = 'tst'
15
+ @@client_ver = '1.0'
16
+ @@default_options = {
17
+ :handshake_on_init => true
18
+ }
19
+
20
+ def Scrobble.client_id=(cid)
21
+ @@client_id = cid
22
+ end
23
+
24
+ def Scrobble.client_ver=(cver)
25
+ @@client_ver = cver
26
+ end
27
+
28
+ def initialize(options={})
29
+ params = @@default_options.merge(options)
30
+ warn 'Warning: Default client id used, this is for testing only' if @@client_id == 'tst'
31
+ raise ArgumentError, 'Session is required' if params[:session].nil?
32
+ raise ArgumentError, 'User is required' if params[:user].nil?
33
+ @session = params[:session]
34
+ @user = params[:user]
35
+ end
36
+
37
+ def handshake
38
+ ts = Time.now.to_i.to_s
39
+ auth_token = Digest::MD5.hexdigest(@session.key + ts)
40
+ params = {
41
+ :hs => 'true',
42
+ :ps => SUBMISSION_VERSION,
43
+ :c => @@client_id,
44
+ :v => @@client_ver,
45
+ :u => @user.name,
46
+ :t => ts,
47
+ :a => auth_token,
48
+ :sk => @session.key
49
+ }
50
+ url = URI.join(SUBMISSION_HOST)
51
+ query = []
52
+ params.each do |k,v|
53
+ query << [Base.sanitize(k), Base.sanitize(v)]
54
+ end
55
+ url.query = query.join('&')
56
+ req = Net::HTTP::Get.new(url.request_uri)
57
+ res = Net::HTTP.start(SUBMISSION_HOST, SUBMISSION_PORT) do |conn|
58
+ conn.request(req)
59
+ end
60
+ res = res.body.split("\n")
61
+ if res[0] == 'OK'
62
+ # Handshake done, parse and return information
63
+ @session_id = res[1]
64
+ @now_playing_url = res[2]
65
+ @submission_url = res[3]
66
+ elsif res[0] == 'BANNED'
67
+ # This indicates that this client version has been banned from the
68
+ # server. This usually happens if the client is violating the protocol
69
+ # in a destructive way. Users should be asked to upgrade their client
70
+ # application.
71
+ raise ClientBannedError, 'Please update your client to a newer version.'
72
+ elsif res[0] == 'BADAUTH'
73
+ # This indicates that the authentication details provided were incorrect.
74
+ # The client should not retry the handshake until the user has changed
75
+ # their details
76
+ raise BadAuthError
77
+ elsif res[0] == 'BADTIME'
78
+ # The timestamp provided was not close enough to the current time.
79
+ # The system clock must be corrected before re-handshaking.
80
+ raise BadTimeError, "Not close enough to current time: #{ts}"
81
+ else
82
+ # This indicates a temporary server failure. The reason indicates the
83
+ # cause of the failure. The client should proceed as directed in the
84
+ # failure handling section
85
+ raise RequestFailedError, res[0]
86
+ end
87
+ end #^ handshake
88
+
89
+ def now_playing(track)
90
+ params = {
91
+ 's' => @session_id,
92
+ 't' => track.name,
93
+ 'a' => track.artist.name,
94
+ 'b' => '',
95
+ 'l' => '',
96
+ 'n' => '',
97
+ 'm' => ''
98
+ }
99
+ params['b'] = track.album.name unless track.album.nil?
100
+ params['l'] = track.duration.to_s unless track.duration.nil? && track.duration > 30
101
+ # @todo params['n']
102
+ params['m'] = track.mbid unless track.mbid.nil?
103
+ res = Net::HTTP.post_form(URI.parse(SUBMISSION_HOST), params).body.split("\n")
104
+ if res[0] == 'BADSESSION'
105
+ # This indicates that the Session ID sent was somehow invalid, possibly
106
+ # because another client has performed a handshake for this user. On
107
+ # receiving this, the client should re-handshake with the server before
108
+ # continuing.
109
+ raise BadSessionError
110
+ end
111
+ end #^ now_playing
112
+
113
+ def submit(tracks={})
114
+ end #^ submit
115
+ end
116
+ end
@@ -0,0 +1,9 @@
1
+ module Scrobbler
2
+ class Session < Base
3
+ attr_reader :key, :name, :subscriber
4
+
5
+ def initialize(data={})
6
+ populate_data(data)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ module Scrobbler
2
+ class Shout < Base
3
+ attr_reader :author, :date, :body
4
+
5
+ class << self
6
+ def new_from_libxml(xml)
7
+ data={}
8
+ xml.children.each do |child|
9
+ data[:body] = child.content if child.name == 'body'
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
15
+ end
16
+
17
+ def initialize(input={})
18
+ populate_data(input)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,60 @@
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
+ # @todo This is not 2.0!!
12
+ class SimpleAuth
13
+ # you should read last.fm/api/submissions#handshake
14
+
15
+ attr_accessor :user, :password, :client_id, :client_ver
16
+ attr_reader :status, :session_id, :now_playing_url, :submission_url
17
+
18
+ def initialize(args = {})
19
+ @user = args[:user] # last.fm username
20
+ @password = args[:password] # last.fm password
21
+ @client_id = 'rbs' # Client ID assigned by last.fm; Don't change this!
22
+ @client_ver = '0.2.13'
23
+
24
+ raise ArgumentError, 'Missing required argument' if @user.empty? || @password.empty?
25
+
26
+ @connection = REST::Connection.new(AUTH_URL)
27
+ end
28
+
29
+ def handshake!
30
+ password_hash = Digest::MD5.hexdigest(@password)
31
+ timestamp = Time.now.to_i.to_s
32
+ token = Digest::MD5.hexdigest(password_hash + timestamp)
33
+
34
+ query = { :hs => 'true',
35
+ :p => AUTH_VER,
36
+ :c => @client_id,
37
+ :v => @client_ver,
38
+ :u => @user,
39
+ :t => timestamp,
40
+ :a => token }
41
+ result = @connection.get('/', query)
42
+
43
+ @status = result.split(/\n/)[0]
44
+ case @status
45
+ when /OK/
46
+ @session_id, @now_playing_url, @submission_url = result.split(/\n/)[1,3]
47
+ when /BANNED/
48
+ raise BannedError # something is wrong with the gem, check for an update
49
+ when /BADAUTH/
50
+ raise BadAuthError # invalid user/password
51
+ when /FAILED/
52
+ raise RequestFailedError, @status
53
+ when /BADTIME/
54
+ raise BadTimeError # system time is way off
55
+ else
56
+ raise RequestFailedError
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,113 @@
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, :streamable
61
+
62
+ class << self
63
+ def new_from_libxml(xml)
64
+ data = {}
65
+ xml.children.each do |child|
66
+ data[:name] = child.content if child.name == 'name'
67
+ data[:count] = child.content.to_i if child.name == 'count'
68
+ data[:url] = child.content if child.name == 'url'
69
+ if child.name == 'streamable'
70
+ if ['1', 'true'].include?(child.content)
71
+ data[:streamable] = true
72
+ else
73
+ data[:streamable] = false
74
+ end
75
+ end
76
+ end
77
+
78
+ Tag.new(data[:name], data)
79
+ end
80
+ end
81
+
82
+ def initialize(name, data={})
83
+ raise ArgumentError, "Name is required" if name.empty?
84
+ @name = name
85
+ @url = data[:url] unless data[:url].nil?
86
+ @count = data[:count] unless data[:count].nil?
87
+ @streamable = data[:streamable] unless data[:streamable].nil?
88
+ end
89
+
90
+ def top_artists(force=false)
91
+ get_response('tag.gettopartists', :top_artists, 'topartists', 'artist', {'tag'=>@name}, force)
92
+ end
93
+
94
+ def top_albums(force=false)
95
+ get_response('tag.gettopalbums', :top_albums, 'topalbums', 'album', {'tag'=>@name}, force)
96
+ end
97
+
98
+ def top_tracks(force=false)
99
+ get_response('tag.gettoptracks', :top_tracks, 'toptracks', 'track', {'tag'=>@name}, force)
100
+ end
101
+
102
+ def Tag.top_tags
103
+ self.get('tag.gettoptags', :toptags, :tag)
104
+ end
105
+
106
+ # Search for tags similar to this one. Returns tags ranked by similarity,
107
+ # based on listening data.
108
+ def similar(force=false)
109
+ params = {:tag => @name}
110
+ get_response('tag.getsimilar', :similar, 'similartags', 'tag', params, force)
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,138 @@
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
+ # Load Helper modules
39
+ include ImageObjectFuncs
40
+ extend ImageClassFuncs
41
+
42
+ attr_accessor :artist, :name, :mbid, :playcount, :rank, :url, :id, :count
43
+ attr_accessor :streamable, :album, :date, :now_playing, :tagcount
44
+ attr_accessor :duration, :listeners
45
+
46
+ class << self
47
+ def new_from_libxml(xml)
48
+ data = {}
49
+ xml.children.each do |child|
50
+ data[:name] = child.content if child.name == 'name'
51
+ data[:mbid] = child.content if child.name == 'mbid'
52
+ data[:url] = child.content if child.name == 'url'
53
+ data[:date] = Time.parse(child.content) if child.name == 'date'
54
+ data[:artist] = Artist.new_from_libxml(child) if child.name == 'artist'
55
+ data[:album] = Album.new_from_libxml(child) if child.name == 'album'
56
+ data[:playcount] = child.content.to_i if child.name == 'playcount'
57
+ data[:tagcount] = child.content.to_i if child.name == 'tagcount'
58
+ maybe_image_node(data, child)
59
+ if child.name == 'streamable'
60
+ if ['1', 'true'].include?(child.content)
61
+ data[:streamable] = true
62
+ else
63
+ data[:streamable] = false
64
+ end
65
+ end
66
+ 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
+ end
76
+ end
77
+
78
+ def initialize(artist, name, data={})
79
+ raise ArgumentError, "Artist is required" if artist.nil?
80
+ raise ArgumentError, "Name is required" if name.empty?
81
+ @artist = artist
82
+ @name = name
83
+ populate_data(data)
84
+ end
85
+
86
+ def add_tags(tags)
87
+ # This function requires authentication, but SimpleAuth is not yet 2.0
88
+ raise NotImplementedError
89
+ end
90
+
91
+ def ban
92
+ # This function requires authentication, but SimpleAuth is not yet 2.0
93
+ raise NotImplementedError
94
+ end
95
+
96
+ @info_loaded = false
97
+ def load_info
98
+ return nil if @info_loaded
99
+ doc = Base.request('track.getinfo', :artist => @artist.name, :track => @name)
100
+ doc.root.children.each do |childL1|
101
+ next unless childL1.name == 'track'
102
+ childL1.children.each do |child|
103
+ @id = child.content.to_i if child.name == 'id'
104
+ @mbid = child.content if child.name == 'mbid'
105
+ @duration = child.content.to_i if child.name == 'duration'
106
+ @url = child.content if child.name == 'url'
107
+ if child.name == 'streamable'
108
+ if ['1', 'true'].include?(child.content)
109
+ @streamable = true
110
+ else
111
+ @streamable = false
112
+ end
113
+ end
114
+ @listeners = child.content.to_i if child.name == 'listeners'
115
+ @playcount = child.content.to_i if child.name == 'playcount'
116
+ @artist = Artist.new_from_libxml(child) if child.name == 'artist'
117
+ @album = Album.new_from_libxml(child) if child.name == 'album'
118
+ end
119
+ end
120
+ @info_loaded = true
121
+ end
122
+
123
+ def top_fans(force=false)
124
+ get_response('track.gettopfans', :fans, 'topfans', 'user', {:artist=>@artist.name, :track=>@name}, force)
125
+ end
126
+
127
+ def top_tags(force=false)
128
+ get_response('track.gettoptags', :top_tags, 'toptags', 'tag', {:artist=>@artist.name.to_s, :track=>@name}, force)
129
+ end
130
+
131
+ def ==(otherTrack)
132
+ if otherTrack.is_a?(Scrobbler::Track)
133
+ return ((@name == otherTrack.name) && (@artist == otherTrack.artist))
134
+ end
135
+ false
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,222 @@
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
+ # Load Helper modules
50
+ include ImageObjectFuncs
51
+ extend ImageClassFuncs
52
+
53
+ attr_reader :username, :url, :weight, :match, :realname, :name
54
+
55
+ class << self
56
+ def new_from_libxml(xml)
57
+ data = {}
58
+ xml.children.each do |child|
59
+ data[:name] = child.content if child.name == 'name'
60
+ data[:url] = child.content if child.name == 'url'
61
+ data[:weight] = child.content.to_i if child.name == 'weight'
62
+ data[:match] = child.content if child.name == 'match'
63
+ data[:realname] = child.content if child.name == 'realname'
64
+ maybe_image_node(data, child)
65
+ end
66
+ User.new(data[:name], data)
67
+ end
68
+
69
+ def find(*args)
70
+ options = {:include_profile => false}
71
+ options.merge!(args.pop) if args.last.is_a?(Hash)
72
+ users = args.flatten.inject([]) { |users, u| users << User.new(u, options); users }
73
+ users.length == 1 ? users.pop : users
74
+ end
75
+ end
76
+
77
+ def initialize(username, input={})
78
+ data = {:include_profile => false}.merge(input)
79
+ raise ArgumentError if username.empty?
80
+ @username = username
81
+ @name = @username
82
+ load_profile() if data[:include_profile]
83
+ populate_data(data)
84
+ end
85
+
86
+ # Get a list of upcoming events that this user is attending.
87
+ #
88
+ # Supports ical, ics or rss as its format
89
+ def events(force=false)
90
+ get_response('user.getevents', :events, 'events', 'event', {'user'=>@username}, force)
91
+ end
92
+
93
+ # Get a list of the user's friends on Last.fm.
94
+ def friends(force=false, page=1, limit=50)
95
+ get_response('user.getfriends', :friends, 'friends', 'user', {'user'=>@username, 'page'=>page.to_s, 'limit'=>limit.to_s}, force)
96
+ end
97
+
98
+ # Get information about a user profile.
99
+ def load_info
100
+ # This function requires authentication, but SimpleAuth is not yet 2.0
101
+ raise NotImplementedError
102
+ end
103
+
104
+ # Get the last 50 tracks loved by a user.
105
+ def loved_tracks(force=false)
106
+ get_response('user.getlovedtracks', :loved_tracks, 'lovedtracks', 'track', {'user'=>@username}, force)
107
+ end
108
+
109
+ # Get a list of a user's neighbours on Last.fm.
110
+ def neighbours(force=false)
111
+ get_response('user.getneighbours', :neighbours, 'neighbours', 'user', {'user'=>@username}, force)
112
+ end
113
+
114
+ # Get a paginated list of all events a user has attended in the past.
115
+ def past_events(format=:ics)
116
+ # This needs a Event class, which is yet not available
117
+ raise NotImplementedError
118
+ end
119
+
120
+ # Get a list of a user's playlists on Last.fm.
121
+ def playlists(force=false)
122
+ #(api_method, instance_name, parent, element, parameters, force=false)
123
+ get_response('user.getplaylists', :playlist, 'playlists', 'playlist', {'user'=>@username}, force)
124
+ end
125
+
126
+ # Get a list of the recent tracks listened to by this user. Indicates now
127
+ # playing track if the user is currently listening.
128
+ #
129
+ # Possible parameters:
130
+ # - limit: An integer used to limit the number of tracks returned.
131
+ def recent_tracks(force=false, parameters={})
132
+ parameters.merge!({'user' => @username})
133
+ get_response('user.getrecenttracks', :recent_tracks, 'recenttracks', 'track', parameters, force)
134
+ end
135
+
136
+ # Get Last.fm artist recommendations for a user
137
+ def recommended_artists
138
+ # This function require authentication, but SimpleAuth is not yet 2.0
139
+ raise NotImplementedError
140
+ end
141
+
142
+ # Get a paginated list of all events recommended to a user by Last.fm,
143
+ # based on their listening profile.
144
+ def recommended_events
145
+ # This function require authentication, but SimpleAuth is not yet 2.0
146
+ raise NotImplementedError
147
+ end
148
+
149
+ # Get shouts for this user.
150
+ def shouts
151
+ # This needs a Shout class which is yet not available
152
+ raise NotImplementedError
153
+ end
154
+
155
+ # Get the top albums listened to by a user. You can stipulate a time period.
156
+ # Sends the overall chart by default.
157
+ def top_albums(force=false, period='overall')
158
+ get_response('user.gettopalbums', :top_albums, 'topalbums', 'album', {'user'=>@username, 'period'=>period}, force)
159
+ end
160
+
161
+ # Get the top artists listened to by a user. You can stipulate a time
162
+ # period. Sends the overall chart by default.
163
+ def top_artists(force=false, period='overall')
164
+ get_response('user.gettopartists', :top_artists, 'topartists', 'artist', {'user' => @username, 'period'=>period}, force)
165
+ end
166
+
167
+ # Get the top tags used by this user.
168
+ def top_tags(force=false)
169
+ get_response('user.gettoptags', :top_tags, 'toptags', 'tag', {'user'=>@username}, force)
170
+ end
171
+
172
+ # Get the top tracks listened to by a user. You can stipulate a time period.
173
+ # Sends the overall chart by default.
174
+ def top_tracks(force=false, period='overall')
175
+ get_response('user.gettoptracks', :top_tracks, 'toptracks', 'track', {'user'=>@username, 'period'=>period}, force)
176
+ end
177
+
178
+ # Get an album chart for a user profile, for a given date range. If no date
179
+ # range is supplied, it will return the most recent album chart for this
180
+ # user.
181
+ def weekly_album_chart(from=nil, to=nil)
182
+ parameters = {'user' => @username}
183
+ parameters['from'] = from unless from.nil?
184
+ parameters['to'] = to unless to.nil?
185
+ get_response('user.getweeklyalbumchart', nil, 'weeklyalbumchart', 'album', parameters, true)
186
+ end
187
+
188
+ # Get an artist chart for a user profile, for a given date range. If no date
189
+ # range is supplied, it will return the most recent artist chart for this
190
+ # user.
191
+ def weekly_artist_chart(from=nil, to=nil)
192
+ parameters = {'user' => @username}
193
+ parameters['from'] = from unless from.nil?
194
+ parameters['to'] = to unless to.nil?
195
+ get_response('user.getweeklyartistchart', nil, 'weeklyartistchart', 'artist', parameters, true)
196
+ end
197
+
198
+ # Get a list of available charts for this user, expressed as date ranges
199
+ # which can be sent to the chart services.
200
+ def weekly_chart_list(force=false)
201
+ # @todo
202
+ raise NotImplementedError
203
+ end
204
+
205
+ # Get a track chart for a user profile, for a given date range. If no date
206
+ # range is supplied, it will return the most recent track chart for this
207
+ # user.
208
+ def weekly_track_chart(from=nil, to=nil)
209
+ parameters = {'user' => @username}
210
+ parameters['from'] = from unless from.nil?
211
+ parameters['to'] = to unless to.nil?
212
+ get_response('user.getweeklytrackchart', nil, 'weeklytrackchart', 'track', parameters, true)
213
+ end
214
+
215
+ # Shout on this user's shoutbox
216
+ def shout(message)
217
+ # This function require authentication, but SimpleAuth is not yet 2.0
218
+ raise NotImplementedError
219
+ end
220
+
221
+ end
222
+ end