rockstar 0.3.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 (77) hide show
  1. data/.gitignore +3 -0
  2. data/History.txt +6 -0
  3. data/MIT-LICENSE +19 -0
  4. data/Manifest +72 -0
  5. data/README.rdoc +140 -0
  6. data/Rakefile +21 -0
  7. data/VERSION +1 -0
  8. data/examples/.gitignore +1 -0
  9. data/examples/album.rb +16 -0
  10. data/examples/artist.rb +17 -0
  11. data/examples/lastfm.yml_example +3 -0
  12. data/examples/scrobble.rb +55 -0
  13. data/examples/tag.rb +20 -0
  14. data/examples/track.rb +14 -0
  15. data/examples/user.rb +18 -0
  16. data/lib/rockstar.rb +39 -0
  17. data/lib/rockstar/album.rb +108 -0
  18. data/lib/rockstar/artist.rb +135 -0
  19. data/lib/rockstar/auth.rb +21 -0
  20. data/lib/rockstar/base.rb +37 -0
  21. data/lib/rockstar/chart.rb +31 -0
  22. data/lib/rockstar/playing.rb +49 -0
  23. data/lib/rockstar/rest.rb +66 -0
  24. data/lib/rockstar/scrobble.rb +67 -0
  25. data/lib/rockstar/session.rb +19 -0
  26. data/lib/rockstar/simpleauth.rb +62 -0
  27. data/lib/rockstar/tag.rb +100 -0
  28. data/lib/rockstar/tokenauth.rb +84 -0
  29. data/lib/rockstar/track.rb +103 -0
  30. data/lib/rockstar/user.rb +194 -0
  31. data/lib/rockstar/version.rb +3 -0
  32. data/rockstar.gemspec +138 -0
  33. data/test/fixtures/xml/album/getinfo_album_Some_Hearts_artist_Carrie_Underwood.xml +63 -0
  34. data/test/fixtures/xml/artist/getsimilar_artist_Metallica.xml +1203 -0
  35. data/test/fixtures/xml/artist/gettopalbums_artist_Metallica.xml +704 -0
  36. data/test/fixtures/xml/artist/gettopfans_artist_Metallica.xml +504 -0
  37. data/test/fixtures/xml/artist/gettoptags_artist_Metallica.xml +403 -0
  38. data/test/fixtures/xml/artist/gettoptracks_artist_Metallica.xml +800 -0
  39. data/test/fixtures/xml/tag/gettopalbums_tag_rock.xml +654 -0
  40. data/test/fixtures/xml/tag/gettopartists_tag_rock.xml +504 -0
  41. data/test/fixtures/xml/tag/gettoptags.xml +1253 -0
  42. data/test/fixtures/xml/tag/gettoptracks_tag_rock.xml +704 -0
  43. data/test/fixtures/xml/track/gettopfans_artist_Carrie_Underwood_track_Before_He_Cheats.xml +504 -0
  44. data/test/fixtures/xml/track/gettoptags_artist_Carrie_Underwood_track_Before_He_Cheats.xml +403 -0
  45. data/test/fixtures/xml/track/love_artist_Carrie_Underwood_sk_tag_track_Before_He_Cheats.xml +2 -0
  46. data/test/fixtures/xml/user/getfriends_user_jnunemaker.xml +173 -0
  47. data/test/fixtures/xml/user/getinfo_user_jnunemaker.xml +22 -0
  48. data/test/fixtures/xml/user/getinfo_user_oaknd1.xml +22 -0
  49. data/test/fixtures/xml/user/getinfo_user_wharle.xml +22 -0
  50. data/test/fixtures/xml/user/getlovedtracks_user_jnunemaker.xml +775 -0
  51. data/test/fixtures/xml/user/getneighbours_user_jnunemaker.xml +503 -0
  52. data/test/fixtures/xml/user/getrecenttracks_user_jnunemaker.xml +133 -0
  53. data/test/fixtures/xml/user/getrecommendedartists_sk_token_user_jnunemaker.xml +553 -0
  54. data/test/fixtures/xml/user/gettopalbums_user_jnunemaker.xml +704 -0
  55. data/test/fixtures/xml/user/gettopartists_user_jnunemaker.xml +554 -0
  56. data/test/fixtures/xml/user/gettoptags_user_jnunemaker.xml +63 -0
  57. data/test/fixtures/xml/user/gettoptracks_user_jnunemaker.xml +750 -0
  58. data/test/fixtures/xml/user/getweeklyalbumchart_from_1138536002_to_1139140802_user_jnunemaker.xml +143 -0
  59. data/test/fixtures/xml/user/getweeklyalbumchart_from__to__user_jnunemaker.xml +31 -0
  60. data/test/fixtures/xml/user/getweeklyartistchart_from_1138536002_to_1139140802_user_jnunemaker.xml +201 -0
  61. data/test/fixtures/xml/user/getweeklyartistchart_from__to__user_jnunemaker.xml +21 -0
  62. data/test/fixtures/xml/user/getweeklychartlist_user_jnunemaker.xml +232 -0
  63. data/test/fixtures/xml/user/getweeklytrackchart_from_1138536002_to_1139140802_user_jnunemaker.xml +883 -0
  64. data/test/fixtures/xml/user/getweeklytrackchart_from__to__user_jnunemaker.xml +423 -0
  65. data/test/mocks/rest.rb +61 -0
  66. data/test/test_helper.rb +17 -0
  67. data/test/unit/test_album.rb +67 -0
  68. data/test/unit/test_artist.rb +69 -0
  69. data/test/unit/test_chart.rb +35 -0
  70. data/test/unit/test_playing.rb +53 -0
  71. data/test/unit/test_scrobble.rb +69 -0
  72. data/test/unit/test_simpleauth.rb +45 -0
  73. data/test/unit/test_tag.rb +57 -0
  74. data/test/unit/test_tokenauth.rb +45 -0
  75. data/test/unit/test_track.rb +44 -0
  76. data/test/unit/test_user.rb +252 -0
  77. metadata +190 -0
@@ -0,0 +1,108 @@
1
+ # Getting information about an album such as release date and the summary or description on it is very easy.
2
+ #
3
+ # album = Rockstar::Album.new('Carrie Underwood', 'Some Hearts', :include_info => true)
4
+ #
5
+ # puts "Album: #{album.name}"
6
+ # puts "Artist: #{album.artist}"
7
+ # puts "URL: #{album.url}"
8
+ # puts "Release Date: #{album.release_date.strftime('%m/%d/%Y')}"
9
+ #
10
+ # Would output:
11
+ #
12
+ # Album: Some Hearts
13
+ # Artist: Carrie Underwood
14
+ # URL: http://www.last.fm/music/Carrie+Underwood/Some+Hearts
15
+ # Release Date: 11/15/2005
16
+ #
17
+ module Rockstar
18
+ class Album < Base
19
+ attr_accessor :artist, :artist_mbid, :name, :mbid, :playcount, :rank, :url, :release_date
20
+ attr_accessor :image_large, :image_medium, :image_small, :summary, :content, :images
21
+
22
+ # needed on top albums for tag
23
+ attr_accessor :count, :streamable
24
+
25
+ # needed for weekly album charts
26
+ attr_accessor :chartposition
27
+
28
+ class << self
29
+ def find(artist, name, o={})
30
+ new(artist, name, o)
31
+ end
32
+
33
+ def new_from_xml(xml, doc=nil)
34
+ name = (xml).at(:name).inner_html if (xml).at(:name)
35
+ name = xml['name'] if name.nil? && xml['name']
36
+ artist = (xml).at(:artist).at(:name).inner_html if (xml).at(:artist) && (xml).at(:artist).at(:name)
37
+ artist = (xml).at(:artist).inner_html if artist.nil? && (xml).at(:artist)
38
+ artist = doc.root['artist'] if artist.nil? && doc.root['artist']
39
+ a = Album.new(artist, name)
40
+ a.artist_mbid = (xml).at(:artist)['mbid'] if (xml).at(:artist) && (xml).at(:artist)['mbid']
41
+ a.artist_mbid = (xml).at(:artist).at(:mbid).inner_html if a.artist_mbid.nil? && (xml).at(:artist) && (xml).at(:artist).at(:mbid)
42
+ a.mbid = (xml).at(:mbid).inner_html if (xml).at(:mbid)
43
+ a.playcount = (xml).at(:playcount).inner_html if (xml).at(:playcount)
44
+ a.rank = xml['rank'] if xml['rank']
45
+ a.rank = (xml).at(:rank).inner_html if (xml).at(:rank) if a.rank.nil?
46
+ a.url = Base.fix_url((xml).at(:url).inner_html) if (xml).at(:url)
47
+
48
+ a.chartposition = a.rank
49
+
50
+ a.images = {}
51
+ (xml/'image').each {|image|
52
+ a.images[image['size']] = image.inner_html
53
+ }
54
+
55
+ a.image_large = a.images['large']
56
+ a.image_medium = a.images['medium']
57
+ a.image_small = a.images['small']
58
+
59
+ # needed on top albums for tag
60
+ a.count = xml['count'] if xml['count']
61
+ a.streamable = xml['streamable'] if xml['streamable']
62
+ a
63
+ end
64
+ end
65
+
66
+ def initialize(artist, name, o={})
67
+ raise ArgumentError, "Artist is required" if artist.blank?
68
+ raise ArgumentError, "Name is required" if name.blank?
69
+ @artist = artist
70
+ @name = name
71
+ options = {:include_info => false}.merge(o)
72
+ load_info() if options[:include_info]
73
+ end
74
+
75
+ def load_info
76
+ doc = self.class.fetch_and_parse("album.getInfo", {:artist => @artist, :album =>@name})
77
+ @url = Base.fix_url((doc).at(:url).inner_html)
78
+ @release_date = Time.parse((doc).at(:releasedate).inner_html.strip)
79
+
80
+ @images = {}
81
+ (doc/'image').each {|image|
82
+ @images[image['size']] = image.inner_html
83
+ }
84
+
85
+ @image_large = @images['large']
86
+ @image_medium = @images['medium']
87
+ @image_small = @images['small']
88
+
89
+ @mbid = (doc).at(:mbid).inner_html
90
+ @summary = (doc).at(:summary).to_plain_text
91
+ @content = (doc).at(:content).to_plain_text
92
+ end
93
+
94
+ def tracks
95
+ warn "[DEPRECATION] `tracks` is deprecated. The current api doesn't offer this function"
96
+ []
97
+ end
98
+
99
+ def image(which=:small)
100
+ which = which.to_s
101
+ raise ArgumentError unless ['small', 'medium', 'large', 'extralarge'].include?(which)
102
+ if (self.images.nil?)
103
+ load_info
104
+ end
105
+ self.images[which]
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,135 @@
1
+ # Below are examples of how to find an artists top tracks and similar artists.
2
+ #
3
+ # artist = Rockstar::Artist.new('Carrie Underwood')
4
+ #
5
+ # puts 'Top Tracks'
6
+ # puts "=" * 10
7
+ # artist.top_tracks.each { |t| puts "#{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 Rockstar
60
+ class Artist < Base
61
+ attr_accessor :name, :mbid, :playcount, :rank, :url, :thumbnail, :images, :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.chartposition = a.rank = xml['rank'] if xml['rank']
76
+ a.chartposition = a.rank = (xml).at(:rank).inner_html if (xml).at(:rank) if a.rank.nil?
77
+ a.url = Base.fix_url((xml).at(:url).inner_html) if (xml).at(:url)
78
+
79
+ a.images = {}
80
+ (xml/'image').each {|image|
81
+ a.images[image['size']] = image.inner_html
82
+ }
83
+
84
+ a.thumbnail = a.images['small']
85
+
86
+ a.match = (xml).at(:match).inner_html if (xml).at(:match)
87
+
88
+ # in top artists for tag
89
+ a.count = xml['count'] if xml['count']
90
+ a.streamable = xml['streamable'] if xml['streamable']
91
+ a.streamable = (xml).at(:streamable).inner_html == '1' ? 'yes' : 'no' if a.streamable.nil? && (xml).at(:streamable)
92
+ a
93
+ end
94
+ end
95
+
96
+ def initialize(name)
97
+ raise ArgumentError, "Name is required" if name.blank?
98
+ @name = name
99
+ end
100
+
101
+ def current_events(format=:ics)
102
+ warn "[DEPRECATION] `current_events` is deprecated. The current api doesn't offer this function"
103
+ []
104
+ end
105
+
106
+ def similar(force=false)
107
+ get_instance("artist.getSimilar", :similar, :artist, {:artist => @name}, force)
108
+ end
109
+
110
+ def top_fans(force=false)
111
+ get_instance("artist.getTopFans", :top_fans, :user, {:artist => @name}, force)
112
+ end
113
+
114
+ def top_tracks(force=false)
115
+ get_instance("artist.getTopTracks", :top_tracks, :track, {:artist => @name}, force)
116
+ end
117
+
118
+ def top_albums(force=false)
119
+ get_instance("artist.getTopAlbums", :top_albums, :album, {:artist => @name}, force)
120
+ end
121
+
122
+ def top_tags(force=false)
123
+ get_instance("artist.getTopTags", :top_tags, :tag, {:artist => @name}, force)
124
+ end
125
+
126
+ def image(which=:medium)
127
+ which = which.to_s
128
+ raise ArgumentError unless ['small', 'medium', 'large', 'extralarge'].include?(which)
129
+ if (self.images.nil?)
130
+ load_info
131
+ end
132
+ self.images[which]
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,21 @@
1
+ module Rockstar
2
+
3
+ class Auth < Base
4
+ # Returns the token for a session. You have to use
5
+ # TokenAuth first and then use this class with the token
6
+ # that is given by last.fm to create a session token
7
+ # This session token can be stored in your database, it is
8
+ # not expiring.
9
+ # See Rockstar::TokenAuth for a detailed example
10
+ def session(token)
11
+ doc = self.class.fetch_and_parse("auth.getSession", {:token => token}, true)
12
+ Rockstar::Session.new_from_xml(doc)
13
+ end
14
+
15
+ def token
16
+ doc = self.class.fetch_and_parse("auth.getToken", {}, true)
17
+ token = (doc).at(:token).inner_html if (doc).at(:token)
18
+ token
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,37 @@
1
+ module Rockstar
2
+
3
+ API_URL = 'http://ws.audioscrobbler.com/'
4
+ API_VERSION = '2.0'
5
+
6
+ class Base
7
+ class << self
8
+ def connection
9
+ @connection ||= REST::Connection.new(API_URL + API_VERSION + "/")
10
+ end
11
+
12
+ def fetch_and_parse(resource, params = {}, sign_request = false)
13
+ Hpricot::XML(connection.get(resource, sign_request, params))
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, params, force=false, sign_request = false)
20
+ rockstar_class = "rockstar/#{element.to_s}".camelize.constantize
21
+
22
+ if instance_variable_get("@#{instance_name}").nil? || force
23
+ doc = self.class.fetch_and_parse(api_method, params, sign_request)
24
+ elements = (doc/element).inject([]) { |elements, el| elements << rockstar_class.new_from_xml(el, doc); elements }
25
+ instance_variable_set("@#{instance_name}", elements)
26
+ end
27
+ instance_variable_get("@#{instance_name}")
28
+ end
29
+
30
+ def self.fix_url(url)
31
+ if (!url.blank? && !url.match("^http://"))
32
+ url = "http://#{url}"
33
+ end
34
+ url
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,31 @@
1
+ module Rockstar
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 Rockstar
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 Rockstar::SimpleAuth
11
+ @now_playing_url = args[:now_playing_url] # from Rockstar::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('', false, query)
38
+
39
+ case @status
40
+ when /OK/
41
+
42
+ when /BADSESSION/
43
+ raise BadSessionError # rerun Rockstar::SimpleAuth#handshake!
44
+ else
45
+ raise RequestFailedError
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,66 @@
1
+ require 'net/https'
2
+ require 'digest/md5'
3
+
4
+ module Rockstar
5
+ module REST
6
+ class Connection
7
+ def initialize(base_url, args = {})
8
+ @base_url = base_url
9
+ @username = args[:username]
10
+ @password = args[:password]
11
+ end
12
+
13
+ def get(resource, sign_request, args = {})
14
+ request(resource, "get", args, sign_request)
15
+ end
16
+
17
+ def post(resource, sign_request, args = nil)
18
+ request(resource, "post", args, sign_request)
19
+ end
20
+
21
+ def request(resource, method = "get", args = {}, sign_request=false)
22
+ url = URI.parse(@base_url)
23
+
24
+ if (!resource.blank?)
25
+ args[:method] = resource
26
+ args[:api_key]= Rockstar.lastfm_api_key
27
+ end
28
+
29
+ if args
30
+ sorted_keys = args.keys.sort_by{|k|k.to_s}
31
+ query = sorted_keys.collect { |k| "%s=%s" % [escape(k.to_s), escape(args[k].to_s)] }.join("&")
32
+
33
+ if !args[:sk].nil? ||sign_request # Session Key available => sign the request or sign_request = true?
34
+ signed = sorted_keys.collect {|k| "%s%s" % [k.to_s, args[k].to_s]}.join()
35
+
36
+ auth = Digest::MD5.hexdigest("#{signed}#{Rockstar.lastfm_api_secret}")
37
+ query += "&api_sig=#{auth}"
38
+ end
39
+ url.query = query
40
+ end
41
+
42
+ case method
43
+ when "get"
44
+ req = Net::HTTP::Get.new(url.request_uri)
45
+ when "post"
46
+ req = Net::HTTP::Post.new(url.request_uri)
47
+ end
48
+
49
+ if @username and @password
50
+ req.basic_auth(@username, @password)
51
+ end
52
+
53
+ http = Net::HTTP.new(url.host, url.port)
54
+ http.use_ssl = (url.port == 443)
55
+
56
+ res = http.start() { |conn| conn.request(req) }
57
+ res.body
58
+ end
59
+
60
+ private
61
+ def escape(str)
62
+ URI.escape(str, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
63
+ end
64
+ end
65
+ end
66
+ end