]> 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 a3550fb9fbc58930977694626d52b8124f81c711..cda68e102e5d0567299bd6a52724edc642967d0a 100644 (file)
@@ -17,7 +17,7 @@ require 'rexml/document'
 class CurrentConditions
     def initialize(station)
         @station = station
 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
         @etag = String.new
         @mtime = Time.mktime(0)
         @current_conditions = String.new
@@ -35,9 +35,9 @@ class CurrentConditions
             end
         rescue OpenURI::HTTPError => e
             case e
             end
         rescue OpenURI::HTTPError => e
             case e
-            when /304/:
+            when /304/
                 @iscached = true
                 @iscached = true
-            when /404/:
+            when /404/
                 raise "Data for #{@station} not found"
             else
                 raise "Error retrieving data: #{e}"
                 raise "Data for #{@station} not found"
             else
                 raise "Error retrieving data: #{e}"
@@ -71,18 +71,23 @@ 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::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
     when "nws"
       "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/ )"
     when "station", "wu"
   def help(plugin, topic="")
     case topic
     when "nws"
       "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/ )"
     when "station", "wu"
-      "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." 
+      "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."
     else
       "weather information lookup. Looks up weather information for the last location you specified. See topics 'nws' and 'wu' for more information"
     end
   end
     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
 
   def initialize
     super
 
@@ -91,61 +96,59 @@ class WeatherPlugin < Plugin
     @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
     @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)
   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
       else
         service = :wu
-        loc = where.to_s
       end
       end
-      units = params[:units]
     end
 
     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
       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
     end
 
     case service
     when :nws
-      nws_describe(m, loc)
+      nws_describe(m, where)
     when :station
     when :station
-      wu_station(m, loc, wu_units)
+      wu_station(m, where, wu_units)
     when :wu
     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
 
     end
 
-    @registry[m.sourcenick] = [service, loc, units]
+    @registry[m.sourcenick] = [service, where, units]
   end
 
   def nws_describe(m, where)
   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)
           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
         wu_weather_multi(m, xml)
       else
         debug xml
@@ -219,21 +222,9 @@ class WeatherPlugin < Plugin
     end
   end
 
     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
   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|
     # 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,35 +234,39 @@ class WeatherPlugin < Plugin
       par = ar[3]
       w = ar[4]
       if state # US station
       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
       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_check_special(xml)
       end
     }
     m.reply stations.join("; ")
   end
 
   def wu_check_special(xml)
-    if spec_match = xml.match(%r{<a href="([^"]+\?[^"]*feature=warning[^"]+)"[^>]*>([^<]+)</a>})
+    specials = []
+    # We only scan the first half to prevent getting the advisories twice
+    xml[0,xml.length/2].scan(%r{<a href="([^"]+\?[^"]*feature=warning#([^"]+))"[^>]*>([^<]+)</a>}) do
       special = {
         :url => "http://mobile.wunderground.com"+$1,
       special = {
         :url => "http://mobile.wunderground.com"+$1,
-        :special => $2.dup
+        :type => $2.dup,
+        :special => $3.dup
       }
       }
+      spec_rx = Regexp.new("<a name=\"#{special[:type]}\">(?:.+?)<td align=\"left\">\\s+(.+?)\\s+</td>\\s+</tr>\\s+</table>", Regexp::MULTILINE)
       spec_xml = @bot.httputil.get(special[:url])
       spec_xml = @bot.httputil.get(special[:url])
-      if spec_xml and spec_td = spec_xml.match(/<tr>\s*<td align="left">\s*(.*?)\s*<\/td>\s*<\/tr>\s*<\/table>/m)
-        return special.merge(:text => $1.ircify_html)
-      else
-        return special
+      if spec_xml and spec_td = spec_xml.match(spec_rx)
+        special.merge!(:text => spec_td.captures.first.ircify_html)
       end
       end
-    else
-      return nil
+      specials << special
     end
     end
+    return specials
   end
 
   def wu_out_special(m, xml)
     return unless @bot.config['weather.advisory']
   end
 
   def wu_out_special(m, xml)
     return unless @bot.config['weather.advisory']
-    special = wu_check_special(xml).merge(:underline => Underline)
-    if special
+    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
       if special[:text]
         m.reply("%{underline}%{special}%{underline}: %{text}" % special)
       else
@@ -281,21 +276,108 @@ class WeatherPlugin < Plugin
   end
 
   def wu_weather_filter(stuff)
   end
 
   def wu_weather_filter(stuff)
-    txt = wu_clean(stuff)
-
     result = Array.new
     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
     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
     }
     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
 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/
+  }