4 # :title: Geo IP Plugin
6 # Author:: Raine Virta <rane@kapsi.fi>
7 # Copyright:: (C) 2008 Raine Virta
10 # Resolves the geographic locations of users (network-wide) and IP addresses
13 class InvalidHostError < RuntimeError; end
15 GEO_IP_PRIMARY = "http://lakka.kapsi.fi:40086/lookup.yaml?host="
16 GEO_IP_SECONDARY = "http://www.geoiptool.com/en/?IP="
17 HOST_NAME_REGEX = /^[a-z0-9\-]+(?:\.[a-z0-9\-]+)*\.[a-z]{2,4}/i
20 :country => %r{Country:.*?<a href=".*?" target="_blank"> (.*?)</a>}m,
21 :region => %r{Region:.*?<a href=".*?" target="_blank">(.*?)</a>}m,
22 :city => %r{City:.*?<td align="left" class="arial_bold">(.*?)</td>}m,
23 :lat => %r{Latitude:.*?<td align="left" class="arial_bold">(.*?)</td>}m,
24 :lon => %r{Longitude:.*?<td align="left" class="arial_bold">(.*?)</td>}m
27 def self.valid_host?(hostname)
28 hostname =~ HOST_NAME_REGEX ||
29 hostname =~ Resolv::IPv4::Regex && (hostname.split(".").map { |e| e.to_i }.max <= 255)
32 def self.resolve(hostname)
33 raise InvalidHostError unless valid_host?(hostname)
35 yaml = Irc::Utils.bot.httputil.get(GEO_IP_PRIMARY+hostname)
38 return YAML::load(yaml)
41 raw = Irc::Utils.bot.httputil.get_response(GEO_IP_SECONDARY+hostname)
42 raw = raw.decompress_body(raw.raw_body)
44 REGEX.each { |key, regex| res[key] = Iconv.conv('utf-8', 'ISO-8859-1', raw.scan(regex).to_s) }
57 @hash[nick] = [] unless @hash[nick]
70 class GeoIpPlugin < Plugin
71 def help(plugin, topic="")
72 "geoip [<user|hostname|ip>] => returns the geographic location of whichever has been given -- note: user can be anyone on the network"
82 nick = m.whois[:nick].downcase
84 # need to see if the whois reply was invoked by this plugin
85 return unless @stack.has_nick?(nick)
88 msg = host2output(m.target.host, m.target.nick)
90 msg = "no such user on "+@bot.server.hostname.split(".")[-2]
92 @stack[nick].each do |source|
101 m.reply host2output(m.source.host, m.source.nick)
103 if m.replyto.class == Channel
105 # check if there is an user on the channel with nick same as input given
106 user = m.replyto.users.find { |user| user.nick == params[:input] }
109 m.reply host2output(user.host, user.nick)
114 # input is a host name or an IP
115 if GeoIP::valid_host?(params[:input])
116 m.reply host2output(params[:input])
118 # assume input is a nick
119 elsif params[:input] !~ /\./
120 nick = params[:input].downcase
122 @stack[nick] << m.replyto
125 m.reply "invalid input"
130 def host2output(host, nick=nil)
131 return "127.0.0.1 could not be res.. wait, what?" if host == "127.0.0.1"
134 geo = GeoIP::resolve(host)
136 raise if geo[:country].empty?
137 rescue GeoIP::InvalidHostError, RuntimeError
138 return _("#{nick ? "#{nick}'s location" : host} could not be resolved")
141 res = _("%{thing} is #{nick ? "from" : "located in"}") % {
142 :thing => (nick ? nick : Resolv::getaddress(host)),
143 :country => geo[:country]
146 res << " %{city}," % {
148 } unless geo[:city].to_s.empty?
150 res << " %{country}" % {
151 :country => geo[:country]
154 res << " (%{region})" % {
155 :region => geo[:region]
156 } unless geo[:region].to_s.empty? || geo[:region] == geo[:city]
162 plugin = GeoIpPlugin.new
163 plugin.map "geoip [:input]", :action => 'geoip', :thread => true