sandro-rlastfm 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,5 @@
1
+ == 0.0.2 / 2009-03-09
2
+ * Birthday
3
+
4
+ == 0.0.1 / 2009-03-09
5
+ * Initial Release
data/README.txt ADDED
@@ -0,0 +1,63 @@
1
+ rlastfm
2
+ by Sandro Turriate
3
+ http://turriate.com
4
+
5
+ == DESCRIPTION:
6
+
7
+ Extremely minimal Last.fm wrapper.
8
+ This wrapper allows you to lookup an Artist by name, get their albums, and the tracks for each album.
9
+
10
+ == SYNOPSIS:
11
+
12
+ >> Rlastfm.api_key = "your lastfm api key"
13
+ >> Rlastfm.debug = true # watch the requests you make
14
+
15
+ >> artist = Rlastfm::Artist.new('radiohead')
16
+ => #<Rlastfm::Artist:0x115bed0 @initialized_name="radiohead">
17
+
18
+ >> artist.name
19
+ => "Radiohead"
20
+
21
+ >> artist.albums
22
+ [#<Rlastfm::Album:0xd598>, #<Rlastfm::Album:0xd599>]
23
+
24
+ >> artist.albums.first.name
25
+ => "In Rainbows"
26
+
27
+ >> artist.albums.first.tracks
28
+ [#<Rlastfm::Track:0x18f38f0>, #<Rlastfm::Track:0x18f38f1>]
29
+
30
+ == REQUIREMENTS:
31
+
32
+ * hpricot
33
+ * httparty
34
+ * fakeweb (to run specs)
35
+
36
+ == INSTALL:
37
+
38
+ FIXME
39
+
40
+ == LICENSE:
41
+
42
+ (The MIT License)
43
+
44
+ Copyright (c) 2008
45
+
46
+ Permission is hereby granted, free of charge, to any person obtaining
47
+ a copy of this software and associated documentation files (the
48
+ 'Software'), to deal in the Software without restriction, including
49
+ without limitation the rights to use, copy, modify, merge, publish,
50
+ distribute, sublicense, and/or sell copies of the Software, and to
51
+ permit persons to whom the Software is furnished to do so, subject to
52
+ the following conditions:
53
+
54
+ The above copyright notice and this permission notice shall be
55
+ included in all copies or substantial portions of the Software.
56
+
57
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
58
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
59
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
60
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
61
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
62
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
63
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,35 @@
1
+ # Look in the tasks/setup.rb file for the various options that can be
2
+ # configured in this Rakefile. The .rake files in the tasks directory
3
+ # are where the options are used.
4
+
5
+ begin
6
+ require 'bones'
7
+ Bones.setup
8
+ rescue LoadError
9
+ begin
10
+ load 'tasks/setup.rb'
11
+ rescue LoadError
12
+ raise RuntimeError, '### please install the "bones" gem ###'
13
+ end
14
+ end
15
+
16
+ ensure_in_path 'lib'
17
+ require 'rlastfm'
18
+
19
+ task :default => 'spec:run'
20
+
21
+ PROJ.name = 'rlastfm'
22
+ PROJ.summary = "minimal Last.fm wrapper."
23
+ PROJ.description = %Q(#{PROJ.summary}
24
+ This wrapper allows you to lookup an Artist by name, get their albums, and the tracks for each album.)
25
+ PROJ.authors = 'Sandro Turriate'
26
+ PROJ.email = 'sandro.turriate@gmail.com'
27
+ PROJ.url = 'http://turriate.com'
28
+ PROJ.version = Rlastfm::VERSION
29
+ PROJ.rubyforge.name = 'rlastfm'
30
+ PROJ.gem.development_dependencies = []
31
+
32
+ PROJ.spec.opts << '--color'
33
+ PROJ.ignore_file = '.gitignore'
34
+
35
+ # EOF
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), %w(lib rlastfm))
@@ -0,0 +1,30 @@
1
+ class Hash
2
+ def stringify_keys
3
+ inject({}) do |options, (key, value)|
4
+ options[key.to_s] = value
5
+ options
6
+ end
7
+ end
8
+
9
+ # Destructively convert all keys to strings.
10
+ def stringify_keys!
11
+ keys.each do |key|
12
+ self[key.to_s] = delete(key)
13
+ end
14
+ self
15
+ end
16
+
17
+ # Return a new hash with all keys converted to symbols.
18
+ def symbolize_keys
19
+ inject({}) do |options, (key, value)|
20
+ options[(key.to_sym rescue key) || key] = value
21
+ options
22
+ end
23
+ end
24
+
25
+ # Destructively convert all keys to symbols.
26
+ def symbolize_keys!
27
+ self.replace(self.symbolize_keys)
28
+ end
29
+ end
30
+
data/lib/rlastfm.rb ADDED
@@ -0,0 +1,83 @@
1
+ module Rlastfm
2
+ # :stopdoc:
3
+ VERSION = '0.0.2'
4
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
5
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
6
+
7
+ # :startdoc:
8
+
9
+ # Returns the version string for the library.
10
+ #
11
+ def self.version
12
+ VERSION
13
+ end
14
+
15
+ # Returns the library path for the module. If any arguments are given,
16
+ # they will be joined to the end of the libray path using
17
+ # <tt>File.join</tt>.
18
+ #
19
+ def self.libpath( *args )
20
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
21
+ end
22
+
23
+ # Returns the lpath for the module. If any arguments are given,
24
+ # they will be joined to the end of the path using
25
+ # <tt>File.join</tt>.
26
+ #
27
+ def self.path( *args )
28
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
29
+ end
30
+
31
+ # Utility method used to require all files ending in .rb that lie in the
32
+ # directory below this file that has the same name as the filename passed
33
+ # in. Optionally, a specific _directory_ name can be passed in such that
34
+ # the _filename_ does not have to be equivalent to the directory.
35
+ #
36
+ def self.require_all_libs_relative_to( fname, dir = nil )
37
+ dir ||= ::File.basename(fname, '.*')
38
+ search_me = ::File.expand_path(
39
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
40
+
41
+ Dir.glob(search_me).sort.each {|rb| require rb}
42
+ end
43
+
44
+ class << self
45
+ attr_accessor :api_key, :debug
46
+ end
47
+
48
+ self.debug = false
49
+ end # module Rlastfm
50
+
51
+ %w(rubygems hpricot open-uri httparty).each { |l| require l }
52
+
53
+ module HTTParty
54
+ class Request
55
+ def perform_with_debug
56
+ if Rlastfm.debug
57
+ puts "Performing request to: #{uri.to_s}"
58
+ end
59
+ perform_without_debug
60
+ end
61
+
62
+ alias_method :perform_without_debug, :perform
63
+ alias_method :perform, :perform_with_debug
64
+
65
+ def query_string_with_sorting(uri)
66
+ unsorted = query_string_without_sorting(uri)
67
+ unsorted.split("&").sort.join("&") if unsorted
68
+ end
69
+
70
+ alias_method :query_string_without_sorting, :query_string
71
+ alias_method :query_string, :query_string_with_sorting
72
+ end
73
+ end
74
+
75
+
76
+ helper_libs = File.join(File.dirname(__FILE__), %w(rlastfm helpers *.rb))
77
+ Dir.glob(helper_libs).each { |l| require l }
78
+
79
+ require File.join(File.dirname(__FILE__), 'core_extensions')
80
+
81
+ Rlastfm.require_all_libs_relative_to(__FILE__)
82
+
83
+ # EOF
@@ -0,0 +1,42 @@
1
+ module Rlastfm
2
+ class Album
3
+ include Helpers::Scraper
4
+
5
+ attr_reader :raw
6
+ attr_accessor :artist, :images, :name, :play_count, :rank, :url
7
+
8
+ def initialize(hash={})
9
+ @raw = hash.stringify_keys!
10
+ @images = hash['images'] || hash['image']
11
+ @name = hash['name']
12
+ @play_count = hash['play_count'] || hash['playcount']
13
+ @rank = hash['rank']
14
+ @url = hash['url']
15
+ if (a = hash['artist'])
16
+ @artist = a.is_a?(Hash) ? a['name'] : a
17
+ end
18
+ end
19
+
20
+ def tracks
21
+ @tracks ||= get_tracks
22
+ end
23
+
24
+ private
25
+
26
+ def get_tracks
27
+ doc = doc_for(url)
28
+ artist_container = doc.at(".albumHead h1")
29
+ artist = artist_container.at("a").inner_text
30
+ track_container = doc.at("#albumTracklist")
31
+
32
+ track_container.search("tbody tr").map do |row|
33
+ Track.new \
34
+ :artist => artist,
35
+ :album => name,
36
+ :number => row.at(".positionCell").to_plain_text,
37
+ :name => row.at(".subjectCell a").inner_text,
38
+ :duration => row.at(".durationCell").to_plain_text
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,67 @@
1
+ module Rlastfm
2
+ class Artist
3
+ extend Helpers::RemoteAccessor
4
+ include Helpers::Scraper
5
+ include HTTParty
6
+
7
+ base_uri "http://ws.audioscrobbler.com/2.0/"
8
+
9
+ remote_reader :name, :mbid, :stats, :url, :bio
10
+ remote_reader :from => :image, :to => :images
11
+
12
+ def self.top_twenty
13
+ doc = doc_for("/charts/artist")
14
+ top_twenty = doc.at('.mediumImageChart')
15
+ top_twenty.search('tr').map do |artist_row|
16
+ name = artist_row.search('.subject < a').last
17
+ new name
18
+ end
19
+ end
20
+
21
+ def initialize(name)
22
+ self.class.default_params :api_key => Rlastfm.api_key
23
+ @initialized_name = name
24
+ end
25
+
26
+ def album_by_name(album_name)
27
+ albums.detect {|a| a.name == album_name}
28
+ end
29
+
30
+ def albums
31
+ @albums ||= begin
32
+ response = self.class.get('', :query => {:method => "artist.gettopalbums", :artist => name})
33
+ album_collection = response['lfm']['topalbums']['album']
34
+ album_collection.map{|album_attrs| Album.new album_attrs} if album_collection
35
+ end
36
+ end
37
+
38
+ def exists?
39
+ ! mbid.nil?
40
+ end
41
+
42
+ def raw
43
+ @raw ||= remote_sync!
44
+ end
45
+
46
+ def similar_artists
47
+ @similar_artists ||= begin
48
+ raw['similar']['artist'].map do |artist|
49
+ Artist.new artist['name']
50
+ end
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def remote_sync!
57
+ response = self.class.get('', :query => {:method => 'artist.getinfo', :artist => @initialized_name})
58
+ @synced = true
59
+ @raw = response['lfm']['artist']
60
+ end
61
+
62
+ def synced?
63
+ @synced == true
64
+ end
65
+ end
66
+ end
67
+
@@ -0,0 +1,50 @@
1
+ module Rlastfm
2
+ module Helpers
3
+ module RemoteAccessor
4
+ def remote_reader(*accessors)
5
+ accessors.each do |reader|
6
+ case reader
7
+ when Symbol
8
+ add_reader reader
9
+ when Hash
10
+ add_mapped_reader reader
11
+ else
12
+ raise "remote_reader requires a Hash or Symbol"
13
+ end
14
+ end
15
+ end
16
+
17
+ def add_mapped_reader(mapping)
18
+ remote_reader_name = mapping.delete(:from)
19
+ reader_name = mapping.delete(:to)
20
+ raise "remote_accessor_mapping requires :from and :to arguments" unless remote_reader_name && reader_name
21
+
22
+ add_reader reader_name, remote_reader_name
23
+ end
24
+
25
+ def add_reader(reader_name, remote_reader_name=nil)
26
+ remote_reader_name ||= reader_name
27
+ define_method reader_name do
28
+ raw[remote_reader_name.to_s]
29
+ end
30
+
31
+ memoize_method reader_name
32
+ end
33
+
34
+ def memoize_method(method_name)
35
+ i_var_name = :"@#{method_name}"
36
+ method_without = :"#{method_name}_without_memoization"
37
+ method_with = :"#{method_name}_with_memoization"
38
+
39
+ class_eval do
40
+ define_method method_with do
41
+ instance_variable_get(i_var_name) || instance_variable_set(i_var_name, send(method_without))
42
+ end
43
+ alias_method method_without, method_name
44
+ alias_method method_name, method_with
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
@@ -0,0 +1,38 @@
1
+ module Rlastfm
2
+ module Helpers
3
+ module Scraper
4
+ def self.included(base)
5
+ base.class_eval do
6
+ protected
7
+
8
+ extend ProtectedClassMethods
9
+ include ProtectedInstanceMethods
10
+ end
11
+ end
12
+
13
+ module ProtectedClassMethods
14
+ def last_fm_uri(path)
15
+ URI::HTTP.build :host => "www.last.fm", :path => path
16
+ end
17
+
18
+ def doc_for(url_or_path)
19
+ url_or_path = URI.parse(url_or_path)
20
+ url = url_or_path.relative? ? last_fm_uri(url_or_path.to_s).to_s : url_or_path
21
+
22
+ if Rlastfm.debug
23
+ puts "Releasing scraper on: #{url}"
24
+ end
25
+
26
+ doc = Hpricot open(url)
27
+ end
28
+ end
29
+
30
+ module ProtectedInstanceMethods
31
+ def doc_for(url_or_path)
32
+ self.class.doc_for(url_or_path)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+
@@ -0,0 +1,10 @@
1
+ module Rlastfm
2
+ class Track
3
+ attr_accessor :album, :artist, :duration, :name, :number
4
+
5
+ def initialize(hash={})
6
+ hash.each {|k,v| instance_variable_set("@#{k}", v) }
7
+ end
8
+ end
9
+ end
10
+
@@ -0,0 +1,23 @@
1
+ module Rlastfm
2
+ class Venue
3
+ include Helpers::Scraper
4
+
5
+ def initialize(id)
6
+ @venue_id = id
7
+ end
8
+
9
+ def id
10
+ @venue_id
11
+ end
12
+
13
+ def photo
14
+ @photo ||= begin
15
+ url = "/venue/#{id}"
16
+ doc = doc_for(url)
17
+ image = doc.at('#catalogueImage')
18
+ image.attributes['src']
19
+ end
20
+ end
21
+ end
22
+ end
23
+