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 def help(plugin, topic="")
74 "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/ )"
76 "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."
78 "weather information lookup. Looks up weather information for the last location you specified. See topics 'nws' and 'wu' for more information"
87 @wu_url = "http://mobile.wunderground.com/cgi-bin/findweather/getForecast?brand=mobile%s&query=%s"
88 @wu_station_url = "http://mobile.wunderground.com/auto/mobile%s/global/stations/%s.html"
91 def weather(m, params)
92 if params[:where].empty?
93 if @registry.has_key?(m.sourcenick)
94 where = @registry[m.sourcenick]
95 debug "Loaded weather info #{where.inspect} for #{m.sourcenick}"
97 service = where.first.to_sym
99 units = params[:units] || where[2] rescue nil
101 debug "No weather info for #{m.sourcenick}"
102 m.reply "I don't know where you are yet, #{m.sourcenick}. See 'help weather nws' or 'help weather wu' for additional help"
106 where = params[:where]
107 if ['nws','station'].include?(where.first)
108 service = where.first.to_sym
114 units = params[:units]
118 debug "No weather location found for #{m.sourcenick}"
119 m.reply "I don't know where you are yet, #{m.sourcenick}. See 'help weather nws' or 'help weather wu' for additional help"
123 wu_units = String.new
126 when :english, :metric
127 wu_units = "_#{units}"
130 m.reply "Ignoring unknown units #{units}"
131 wu_units = String.new
139 wu_station(m, loc, wu_units)
141 wu_weather(m, loc, wu_units)
144 @registry[m.sourcenick] = [service, loc, units]
147 def nws_describe(m, where)
148 if @nws_cache.has_key?(where) then
149 met = @nws_cache[where]
151 met = CurrentConditions.new(where)
156 @nws_cache[where] = met
161 m.reply "couldn't find weather data for #{where}"
165 def wu_station(m, where, units)
167 xml = @bot.httputil.get(@wu_station_url % [units, CGI.escape(where)])
170 m.reply "couldn't retrieve weather information, sorry"
172 when /Search not found:/
173 m.reply "no such station found (#{where})"
175 when /<table border.*?>(.*?)<\/table>/m
177 m.reply wu_weather_filter(data)
180 m.reply "something went wrong with the data for #{where}..."
183 m.reply "retrieving info about '#{where}' failed (#{e})"
187 def wu_weather(m, where, units)
189 xml = @bot.httputil.get(@wu_url % [units, CGI.escape(where)])
192 m.reply "couldn't retrieve weather information, sorry"
193 when /City Not Found/
194 m.reply "no such location found (#{where})"
197 xml.scan(/<table border.*?>(.*?)<\/table>/m).each do |match|
198 data += wu_weather_filter(match.first)
203 m.reply "couldn't parse weather data from #{where}"
205 when /<a href="\/(?:global\/stations|US\/\w\w)\//
206 wu_weather_multi(m, xml)
209 m.reply "something went wrong with the data from #{where}..."
212 m.reply "retrieving info about '#{where}' failed (#{e})"
218 txt.gsub!(/[\n\s]+/,' ')
219 txt.gsub!(/ /, ' ')
220 txt.gsub!(/°/, ' ') # degree sign
221 txt.gsub!(/<\/?b>/,'')
222 txt.gsub!(/<\/?span[^<>]*?>/,'')
223 txt.gsub!(/<img\s*[^<>]*?>/,'')
224 txt.gsub!(/<br\s?\/?>/,'')
228 def wu_weather_multi(m, xml)
229 stations = xml.scan(/<td>\s*<a href="\/(?:global\/stations|US\/(\w\w))\/(.*?)\.html">(.*?)<\/a>\s*:\s*(.*?)<\/td>/m)
230 m.reply "multiple stations available, use 'weather station <code>' or 'weather <city, state>' as appropriate, for one of the following (current temp shown):"
232 if ar.first # US state
233 "%s, %s (%s): %s" % [ar[1], ar[0], ar[2], wu_clean(ar[3])]
234 else # non-US station
235 "station %s (%s): %s" % [ar[1], ar[2], wu_clean(ar[3])]
238 m.reply stations.join("; ")
241 def wu_weather_filter(stuff)
242 txt = wu_clean(stuff)
245 if txt.match(/<\/a>\s*Updated:\s*(.*?)\s*Observed at\s*(.*?)\s*<\/td>/)
246 result << ("Weather info for %s (updated on %s)" % [$2, $1])
248 txt.scan(/<tr>\s*<td>\s*(.*?)\s*<\/td>\s*<td>\s*(.*?)\s*<\/td>\s*<\/tr>/) { |k, v|
250 next if ["-", "- approx.", "N/A", "N/A approx."].include?(v)
251 next if k == "Raw METAR"
252 result << ("%s: %s" % [k, v])
254 return result.join('; ')
258 plugin = WeatherPlugin.new
259 plugin.map 'weather :units *where', :defaults => {:where => false, :units => false}, :requirements => {:units => /metric|english|both/}