WoW auction house search library
Example usage;
eldre = Warcraft::AuctionHouse.new("Eldre'Thalas","Alliance")
gems = eldre.query("solid star of elune", :sort => 'bid', :order => 'asc')
puts gems[0] unless gems.empty?
[Solid Star of Elune]: 65g/70g (14:15 left)
require 'net/http' require 'delegate' module Warcraft # Stores details of an auction Auction = Struct.new(:name,:quality,:quantity,:seller,:bid,:buyout,:time) unless Auction class Auction def to_s times = if quantity > 1 then "x#{quantity}" end bo = if buyout then "/#{buyout}" end "[#{name}]#{times}: #{bid}#{bo} (#{time} left)" end end CategoryInfo = <<-EOF.strip weapon 1h axe 2h axe bow gun 1h mace 2h mace polearm 1h sword 2h sword staff fist misc dagger thrown crossbow wand fishing pole armor miscellaneous cloth leather mail plate shield libram idol totem container bag soul bag herb bag enchanting bag engineering bag gem bag mining bag consumable trade good projectile arrow bullet quiver quiver ammo pouch recipe book leather tailor engineering blacksmithing cooking alchemy first aid enchanting jewelcrafting reagent misc EOF ItemClasses=Hash.new {|h,k| raise "Unknown class #{k}"} ItemSubclasses=Hash.new {|h,k| raise "Unknown subclass #{k}"} ItemHierarchy = [ItemClasses,ItemSubclasses] ItemClasses[nil]=ItemSubclasses[nil]=nil CategoryInfo.each_with_index {|line,i| line =~ /^( *)(\w.*?)(\s*)$/ ItemHierarchy[$1.length][$2]=i+1 } Qualities = { "common" => 1, "white" => 1, "uncommon" => 2, "green" => 2, "rare" => 3, "blue" => 3, "epic" => 4, "purple" => 4 } Sorting = { "name" => 6, "level" => 2, "time" => 3, "seller" => 7, "bid" => 4, "price" => 4 } Order = { "ascending" => 0, "asc" => 0, "desc" => 1, "descending" => 1 } # Represents an AH to query. class AuctionHouse # e.g. AuctionHouse.new("Eldre'Thalas","Alliance") # Neutral AH are not available # Third parameter is 'EU' if you're on a european server. def initialize(realm,faction,locale="US") @realm,@locale = realm,locale @faction = case faction.to_s.downcase when "alliance" "Alliance" when "horde" "Horde" end end # Main entry point. # Usage: query("item name", :opt1 => "value", ...) # Item name can be nil. # Options are: # :min minimum level item # :max maximum level item # :quality white/green/blue/purple minimum item quality # :type weapon/armor/consumable/trade good/recipe etc # :subtype 1h axe/enchanting/soul bag etc - must have the correct type specified too # :seller seller to search for # :sort name/level/time/bid/seller sorting method # :order asc/desc sort order # :page which page of results (1-based). 10 returned at a time. # Returns an array of Warcraft::Auctions. def query(name, opts={}) params = {} params["realm"] = "#@realm #@locale" #"Eldre'Thalas US" params["faction"] = if @faction=="Alliance" then 1 else 2 end params["ItemName"] = name params["LevelStart"] = opts[:min] # Level range to filter by params["LevelEnd"] = opts[:max] params["Seller"] = opts[:seller] params["Rarity"] = Qualities[opts[:quality]] params["itemClassID"] = ItemClasses[opts[:type]] params["itemSubClass"] = ItemSubclasses[opts[:subtype]] params["pagenum"] = (opts[:page] || 1).to_s params["invenTypeID"] = nil # think this is the slot for armor searches - e.g. armor -> leather -> *shoulder*. unimplemented. params["sort_column"] = Sorting[opts[:sort]] params["sort_order"] = Order[opts[:order]] querytext = MiniJSON::encode(params) puts "Query:\n#{querytext}" if $DEBUG retrieve_results(querytext) end def retrieve_results(querytext) # We just post the JSON to the given URL and get a JSON encoded string back req = Net::HTTP::Post.new("/AuctionInfo.asmx/getAuctionInfo") req["Content-Type"]="application/json" response = Net::HTTP.new("www.auctionwowhouse.com",80).start {|http| http.request(req,querytext)} response.error! unless Net::HTTPSuccess === response resulttext = response.body resulttext = MiniJSON::decode(resulttext) puts "Response:\n#{resulttext}" if $DEBUG parse_results(resulttext) end # Web service returns a blob of ugly html, so we scrape the data out of it. Cover your eyes... def parse_results(resulttext) items = [] resulttext.scan(/<TR style='height.*?>(.*?)<\/TR>/) {|text,| # A row of the main table containing an item puts "Item:\n#{text}" if $DEBUG item = Auction.new text.scan(/<font color=(.*?)>(.*?)<\/font>/) {|color,name| item.quality = color item.name = name } text.scan(/<td width="81" .*?>(.*?)<\/td>/) {|name,| item.seller = name } text.scan(/<td width="75" .*?>(.*?)<\/td>/) {|q,| item.quantity = q.to_i } text.scan(/<td width="82" .*?><div .*?>(.*?)<\/div><\/td>/) {|time,| item.time = Duration.new(time.to_i) } text.scan(%r{(?:(\d+)<img border=0 src="/images/wow_40_jin\.gif">)?(?:(\d+)<img border=0 src="/images/wow_42_yin\.gif">)?(\d+)<img border=0 src="/images/wow_44_tong\.gif"><BR>(?:(?:(\d+)<img border=0 src="/images/wow_40_jin\.gif">)?(?:(\d+)<img border=0 src="/images/wow_42_yin\.gif">)?(\d+)<img border=0 src="/images/wow_44_tong\.gif">)?</a>}) {|bg,bs,bc,g,s,c| item.bid = Price.new(10000*bg.to_i + 100 * bs.to_i + bc.to_i) item.buyout = Price.new(10000*g.to_i + 100 * s.to_i + c.to_i) if g #buyout may be absent } items << item } items end end # Chunk of money (gold/silver/copper) class Price < DelegateClass(Integer); end unless Price # So we can reload the library without throwing class Price def gold; self / 10000; end def silver; (self / 100)%100; end def copper; self % 100; end def to_a; [gold,silver,copper]; end def inspect; "<#{self.class} #{self.to_i}>"; end # to display we pick the dominant unit (gold if it's at least 1g, else silver if it's at # least 1s, else copper) and round to the nearest def to_s big,little,letter = if gold > 0 [gold,silver,"g"] elsif silver > 0 [silver,copper,"s"] else [copper,0,"c"] end big += 1 if little >= 50 "#{big}#{letter}" end end # Measure of time (hours-minutes-seconds) class Duration < DelegateClass(Integer); end unless Duration class Duration def hours; self / 3600; end def minutes; (self / 60)%60; end def seconds; self % 60; end def to_a; [hours,minutes,seconds] end def to_s; "%d:%02d" % self.to_a; end # just show hours and minutes def inspect; "<#{self.class} #{to_s}>"; end end #Bare minimum JSON support to talk to the service module MiniJSON # Encode an object - we only support strings, numbers, and hashes def self.encode(x) case x when String "\"#{x.gsub('"','\\"')}\"" when Numeric x.to_s when Hash "{" + x.map{|k,v| encode(k)+": "+encode(v)}.join(", ") + "}" when NilClass "\"\"" else raise "Can't encode #{x.class}" end end # Decode an object - only supports a single string literal def self.decode(x) x.strip! if x.length > 1 and x[0]==?" and x[-1]==?" and x[-2]!=?\\ x[1..-2].gsub('\\"','"').gsub("\\'","'").gsub(/\\u([a-fA-F0-9]{4})/) {[$1.hex].pack("U") } else raise "Can't decode #{x.inspect}" end end end end