X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=data%2Frbot%2Fplugins%2Fweather.rb;h=7c1336f0d017dfd62655bb2d4d859e7a563bd649;hb=fda847bf583b9cc02eda341730d53887302ffe57;hp=3e4134e4b4c70c88531337ff316976f1f29e35aa;hpb=21949774b91eaec6ecde4eaa8ad121e2c0a36b87;p=user%2Fhenk%2Fcode%2Fruby%2Frbot.git diff --git a/data/rbot/plugins/weather.rb b/data/rbot/plugins/weather.rb index 3e4134e4..7c1336f0 100644 --- a/data/rbot/plugins/weather.rb +++ b/data/rbot/plugins/weather.rb @@ -1,55 +1,392 @@ +#-- vim:sw=2:et +#++ +# +# :title: Weather plugin for rbot +# +# Author:: MrChucho (mrchucho@mrchucho.net): NOAA National Weather Service support +# Author:: Giuseppe "Oblomov" Bilotta +# +# Copyright:: (C) 2006 Ralph M. Churchill +# Copyright:: (C) 2006-2007 Giuseppe Bilotta +# +# License:: GPL v2 + +require 'rexml/document' + +# Wraps NOAA National Weather Service information +class CurrentConditions + def initialize(station, bot) + @station = station + @bot = bot + @url = "http://www.nws.noaa.gov/data/current_obs/#{URI.encode @station.upcase}.xml" + @current_conditions = String.new + end + def update + 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 + 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 + 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 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 + end +end + 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="") - "weather => display the current weather at the location specified by the ICAO code [Lookup your ICAO code at http://www.nws.noaa.gov/oso/siteloc.shtml] - this will also store the ICAO against your nick, so you can later just say \"weather\", weather => display the current weather at the location you last asked for" + case topic + when "nws" + "weather nws => display the current conditions at the location specified by the NOAA National Weather Service station code ( lookup your station code at http://www.nws.noaa.gov/data/current_obs/ )" + when "station", "wu" + "weather [] => display the current conditions at the location specified, looking it up on the Weather Underground site; you can use 'station ' to look up data by station code ( lookup your station code at http://www.weatherunderground.com/ ); you can optionally set to 'metric' or 'english' if you only want data with the units; use 'both' for units to go back to having both." + else + "weather information lookup. Looks up weather information for the last location you specified. See topics 'nws' and 'wu' for more information" + end end - + def initialize super - # this plugin only wants to store strings - class << @registry - def store(val) - val - end - def restore(val) - val + + @nws_cache = Hash.new + + @wu_url = "http://mobile.wunderground.com/cgi-bin/findweather/getForecast?brand=mobile%s&query=%s" + @wu_station_url = "http://mobile.wunderground.com/auto/mobile%s/global/stations/%s.html" + end + + def weather(m, params) + 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 end end - @metar_cache = Hash.new + + 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 + + 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, where) + when :station + wu_station(m, where, wu_units) + when :wu + 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, where, units] end - - def describe(m, where) - if @metar_cache.has_key?(where) && - Time.now - @metar_cache[where].date < 3600 - met = @metar_cache[where] + + def nws_describe(m, where) + if @nws_cache.has_key?(where) then + met = @nws_cache[where] else - met = Utils.get_metar(where) + met = CurrentConditions.new(where, @bot) end - if met - m.reply met.pretty_print - @metar_cache[where] = 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 else m.reply "couldn't find weather data for #{where}" end end - - def privmsg(m) - case m.params - when nil - if @registry.has_key?(m.sourcenick) - where = @registry[m.sourcenick] - describe(m,where) + + def wu_station(m, where, units="") + begin + xml = @bot.httputil.get(@wu_station_url % [units, CGI.escape(where)]) + case xml + when nil + m.reply "couldn't retrieve weather information, sorry" + return + when /Search not found:/ + m.reply "no such station found (#{where})" + return + when /(.*?)<\/table>/m + data = $1.dup + m.reply wu_weather_filter(data) + wu_out_special(m, xml) + else + debug xml + m.reply "something went wrong with the data for #{where}..." + end + rescue => e + m.reply "retrieving info about '#{where}' failed (#{e})" + end + end + + def wu_weather(m, where, units="") + begin + xml = @bot.httputil.get(@wu_url % [units, CGI.escape(where)]) + case xml + when nil + m.reply "couldn't retrieve weather information, sorry" + when /City Not Found/ + m.reply "no such location found (#{where})" + when /Current<\/a>/ + data = "" + xml.scan(/
(.*?)<\/table>/m).each do |match| + data += wu_weather_filter(match.first) + end + if data.length > 0 + m.reply data + else + m.reply "couldn't parse weather data from #{where}" + end + wu_out_special(m, xml) + when / e + m.reply "retrieving info about '#{where}' failed (#{e})" + end + end + + def wu_weather_multi(m, xml) + # debug xml + stations = xml.scan(/\\s+\\s+
\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| + warning = ar[0] + loc = ar[2] + state = ar[1] + par = ar[3] + w = ar[4] + if state # US station + (warning ? "*" : "") + ("%s, %s (%s): %s" % [loc, state, par, w.ircify_html]) + else # non-US station + (warning ? "*" : "") + ("station %s (%s): %s" % [loc, par, w.ircify_html]) + end + } + m.reply stations.join("; ") + end + + def wu_check_special(xml) + specials = [] + # We only scan the first half to prevent getting the advisories twice + xml[0,xml.length/2].scan(%r{]*>([^<]+)}) do + special = { + :url => "http://mobile.wunderground.com"+$1, + :type => $2.dup, + :special => $3.dup + } + spec_rx = Regexp.new("(?:.+?)\\s+(.+?)\\s+
", Regexp::MULTILINE) + spec_xml = @bot.httputil.get(special[:url]) + if spec_xml and spec_td = spec_xml.match(spec_rx) + special.merge!(:text => spec_td.captures.first.ircify_html) + end + specials << special + end + return specials + end + + def wu_out_special(m, xml) + return unless @bot.config['weather.advisory'] + specials = wu_check_special(xml) + debug specials + specials.each do |special| + special.merge!(:underline => Underline) + if special[:text] + m.reply("%{underline}%{special}%{underline}: %{text}" % special) + else + m.reply("%{underline}%{special}%{underline} @ %{url}" % special) + end + end + end + + def wu_weather_filter(stuff) + result = Array.new + 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 + 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 - when (/^(\S{4})$/) - where = $1 - @registry[m.sourcenick] = where - describe(m,where) + 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.register("weather") +plugin.map 'weather :units :service *where', + :defaults => { + :where => false, + :units => false, + :service => false + }, + :requirements => { + :units => /metric|english|both/, + :service => /wu|nws|station|google/ + }