]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blobdiff - data/rbot/plugins/geoip.rb
refactor: httputil no longer core module see #38
[user/henk/code/ruby/rbot.git] / data / rbot / plugins / geoip.rb
old mode 100755 (executable)
new mode 100644 (file)
index 5f52d04..48391a1
@@ -9,40 +9,80 @@
 #
 # Resolves the geographic locations of users (network-wide) and IP addresses
 
-module GeoIP
+module ::GeoIP
   class InvalidHostError < RuntimeError; end
+  class BadAPIError < RuntimeError; end
 
-  GEO_IP_PRIMARY   = "http://lakka.kapsi.fi:40086/lookup.yaml?host="
-  GEO_IP_SECONDARY = "http://www.geoiptool.com/en/?IP="
-  HOST_NAME_REGEX  = /[a-z0-9\-]+(?:\.[a-z0-9\-]+)*\.[a-z]{2,4}/i
-
-  REGEX  = {
-    :country => %r{Country:.*?<a href=".*?" target="_blank"> (.*?)</a>}m,
-    :region  => %r{Region:.*?<a href=".*?" target="_blank">(.*?)</a>}m,
-    :city    => %r{City:.*?<td align="left" class="arial_bold">(.*?)</td>}m
-  }
+  HOST_NAME_REGEX  = /^[a-z0-9\-]+(?:\.[a-z0-9\-]+)*\.[a-z]{2,4}/i
 
   def self.valid_host?(hostname)
     hostname =~ HOST_NAME_REGEX ||
     hostname =~ Resolv::IPv4::Regex && (hostname.split(".").map { |e| e.to_i }.max <= 255)
   end
 
-  def self.resolve(hostname)
-    raise InvalidHostError unless valid_host?(hostname)
+  def self.geoiptool(bot, ip)
+    url = "http://www.geoiptool.com/en/?IP="
+    regexes  = {
+      :country => %r{Country:.*?<a href=".*?" target="_blank"> (.*?)</a>}m,
+      :region  => %r{Region:.*?<a href=".*?" target="_blank">(.*?)</a>}m,
+      :city    => %r{City:.*?<td align="left" class="arial_bold">(.*?)</td>}m,
+      :lat     => %r{Latitude:.*?<td align="left" class="arial_bold">(.*?)</td>}m,
+      :lon     => %r{Longitude:.*?<td align="left" class="arial_bold">(.*?)</td>}m
+    }
+    res = {}
+    raw = bot.httputil.get_response(url+ip)
+    raw = raw.decompress_body(raw.raw_body)
+
+    regexes.each { |key, regex| res[key] = raw.scan(regex).join('') }
+
+    return res
+  end
 
-    yaml = Irc::Utils.bot.httputil.get(GEO_IP_PRIMARY+hostname)
+  IPINFODB_URL = "http://api.ipinfodb.com/v2/ip_query.php?key=%{key}&ip=%{ip}"
 
-    if yaml
-      return YAML::load(yaml)
+  def self.ipinfodb(bot, ip)
+    key = bot.config['geoip.ipinfodb_key']
+    return if not key or key.empty?
+    url = IPINFODB_URL % {
+      :ip => ip,
+      :key => key
+    }
+    debug "Requesting #{url}"
+
+    xml = bot.httputil.get(url)
+
+    if xml
+      obj = REXML::Document.new(xml)
+      debug "Found #{obj}"
+      newobj = {
+        :country => obj.elements["Response"].elements["CountryName"].text,
+        :city => obj.elements["Response"].elements["City"].text,
+        :region => obj.elements["Response"].elements["RegionName"].text,
+      }
+      debug "Returning #{newobj}"
+      return newobj
     else
-      res = {}
-      raw = Irc::Utils.bot.httputil.get_response(GEO_IP_SECONDARY+hostname)
-      raw = raw.decompress_body(raw.raw_body)
+      raise InvalidHostError
+    end
+  end
+
+  JUMP_TABLE = {
+    "ipinfodb" => Proc.new { |bot, ip| ipinfodb(bot, ip) },
+    "geoiptool" => Proc.new { |bot, ip| geoiptool(bot, ip) },
+  }
 
-      REGEX.each { |key, regex| res[key] = Iconv.conv('utf-8', 'ISO-8859-1', raw.scan(regex).to_s) }
+  def self.resolve(bot, hostname, api)
+    raise InvalidHostError unless valid_host?(hostname)
 
-      return res
+    begin
+      ip = Resolv.getaddress(hostname)
+    rescue Resolv::ResolvError
+      raise InvalidHostError
     end
+
+    raise BadAPIError unless JUMP_TABLE.key?(api)
+
+    return JUMP_TABLE[api].call(bot, ip)
   end
 end
 
@@ -66,6 +106,13 @@ class Stack
 end
 
 class GeoIpPlugin < Plugin
+  Config.register Config::ArrayValue.new('geoip.sources',
+      :default => [ "ipinfodb", "geoiptool" ],
+      :desc => "Which API to use for lookups. Supported values: ipinfodb, geoiptool")
+  Config.register Config::StringValue.new('geoip.ipinfodb_key',
+      :default => "",
+      :desc => "API key for the IPinfoDB geolocation service")
+
   def help(plugin, topic="")
     "geoip [<user|hostname|ip>] => returns the geographic location of whichever has been given -- note: user can be anyone on the network"
   end
@@ -82,12 +129,13 @@ class GeoIpPlugin < Plugin
     # need to see if the whois reply was invoked by this plugin
     return unless @stack.has_nick?(nick)
 
+    if m.target
+      msg = host2output(m.target.host, m.target.nick)
+    else
+      msg = "no such user on "+@bot.server.hostname.split(".")[-2]
+    end
     @stack[nick].each do |source|
-      if m.target
-        @bot.say source, host2output(m.target.host, m.target.nick)
-      else
-        @bot.say source, "no such user on "+@bot.server.hostname.split(".")[-2]
-      end
+      @bot.say source, msg
     end
 
     @stack.clear(nick)
@@ -100,7 +148,7 @@ class GeoIpPlugin < Plugin
       if m.replyto.class == Channel
 
         # check if there is an user on the channel with nick same as input given
-        user = m.replyto.users.find { |user| user.nick == params[:input] }
+        user = m.replyto.users.find { |usr| usr.nick == params[:input] }
 
         if user
           m.reply host2output(user.host, user.nick)
@@ -127,34 +175,43 @@ class GeoIpPlugin < Plugin
   def host2output(host, nick=nil)
     return "127.0.0.1 could not be res.. wait, what?" if host == "127.0.0.1"
 
+    geo = {:country => ""}
     begin
-      geo = GeoIP::resolve(host)
-
-      raise if geo[:country].empty?
+      apis = @bot.config['geoip.sources']
+      apis.compact.each { |api|
+        geo = GeoIP::resolve(@bot, host, api)
+        if geo and geo[:country] != ""
+          break
+        end
+      }
     rescue GeoIP::InvalidHostError, RuntimeError
-      return _("#{nick ? "#{nick}'s location" : host} could not be resolved")
+      if nick
+        return _("%{nick}'s location could not be resolved") % { :nick => nick }
+      else
+        return _("%{host} could not be resolved") % { :host => host }
+      end
+    rescue GeoIP::BadAPIError
+      return _("The owner configured me to use an API that doesn't exist, bug them!")
     end
 
-    res = _("%{thing} is #{nick ? "from" : "located in"}") % {
-      :thing   => (nick ? nick : Resolv::getaddress(host)),
-      :country => geo[:country]
-    }
+    location = []
+    location << geo[:city] unless geo[:city].nil_or_empty?
+    location << geo[:region] unless geo[:region].nil_or_empty? or geo[:region] == geo[:city]
+    location << geo[:country] unless geo[:country].nil_or_empty?
 
-    res << " %{city}," % {
-      :city => geo[:city]
-    } unless geo[:city].to_s.empty?
+    if nick
+      res = _("%{nick} is from %{location}")
+    else
+      res = _("%{host} is located in %{location}")
+    end
 
-    res << " %{country}" % {
-      :country => geo[:country]
+    return res % {
+      :nick => nick,
+      :host => host,
+      :location => location.join(', ')
     }
-
-    res << " (%{region})" % {
-      :region  => geo[:region]
-    } unless geo[:region].to_s.empty? || geo[:region] == geo[:city]
-
-    return res
   end
 end
 
 plugin = GeoIpPlugin.new
-plugin.map "geoip [:input]", :action => 'geoip'
\ No newline at end of file
+plugin.map "geoip [:input]", :action => 'geoip', :thread => true