]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - data/rbot/plugins/weather.rb
07584d883f7f7a45381ce84630e6b21664f64f75
[user/henk/code/ruby/rbot.git] / data / rbot / plugins / weather.rb
1 # Weather plugin for rbot
2 #
3 # NOAA National Weather Service support by MrChucho (mrchucho@mrchucho.net)
4 # Copyright (C) 2006 Ralph M. Churchill
5 #
6 # Weather Undeground support by Giuseppe "Oblomov" Bilotta <giuseppe.bilotta@gmail.com>
7 # Copyright (C) 2006 Giuseppe Bilotta
8
9 require 'uri'
10 require 'rexml/document'
11
12 # Wraps NOAA National Weather Service information
13 class CurrentConditions
14     def initialize(station)
15         @station = station
16         @url = "http://www.nws.noaa.gov/data/current_obs/#{@station.upcase}.xml"
17         @etag = String.new
18         @mtime = Time.mktime(0)
19         @current_conditions = String.new
20         @iscached = false
21     end
22     def update
23         begin
24             open(@url,"If-Modified-Since" => @mtime.rfc2822) do |feed|
25             # open(@url,"If-None-Match"=>@etag) do |feed|
26                 @etag = feed.meta['etag']
27                 @mtime = feed.last_modified
28                 cc_doc = (REXML::Document.new feed).root
29                 @iscached = false
30                 @current_conditions = parse(cc_doc)
31             end
32         rescue OpenURI::HTTPError => e
33             case e
34             when /304/:
35                 @iscached = true
36             when /404/:
37                 raise "Data for #{@station} not found"
38             else
39                 raise "Error retrieving data: #{e}"
40             end
41         end
42         @current_conditions # +" Cached? "+ ((@iscached) ? "Y" : "N")
43     end
44     def parse(cc_doc)
45         cc = Hash.new
46         cc_doc.elements.each do |c|
47             cc[c.name.to_sym] = c.text
48         end
49         "At #{cc[:observation_time_rfc822]}, the wind was #{cc[:wind_string]} at #{cc[:location]} (#{cc[:station_id]}). The temperature was #{cc[:temperature_string]}#{heat_index_or_wind_chill(cc)}, and the pressure was #{cc[:pressure_string]}. The relative humidity was #{cc[:relative_humidity]}%. Current conditions are #{cc[:weather]} with #{cc[:visibility_mi]}mi visibility."
50     end
51 private
52     def heat_index_or_wind_chill(cc)
53         hi = cc[:heat_index_string]
54         wc = cc[:windchill_string]
55         if hi != 'NA' then
56             " with a heat index of #{hi}"
57         elsif wc != 'NA' then
58             " with a windchill of #{wc}"
59         else
60             ""
61         end
62     end
63 end
64
65 class WeatherPlugin < Plugin
66   
67   def help(plugin, topic="")
68     case topic
69     when "nws"
70       "weather nws <station> => display the current conditions at the location specified by the NOAA National Weather Service station code <station> ( lookup your station code at http://www.nws.noaa.gov/data/current_obs/ )"
71     when "station", "wu"
72       "weather [<units>] <location> => display the current conditions at the location specified, looking it up on the Weather Underground site; you can use 'station <code>' to look up data by station code ( lookup your station code at http://www.weatherunderground.com/ ); you can optionally set <units>  to 'metric' or 'english' if you only want data with the units; use 'both' for units to go back to having both." 
73     else
74       "weather information lookup. Looks up weather information for the last location you specified. See topics 'nws' and 'wu' for more information"
75     end
76   end
77   
78   def initialize
79     super
80
81     @nws_cache = Hash.new
82
83     @wu_url         = "http://mobile.wunderground.com/cgi-bin/findweather/getForecast?brand=mobile%s&query=%s"
84     @wu_station_url = "http://mobile.wunderground.com/auto/mobile%s/global/stations/%s.html"
85   end
86   
87   def weather(m, params)
88     if params[:where].empty?
89       if @registry.has_key?(m.sourcenick)
90         where = @registry[m.sourcenick]
91         debug "Loaded weather info #{where.inspect} for #{m.sourcenick}"
92
93         service = where.first.to_sym
94         loc = where[1].to_s
95         units = params[:units] || where[2] rescue nil
96       else
97         debug "No weather info for #{m.sourcenick}"
98         m.reply "I don't know where you are yet, #{m.sourcenick}. See 'help weather nws' or 'help weather wu' for additional help"
99         return
100       end
101     else
102       where = params[:where]
103       if ['nws','station'].include?(where.first)
104         service = where.first.to_sym
105         loc = where[1].to_s
106       else
107         service = :wu
108         loc = where.to_s
109       end
110       units = params[:units]
111     end
112
113     if loc.empty?
114       debug "No weather location found for #{m.sourcenick}"
115       m.reply "I don't know where you are yet, #{m.sourcenick}. See 'help weather nws' or 'help weather wu' for additional help"
116       return
117     end
118
119     wu_units = String.new
120     if units
121       case units.to_sym
122       when :english, :metric
123         wu_units = "_#{units}"
124       when :both
125       else
126         m.reply "Ignoring unknown units #{units}"
127         wu_units = String.new
128       end
129     end
130
131     case service
132     when :nws
133       nws_describe(m, loc)
134     when :station
135       wu_station(m, loc, wu_units)
136     when :wu
137       wu_weather(m, loc, wu_units)
138     end
139
140     @registry[m.sourcenick] = [service, loc, units]
141   end
142
143   def nws_describe(m, where)
144     if @nws_cache.has_key?(where) then
145         met = @nws_cache[where]
146     else
147         met = CurrentConditions.new(where)
148     end
149     if met
150       begin
151         m.reply met.update
152         @nws_cache[where] = met
153       rescue => e
154         m.reply e.message
155       end
156     else
157       m.reply "couldn't find weather data for #{where}"
158     end
159   end
160
161   def wu_station(m, where, units)
162     begin
163       xml = @bot.httputil.get_cached(@wu_station_url % [units, URI.escape(where)])
164       case xml
165       when nil
166         m.reply "couldn't retrieve weather information, sorry"
167         return
168       when /Search not found:/
169         m.reply "no such station found (#{where})"
170         return
171       when /<table border.*?>(.*?)<\/table>/m
172         data = $1
173         m.reply wu_weather_filter(data)
174       else
175         debug xml
176         m.reply "something went wrong with the data for #{where}..."
177       end
178     rescue => e
179       m.reply "retrieving info about '#{where}' failed (#{e})"
180     end
181   end
182
183   def wu_weather(m, where, units)
184     begin
185       xml = @bot.httputil.get_cached(@wu_url % [units, URI.escape(where)])
186       case xml
187       when nil
188         m.reply "couldn't retrieve weather information, sorry"
189         return
190       when /City Not Found/
191         m.reply "no such location found (#{where})"
192         return
193       when /<table border.*?>(.*?)<\/table>/m
194         data = $1
195         m.reply wu_weather_filter(data)
196       when /<a href="\/global\/stations\//
197         stations = xml.scan(/<a href="\/global\/stations\/(.*?)\.html">/)
198         m.reply "multiple stations available, use 'weather station <code>' where code is one of " + stations.join(", ")
199       else
200         debug xml
201         m.reply "something went wrong with the data from #{where}..."
202       end
203     rescue => e
204       m.reply "retrieving info about '#{where}' failed (#{e})"
205     end
206   end
207
208   def wu_weather_filter(stuff)
209     txt = stuff
210     txt.gsub!(/[\n\s]+/,' ')
211     data = Hash.new
212     txt.gsub!(/&nbsp;/, ' ')
213     txt.gsub!(/&#176;/, ' ') # degree sign
214     txt.gsub!(/<\/?b>/,'')
215     txt.gsub!(/<\/?span[^<>]*?>/,'')
216     txt.gsub!(/<img\s*[^<>]*?>/,'')
217     txt.gsub!(/<br\s?\/?>/,'')
218
219     result = Array.new
220     if txt.match(/<\/a>\s*Updated:\s*(.*?)\s*Observed at\s*(.*?)\s*<\/td>/)
221       result << ("Weather info for %s (updated on %s)" % [$2, $1])
222     end
223     txt.scan(/<tr>\s*<td>\s*(.*?)\s*<\/td>\s*<td>\s*(.*?)\s*<\/td>\s*<\/tr>/) { |k, v|
224       next if v.empty?
225       next if ["-", "- approx.", "N/A", "N/A approx."].include?(v)
226       next if k == "Raw METAR"
227       result << ("%s: %s" % [k, v])
228     }
229     return result.join('; ')
230   end
231 end
232
233 plugin = WeatherPlugin.new
234 plugin.map 'weather :units *where', :defaults => {:where => false, :units => false}, :requirements => {:units => /metric|english|both/}