4 # :title: Weather plugin for rbot
6 # Author:: MrChucho (mrchucho@mrchucho.net): NOAA National Weather Service support
7 # Author:: Giuseppe "Oblomov" Bilotta <giuseppe.bilotta@gmail.com>
9 # Copyright:: (C) 2006 Ralph M. Churchill
10 # Copyright:: (C) 2006-2007 Giuseppe Bilotta
14 require 'rexml/document'
16 # Wraps NOAA National Weather Service information
17 class CurrentConditions
18 def initialize(station)
20 @url = "http://www.nws.noaa.gov/data/current_obs/#{@station.upcase}.xml"
22 @mtime = Time.mktime(0)
23 @current_conditions = String.new
28 open(@url,"If-Modified-Since" => @mtime.rfc2822) do |feed|
29 # open(@url,"If-None-Match"=>@etag) do |feed|
30 @etag = feed.meta['etag']
31 @mtime = feed.last_modified
32 cc_doc = (REXML::Document.new feed).root
34 @current_conditions = parse(cc_doc)
36 rescue OpenURI::HTTPError => e
41 raise "Data for #{@station} not found"
43 raise "Error retrieving data: #{e}"
46 @current_conditions # +" Cached? "+ ((@iscached) ? "Y" : "N")
50 cc_doc.elements.each do |c|
51 cc[c.name.to_sym] = c.text
53 "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."
56 def heat_index_or_wind_chill(cc)
57 hi = cc[:heat_index_string]
58 wc = cc[:windchill_string]
60 " with a heat index of #{hi}"
62 " with a windchill of #{wc}"
69 class WeatherPlugin < Plugin
71 Config.register Config::BooleanValue.new('weather.advisory',
73 :desc => "Should the bot report special weather advisories when any is present?")
74 Config.register Config::EnumValue.new('weather.units',
75 :values => ['metric', 'english', 'both'],
77 :desc => "Units to be used by default in Weather Underground reports")
80 def help(plugin, topic="")
83 "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/ )"
85 "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."
87 "weather information lookup. Looks up weather information for the last location you specified. See topics 'nws' and 'wu' for more information"
96 @wu_url = "http://mobile.wunderground.com/cgi-bin/findweather/getForecast?brand=mobile%s&query=%s"
97 @wu_station_url = "http://mobile.wunderground.com/auto/mobile%s/global/stations/%s.html"
100 def weather(m, params)
101 if params[:where].empty?
102 if @registry.has_key?(m.sourcenick)
103 where = @registry[m.sourcenick]
104 debug "Loaded weather info #{where.inspect} for #{m.sourcenick}"
106 service = where.first.to_sym
108 units = params[:units] || where[2] rescue nil
110 debug "No weather info for #{m.sourcenick}"
111 m.reply "I don't know where you are yet, #{m.sourcenick}. See 'help weather nws' or 'help weather wu' for additional help"
115 where = params[:where]
116 if ['nws','station'].include?(where.first)
117 service = where.first.to_sym
123 units = params[:units]
127 debug "No weather location found for #{m.sourcenick}"
128 m.reply "I don't know where you are yet, #{m.sourcenick}. See 'help weather nws' or 'help weather wu' for additional help"
132 wu_units = String.new
134 units ||= @bot.config['weather.units']
136 when :english, :metric
137 wu_units = "_#{units}"
140 m.reply "Ignoring unknown units #{units}"
147 wu_station(m, loc, wu_units)
149 wu_weather(m, loc, wu_units)
152 @registry[m.sourcenick] = [service, loc, units]
155 def nws_describe(m, where)
156 if @nws_cache.has_key?(where) then
157 met = @nws_cache[where]
159 met = CurrentConditions.new(where)
164 @nws_cache[where] = met
169 m.reply "couldn't find weather data for #{where}"
173 def wu_station(m, where, units)
175 xml = @bot.httputil.get(@wu_station_url % [units, CGI.escape(where)])
178 m.reply "couldn't retrieve weather information, sorry"
180 when /Search not found:/
181 m.reply "no such station found (#{where})"
183 when /<table border.*?>(.*?)<\/table>/m
185 m.reply wu_weather_filter(data)
186 wu_out_special(m, xml)
189 m.reply "something went wrong with the data for #{where}..."
192 m.reply "retrieving info about '#{where}' failed (#{e})"
196 def wu_weather(m, where, units)
198 xml = @bot.httputil.get(@wu_url % [units, CGI.escape(where)])
201 m.reply "couldn't retrieve weather information, sorry"
202 when /City Not Found/
203 m.reply "no such location found (#{where})"
206 xml.scan(/<table border.*?>(.*?)<\/table>/m).each do |match|
207 data += wu_weather_filter(match.first)
212 m.reply "couldn't parse weather data from #{where}"
214 wu_out_special(m, xml)
215 when /<a href="\/(?:global\/stations|US\/\w\w)\//
216 wu_weather_multi(m, xml)
219 m.reply "something went wrong with the data from #{where}..."
222 m.reply "retrieving info about '#{where}' failed (#{e})"
228 txt.gsub!(/[\n\s]+/,' ')
229 txt.gsub!(/ /, ' ')
230 txt.gsub!(/°/, ' ') # degree sign
231 txt.gsub!(/<\/?b>/,'')
232 txt.gsub!(/<\/?span[^<>]*?>/,'')
233 txt.gsub!(/<img\s*[^<>]*?>/,'')
234 txt.gsub!(/<br\s?\/?>/,'')
238 def wu_weather_multi(m, xml)
240 stations = xml.scan(/<td>\s*(?:<a href="([^?"]+\?feature=[^"]+)"\s*[^>]*><img [^>]+><\/a>\s*)?<a href="\/(?:global\/stations|US\/(\w\w))\/([^"]*?)\.html">(.*?)<\/a>\s*:\s*(.*?)<\/td>/m)
242 m.reply "multiple stations available, use 'weather station <code>' or 'weather <city, state>' as appropriate, for one of the following (current temp shown):"
249 if state # US station
250 (warning ? "*" : "") + ("%s, %s (%s): %s" % [loc, state, par, wu_clean(w)])
251 else # non-US station
252 (warning ? "*" : "") + ("station %s (%s): %s" % [loc, par, wu_clean(w)])
255 m.reply stations.join("; ")
258 def wu_check_special(xml)
260 # We only scan the first half to prevent getting the advisories twice
261 xml[0,xml.length/2].scan(%r{<a href="([^"]+\?[^"]*feature=warning#([^"]+))"[^>]*>([^<]+)</a>}) do
263 :url => "http://mobile.wunderground.com"+$1,
267 spec_rx = Regexp.new("<a name=\"#{special[:type]}\">(?:.+?)<td align=\"left\">\\s+(.+?)\\s+</td>\\s+</tr>\\s+</table>", Regexp::MULTILINE)
268 spec_xml = @bot.httputil.get(special[:url])
269 if spec_xml and spec_td = spec_xml.match(spec_rx)
270 special.merge!(:text => spec_td.captures.first.ircify_html)
277 def wu_out_special(m, xml)
278 return unless @bot.config['weather.advisory']
279 specials = wu_check_special(xml)
281 specials.each do |special|
282 special.merge!(:underline => Underline)
284 m.reply("%{underline}%{special}%{underline}: %{text}" % special)
286 m.reply("%{underline}%{special}%{underline} @ %{url}" % special)
291 def wu_weather_filter(stuff)
292 txt = wu_clean(stuff)
295 if txt.match(/<\/a>\s*Updated:\s*(.*?)\s*Observed at\s*(.*?)\s*<\/td>/)
296 result << ("Weather info for %s (updated on %s)" % [$2, $1])
298 txt.scan(/<tr>\s*<td>\s*(.*?)\s*<\/td>\s*<td>\s*(.*?)\s*<\/td>\s*<\/tr>/) { |k, v|
300 next if ["-", "- approx.", "N/A", "N/A approx."].include?(v)
301 next if k == "Raw METAR"
302 result << ("%s: %s" % [k, v])
304 return result.join('; ')
308 plugin = WeatherPlugin.new
309 plugin.map 'weather :units *where', :defaults => {:where => false, :units => false}, :requirements => {:units => /metric|english|both/}