]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blobdiff - data/rbot/plugins/weather.rb
weather: URI-encode station
[user/henk/code/ruby/rbot.git] / data / rbot / plugins / weather.rb
index d8f7579c95c45cda0ce9d93480b164f4e260d116..cda68e102e5d0567299bd6a52724edc642967d0a 100644 (file)
@@ -17,7 +17,7 @@ require 'rexml/document'
 class CurrentConditions
     def initialize(station)
         @station = station
-        @url = "http://www.nws.noaa.gov/data/current_obs/#{@station.upcase}.xml"
+        @url = "http://www.nws.noaa.gov/data/current_obs/#{URI.encode @station.upcase}.xml"
         @etag = String.new
         @mtime = Time.mktime(0)
         @current_conditions = String.new
@@ -35,9 +35,9 @@ class CurrentConditions
             end
         rescue OpenURI::HTTPError => e
             case e
-            when /304/:
+            when /304/
                 @iscached = true
-            when /404/:
+            when /404/
                 raise "Data for #{@station} not found"
             else
                 raise "Error retrieving data: #{e}"
@@ -71,6 +71,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 +98,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)
@@ -208,7 +211,7 @@ class WeatherPlugin < Plugin
           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
@@ -219,21 +222,9 @@ class WeatherPlugin < Plugin
     end
   end
 
-  def wu_clean(stuff)
-    txt = stuff
-    txt.gsub!(/[\n\s]+/,' ')
-    txt.gsub!(/&nbsp;/, ' ')
-    txt.gsub!(/&#176;/, ' ') # 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|
@@ -243,9 +234,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 +276,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(/<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/
+  }