# Wraps NOAA National Weather Service information
class CurrentConditions
- def initialize(station)
+ def initialize(station, bot)
@station = station
- @url = "http://www.nws.noaa.gov/data/current_obs/#{@station.upcase}.xml"
- @etag = String.new
- @mtime = Time.mktime(0)
+ @bot = bot
+ @url = "http://www.nws.noaa.gov/data/current_obs/#{URI.encode @station.upcase}.xml"
@current_conditions = String.new
- @iscached = false
end
def update
- begin
- open(@url,"If-Modified-Since" => @mtime.rfc2822) do |feed|
- # open(@url,"If-None-Match"=>@etag) do |feed|
- @etag = feed.meta['etag']
- @mtime = feed.last_modified
- cc_doc = (REXML::Document.new feed).root
- @iscached = false
- @current_conditions = parse(cc_doc)
- end
- rescue OpenURI::HTTPError => e
- case e
- when /304/:
- @iscached = true
- when /404/:
- raise "Data for #{@station} not found"
- else
- raise "Error retrieving data: #{e}"
- end
+ begin
+ resp = @bot.httputil.get_response(@url)
+ case resp
+ when Net::HTTPSuccess
+ cc_doc = (REXML::Document.new resp.body).root
+ @current_conditions = parse(cc_doc)
+ else
+ raise Net::HTTPError.new(_("couldn't get data for %{station} (%{message})") % {
+ :station => @station, :message => resp.message
+ }, resp)
+ end
+ rescue => e
+ if Net::HTTPError === e
+ raise
+ else
+ error e
+ raise "error retrieving data: #{e}"
end
- @current_conditions # +" Cached? "+ ((@iscached) ? "Y" : "N")
+ end
+ @current_conditions
end
def parse(cc_doc)
cc = Hash.new
cc_doc.elements.each do |c|
cc[c.name.to_sym] = c.text
end
- "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."
+ cc[:time] = cc[:observation_time_rfc822]
+ cc[:wind] = cc[:wind_string]
+ cc[:temperature] = cc[:temperature_string]
+ cc[:heatindexorwindchill] = heat_index_or_wind_chill(cc)
+ cc[:pressure] = cc[:pressure_string]
+
+ _("At %{time} the conditions at %{location} (%{station_id}) were %{weather} with a visibility of %{visibility_mi}mi. The wind was %{wind} with %{relative_humidity}%% relative humidity. The temperature was %{temperature}%{heatindexorwindchill}, and the pressure was %{pressure}.") % cc
end
private
def heat_index_or_wind_chill(cc)
hi = cc[:heat_index_string]
wc = cc[:windchill_string]
- if hi != 'NA' then
- " with a heat index of #{hi}"
- elsif wc != 'NA' then
- " with a windchill of #{wc}"
+ if hi and hi != 'NA' then
+ _(" with a heat index of %{hi}") % { :hi => hi }
+ elsif wc and wc != 'NA' then
+ _(" with a windchill of %{wc}") % { :wc => wc }
else
""
end
Config.register Config::BooleanValue.new('weather.advisory',
:default => true,
:desc => "Should the bot report special weather advisories when any is present?")
+ Config.register Config::EnumValue.new('weather.units',
+ :values => ['metric', 'english', 'both'],
+ :default => 'both',
+ :desc => "Units to be used by default in Weather Underground reports")
+
def help(plugin, topic="")
case topic
end
def weather(m, params)
- if params[:where].empty?
- if @registry.has_key?(m.sourcenick)
- where = @registry[m.sourcenick]
- debug "Loaded weather info #{where.inspect} for #{m.sourcenick}"
-
- service = where.first.to_sym
- loc = where[1].to_s
- units = params[:units] || where[2] rescue nil
- else
- debug "No weather info for #{m.sourcenick}"
- m.reply "I don't know where you are yet, #{m.sourcenick}. See 'help weather nws' or 'help weather wu' for additional help"
- return
- end
- else
- where = params[:where]
- if ['nws','station'].include?(where.first)
- service = where.first.to_sym
- loc = where[1].to_s
+ where = params[:where].to_s
+ service = params[:service].to_sym rescue nil
+ units = params[:units]
+
+ if where.empty? or !service or !units and @registry.has_key?(m.sourcenick)
+ reg = @registry[m.sourcenick]
+ debug "loaded weather info #{reg.inspect} for #{m.sourcenick}"
+ service = reg.first.to_sym if !service
+ where = reg[1].to_s if where.empty?
+ units = reg[2] rescue nil
+ end
+
+ if !service
+ if where.sub!(/^station\s+/,'')
+ service = :nws
else
service = :wu
- loc = where.to_s
end
- units = params[:units]
end
- if loc.empty?
+ if where.empty?
debug "No weather location found for #{m.sourcenick}"
m.reply "I don't know where you are yet, #{m.sourcenick}. See 'help weather nws' or 'help weather wu' for additional help"
return
end
wu_units = String.new
- if units
- case units.to_sym
- when :english, :metric
- wu_units = "_#{units}"
- when :both
- else
- m.reply "Ignoring unknown units #{units}"
- wu_units = String.new
- end
+
+ units = @bot.config['weather.units'] unless units
+
+ case units.to_sym
+ when :english, :metric
+ wu_units = "_#{units}"
+ when :both
+ else
+ m.reply "Ignoring unknown units #{units}"
end
case service
when :nws
- nws_describe(m, loc)
+ nws_describe(m, where)
when :station
- wu_station(m, loc, wu_units)
+ wu_station(m, where, wu_units)
when :wu
- wu_weather(m, loc, wu_units)
+ wu_weather(m, where, wu_units)
+ when :google
+ google_weather(m, where)
+ else
+ m.reply "I don't know the weather service #{service}, sorry"
+ return
end
- @registry[m.sourcenick] = [service, loc, units]
+ @registry[m.sourcenick] = [service, where, units]
end
def nws_describe(m, where)
if @nws_cache.has_key?(where) then
met = @nws_cache[where]
else
- met = CurrentConditions.new(where)
+ met = CurrentConditions.new(where, @bot)
end
if met
begin
m.reply met.update
@nws_cache[where] = met
+ rescue Net::HTTPError => e
+ m.reply _("%{error}, will try WU service") % { :error => e.message }
+ wu_weather(m, where)
rescue => e
m.reply e.message
end
end
end
- def wu_station(m, where, units)
+ def wu_station(m, where, units="")
begin
xml = @bot.httputil.get(@wu_station_url % [units, CGI.escape(where)])
case xml
end
end
- def wu_weather(m, where, units)
+ def wu_weather(m, where, units="")
begin
xml = @bot.httputil.get(@wu_url % [units, CGI.escape(where)])
case xml
m.reply "couldn't parse weather data from #{where}"
end
wu_out_special(m, xml)
- when /<a href="\/(?:global\/stations|US\/\w\w)\//
+ when /<a href="\/auto\/mobile[^\/]+\/(?:global\/stations|[A-Z][A-Z])\//
wu_weather_multi(m, xml)
else
debug xml
end
end
- def wu_clean(stuff)
- txt = stuff
- txt.gsub!(/[\n\s]+/,' ')
- txt.gsub!(/ /, ' ')
- txt.gsub!(/°/, ' ') # degree sign
- txt.gsub!(/<\/?b>/,'')
- txt.gsub!(/<\/?span[^<>]*?>/,'')
- txt.gsub!(/<img\s*[^<>]*?>/,'')
- txt.gsub!(/<br\s?\/?>/,'')
- txt
- end
-
def wu_weather_multi(m, xml)
# debug xml
- 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)
+ stations = xml.scan(/<td>\s*(?:<a href="([^?"]+\?feature=[^"]+)"\s*[^>]*><img [^>]+><\/a>\s*)?<a href="\/auto\/mobile[^\/]+\/(?:global\/stations|([A-Z][A-Z]))\/([^"]*?)\.html">(.*?)<\/a>\s*:\s*(.*?)<\/td>/m)
# debug stations
m.reply "multiple stations available, use 'weather station <code>' or 'weather <city, state>' as appropriate, for one of the following (current temp shown):"
stations.map! { |ar|
par = ar[3]
w = ar[4]
if state # US station
- (warning ? "*" : "") + ("%s, %s (%s): %s" % [loc, state, par, wu_clean(w)])
+ (warning ? "*" : "") + ("%s, %s (%s): %s" % [loc, state, par, w.ircify_html])
else # non-US station
- (warning ? "*" : "") + ("station %s (%s): %s" % [loc, par, wu_clean(w)])
+ (warning ? "*" : "") + ("station %s (%s): %s" % [loc, par, w.ircify_html])
end
}
m.reply stations.join("; ")
end
def wu_weather_filter(stuff)
- txt = wu_clean(stuff)
-
result = Array.new
- if txt.match(/<\/a>\s*Updated:\s*(.*?)\s*Observed at\s*(.*?)\s*<\/td>/)
- result << ("Weather info for %s (updated on %s)" % [$2, $1])
+ if stuff.match(/<\/a>\s*Updated:\s*(.*?)\s*Observed at\s*(.*?)\s*<\/td>/)
+ result << ("Weather info for %s (updated on %s)" % [$2.ircify_html, $1.ircify_html])
end
- txt.scan(/<tr>\s*<td>\s*(.*?)\s*<\/td>\s*<td>\s*(.*?)\s*<\/td>\s*<\/tr>/) { |k, v|
- next if v.empty?
- next if ["-", "- approx.", "N/A", "N/A approx."].include?(v)
- next if k == "Raw METAR"
- result << ("%s: %s" % [k, v])
+ stuff.scan(/<tr>\s*<td>\s*(.*?)\s*<\/td>\s*<td>\s*(.*?)\s*<\/td>\s*<\/tr>/m) { |k, v|
+ kk = k.riphtml
+ vv = v.riphtml
+ next if vv.empty?
+ next if ["-", "- approx.", "N/A", "N/A approx."].include?(vv)
+ next if kk == "Raw METAR"
+ result << ("%s: %s" % [kk, vv])
}
return result.join('; ')
end
+
+ # TODO allow units choice other than lang, find how the API does it
+ def google_weather(m, where)
+ botlang = @bot.config['core.language'].intern
+ if Language::Lang2Locale.key?(botlang)
+ lang = Language::Lang2Locale[botlang].sub(/.UTF.?8$/,'')
+ else
+ lang = botlang.to_s[0,2]
+ end
+
+ debug "Google weather with language #{lang}"
+ xml = @bot.httputil.get("http://www.google.com/ig/api?hl=#{lang}&weather=#{CGI.escape where}")
+ debug xml
+ weather = REXML::Document.new(xml).root.elements["weather"]
+ begin
+ error = weather.elements["problem_cause"]
+ if error
+ ermsg = error.attributes["data"]
+ ermsg = _("no reason specified") if ermsg.empty?
+ raise ermsg
+ end
+ city = weather.elements["forecast_information/city"].attributes["data"]
+ date = Time.parse(weather.elements["forecast_information/current_date_time"].attributes["data"])
+ units = weather.elements["forecast_information/unit_system"].attributes["data"].intern
+ current_conditions = weather.elements["current_conditions"]
+ foreconds = weather.elements.to_a("forecast_conditions")
+
+ conds = []
+ current_conditions.each { |el|
+ name = el.name.intern
+ value = el.attributes["data"].dup
+ debug [name, value]
+ case name
+ when :icon
+ next
+ when :temp_f
+ next if units == :SI
+ value << "°F"
+ when :temp_c
+ next if units == :US
+ value << "°C"
+ end
+ conds << value
+ }
+
+ forecasts = []
+ foreconds.each { |forecast|
+ cond = []
+ forecast.each { |el|
+ name = el.name.intern
+ value = el.attributes["data"]
+ case name
+ when :icon
+ next
+ when :high, :low
+ value << (units == :SI ? "°C" : "°F")
+ value << " |" if name == :low
+ when :condition
+ value = "(#{value})"
+ end
+ cond << value
+ }
+ forecasts << cond.join(' ')
+ }
+
+ m.reply _("Google weather info for %{city} on %{date}: %{conds}. Three-day forecast: %{forecast}") % {
+ :city => city,
+ :date => date,
+ :conds => conds.join(', '),
+ :forecast => forecasts.join('; ')
+ }
+ rescue => e
+ debug e
+ m.reply _("Google weather failed: %{e}") % { :e => e}
+ end
+
+ end
+
end
plugin = WeatherPlugin.new
-plugin.map 'weather :units *where', :defaults => {:where => false, :units => false}, :requirements => {:units => /metric|english|both/}
+plugin.map 'weather :units :service *where',
+ :defaults => {
+ :where => false,
+ :units => false,
+ :service => false
+ },
+ :requirements => {
+ :units => /metric|english|both/,
+ :service => /wu|nws|station|google/
+ }