X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=data%2Frbot%2Fplugins%2Fweather.rb;h=9d2b8aed5e3c368b49b62e30a3f6511d3fa8e4cd;hb=234270da7bf2757d84e12e4179a1b5227be56a13;hp=d8f7579c95c45cda0ce9d93480b164f4e260d116;hpb=783ffa4235330029d661752b1023db635b26f2b3;p=user%2Fhenk%2Fcode%2Fruby%2Frbot.git diff --git a/data/rbot/plugins/weather.rb b/data/rbot/plugins/weather.rb index d8f7579c..9d2b8aed 100644 --- a/data/rbot/plugins/weather.rb +++ b/data/rbot/plugins/weather.rb @@ -15,51 +15,55 @@ require 'rexml/document' # Wraps NOAA National Weather Service information class CurrentConditions + @@bot = Irc::Utils.bot def initialize(station) @station = station - @url = "http://www.nws.noaa.gov/data/current_obs/#{@station.upcase}.xml" - @etag = String.new - @mtime = Time.mktime(0) + @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 @@ -71,6 +75,11 @@ class WeatherPlugin < Plugin 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 @@ -93,59 +102,57 @@ class WeatherPlugin < Plugin 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 + + case (units || @bot.config['weather.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) @@ -158,6 +165,9 @@ class WeatherPlugin < Plugin 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 @@ -166,7 +176,7 @@ class WeatherPlugin < Plugin 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 @@ -189,7 +199,7 @@ class WeatherPlugin < Plugin 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 @@ -208,7 +218,7 @@ class WeatherPlugin < Plugin m.reply "couldn't parse weather data from #{where}" end wu_out_special(m, xml) - when //,'') - txt.gsub!(/<\/?span[^<>]*?>/,'') - txt.gsub!(/]*?>/,'') - txt.gsub!(//,'') - txt - end - def wu_weather_multi(m, xml) # debug xml - stations = xml.scan(/\s*(?:]*>]+><\/a>\s*)?(.*?)<\/a>\s*:\s*(.*?)<\/td>/m) + stations = xml.scan(/\s*(?:]*>]+><\/a>\s*)?(.*?)<\/a>\s*:\s*(.*?)<\/td>/m) # debug stations m.reply "multiple stations available, use 'weather station ' or 'weather ' as appropriate, for one of the following (current temp shown):" stations.map! { |ar| @@ -243,9 +241,9 @@ class WeatherPlugin < Plugin 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("; ") @@ -285,21 +283,108 @@ class WeatherPlugin < Plugin 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(/\s*\s*(.*?)\s*<\/td>\s*\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(/\s*\s*(.*?)\s*<\/td>\s*\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/ + }