]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - data/rbot/plugins/geoip.rb
geoip: also read lat/lon coordinates
[user/henk/code/ruby/rbot.git] / data / rbot / plugins / geoip.rb
1 #-- vim:sw=2:et
2 #++
3 #
4 # :title: Geo IP Plugin
5 #
6 # Author:: Raine Virta <rane@kapsi.fi>
7 # Copyright:: (C) 2008 Raine Virta
8 # License:: GPL v2
9 #
10 # Resolves the geographic locations of users (network-wide) and IP addresses
11
12 module GeoIP
13   class InvalidHostError < RuntimeError; end
14
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
18
19   REGEX  = {
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
25   }
26
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)
30   end
31
32   def self.resolve(hostname)
33     raise InvalidHostError unless valid_host?(hostname)
34
35     yaml = Irc::Utils.bot.httputil.get(GEO_IP_PRIMARY+hostname)
36
37     if yaml
38       return YAML::load(yaml)
39     else
40       res = {}
41       raw = Irc::Utils.bot.httputil.get_response(GEO_IP_SECONDARY+hostname)
42       raw = raw.decompress_body(raw.raw_body)
43
44       REGEX.each { |key, regex| res[key] = Iconv.conv('utf-8', 'ISO-8859-1', raw.scan(regex).to_s) }
45
46       return res
47     end
48   end
49 end
50
51 class Stack
52   def initialize
53     @hash = {}
54   end
55
56   def [](nick)
57     @hash[nick] = [] unless @hash[nick]
58     @hash[nick]
59   end
60
61   def has_nick?(nick)
62     @hash.has_key?(nick)
63   end
64
65   def clear(nick)
66     @hash.delete(nick)
67   end
68 end
69
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"
73   end
74
75   def initialize
76     super
77
78     @stack = Stack.new
79   end
80
81   def whois(m)
82     nick = m.whois[:nick].downcase
83
84     # need to see if the whois reply was invoked by this plugin
85     return unless @stack.has_nick?(nick)
86
87     if m.target
88       msg = host2output(m.target.host, m.target.nick)
89     else
90       msg = "no such user on "+@bot.server.hostname.split(".")[-2]
91     end
92     @stack[nick].each do |source|
93       @bot.say source, msg
94     end
95
96     @stack.clear(nick)
97   end
98
99   def geoip(m, params)
100     if params.empty?
101       m.reply host2output(m.source.host, m.source.nick)
102     else
103       if m.replyto.class == Channel
104
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] }
107
108         if user
109           m.reply host2output(user.host, user.nick)
110           return
111         end
112       end
113
114       # input is a host name or an IP
115       if GeoIP::valid_host?(params[:input])
116          m.reply host2output(params[:input])
117
118       # assume input is a nick
119       elsif params[:input] !~ /\./
120         nick = params[:input].downcase
121
122         @stack[nick] << m.replyto
123         @bot.whois(nick)
124       else
125         m.reply "invalid input"
126       end
127     end
128   end
129
130   def host2output(host, nick=nil)
131     return "127.0.0.1 could not be res.. wait, what?" if host == "127.0.0.1"
132
133     begin
134       geo = GeoIP::resolve(host)
135
136       raise if geo[:country].empty?
137     rescue GeoIP::InvalidHostError, RuntimeError
138       return _("#{nick ? "#{nick}'s location" : host} could not be resolved")
139     end
140
141     res = _("%{thing} is #{nick ? "from" : "located in"}") % {
142       :thing   => (nick ? nick : Resolv::getaddress(host)),
143       :country => geo[:country]
144     }
145
146     res << " %{city}," % {
147       :city => geo[:city]
148     } unless geo[:city].to_s.empty?
149
150     res << " %{country}" % {
151       :country => geo[:country]
152     }
153
154     res << " (%{region})" % {
155       :region  => geo[:region]
156     } unless geo[:region].to_s.empty? || geo[:region] == geo[:city]
157
158     return res
159   end
160 end
161
162 plugin = GeoIpPlugin.new
163 plugin.map "geoip [:input]", :action => 'geoip', :thread => true