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