]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/commitdiff
Fri Jul 29 13:07:56 BST 2005 Tom Gilbert <tom@linuxbrit.co.uk>
authorTom Gilbert <tom@linuxbrit.co.uk>
Fri, 29 Jul 2005 13:44:33 +0000 (13:44 +0000)
committerTom Gilbert <tom@linuxbrit.co.uk>
Fri, 29 Jul 2005 13:44:33 +0000 (13:44 +0000)
  * Moved some stuff out of util.rb into the plugins that actually need
them. Those methods didn't belong in util as they were plugin-specific.
* moved a few more plugins to use map() where appropriate
* made the url plugin only store unique urls

19 files changed:
ChangeLog
data/rbot/plugins/nslookup.rb
data/rbot/plugins/opmeh.rb [deleted file]
data/rbot/plugins/remind.rb
data/rbot/plugins/rot13.rb
data/rbot/plugins/roulette.rb
data/rbot/plugins/seen.rb
data/rbot/plugins/slashdot.rb
data/rbot/plugins/tube.rb
data/rbot/plugins/url.rb
data/rbot/plugins/weather.rb
data/rbot/plugins/wserver.rb
data/rbot/templates/keywords.rbot
data/rbot/templates/levels.rbot
lib/rbot/config.rb
lib/rbot/httputil.rb
lib/rbot/messagemapper.rb
lib/rbot/plugins.rb
lib/rbot/utils.rb

index fcb65aacfab2a47a7f543c81efe6defd2cb3c491..aca8591992e631d429658569bc2788ae2fd53472 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+Fri Jul 29 13:07:56 BST 2005  Tom Gilbert <tom@linuxbrit.co.uk>
+
+  * Moved some stuff out of util.rb into the plugins that actually need
+       them. Those methods didn't belong in util as they were plugin-specific.
+       * moved a few more plugins to use map() where appropriate
+       * made the url plugin only store unique urls
+
 Thu Jul 28 23:45:26 BST 2005  Tom Gilbert <tom@linuxbrit.co.uk>
 
   * Reworked the Timer module. The Timer now has a smart thread manager to
index 92da1ba779e2f4250d219cc5155a68d503278b6e..160fee85f7aa133ce1d0673505e14ce8dccde2bb 100644 (file)
@@ -1,56 +1,43 @@
 class DnsPlugin < Plugin
-  begin
-    require 'resolv-replace'
-    def gethostname(address)
-      Resolv.getname(address)
-    end
-    def getaddresses(name)
-      Resolv.getaddresses(name)
-    end
-  rescue LoadError
-    def gethostname(address)
-      Socket.gethostbyname(address).first
-    end
-    def getaddresses(name)
-      a = Socket.gethostbyname(name)
-      list = Socket.getaddrinfo(a[0], 'http')
-      addresses = Array.new
-      list.each {|line|
-       addresses << line[3]
-      }
-      addresses
-    end
+  require 'resolv'
+  def gethostname(address)
+    Resolv.getname(address)
+  end
+  def getaddresses(name)
+    Resolv.getaddresses(name)
   end
 
   def help(plugin, topic="")
-    "nslookup|dns <hostname|ip> => show local resolution results for hostname or ip address"
+    "dns <hostname|ip> => show local resolution results for hostname or ip address"
   end
-  def privmsg(m)
-    unless(m.params)
-      m.reply "incorrect usage: " + help(m.plugin)
-      return
+  
+  def name_to_ip(m, params)
+    Thread.new do
+      begin
+        a = getaddresses(params[:host])
+        if a.length > 0
+          m.reply m.params + ": " + a.join(", ")
+        else
+          m.reply "#{params[:host]}: not found"
+        end
+      rescue StandardError => err
+        m.reply "#{params[:host]}: not found"
+      end
     end
+  end
+  
+  def ip_to_name(m, params)
     Thread.new do
-      if(m.params =~ /^\d+\.\d+\.\d+\.\d+$/)
        begin
-         a = gethostname(m.params)
+         a = gethostname(params[:ip])
          m.reply m.params + ": " + a if a
        rescue StandardError => err
-         m.reply "#{m.params}: not found"
-       end
-      elsif(m.params =~ /^\S+$/)
-       begin
-         a = getaddresses(m.params)
-         m.reply m.params + ": " + a.join(", ")
-       rescue StandardError => err
-         m.reply "#{m.params}: not found"
+         m.reply "#{params[:ip]}: not found (does not reverse resolve)"
        end
-      else
-       m.reply "incorrect usage: " + help(m.plugin)
-      end
-    end
+     end
   end
 end
 plugin = DnsPlugin.new
-plugin.register("nslookup")
-plugin.register("dns")
+plugin.map 'dns :ip', :action => 'ip_to_name', 
+                      :requirements => {:ip => /^\d+\.\d+\.\d+\.\d+$/}
+plugin.map 'dns :host', :action => 'name_to_ip'
diff --git a/data/rbot/plugins/opmeh.rb b/data/rbot/plugins/opmeh.rb
deleted file mode 100644 (file)
index aad388a..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-class OpMePlugin < Plugin\r
-\r
-  def help(plugin, topic="")\r
-    return "opme <channel> => grant user ops in <channel>"\r
-  end\r
-\r
-  def privmsg(m)\r
-    if(m.params)\r
-      channel = m.params\r
-    else\r
-      channel = m.channel\r
-    end\r
-    target = m.sourcenick\r
-    @bot.sendq("MODE #{channel} +o #{target}")\r
-    m.okay\r
-  end\r
-end\r
-plugin = OpMePlugin.new\r
-plugin.register("opme")\r
index 5ad980aeac8d33012da673e0c0594796d8100f32..f66c4fc8a40b79b7f9da4974df1c9f13cf6afc01 100644 (file)
@@ -1,6 +1,108 @@
 require 'rbot/utils'
 
 class RemindPlugin < Plugin
+  # read a time in string format, turn it into "seconds from now".
+  # example formats handled are "5 minutes", "2 days", "five hours",
+  # "11:30", "15:45:11", "one day", etc.
+  #
+  # Throws:: RunTimeError "invalid time string" on parse failure
+  def timestr_offset(timestr)
+    case timestr
+      when (/^(\S+)\s+(\S+)$/)
+        mult = $1
+        unit = $2
+        if(mult =~ /^([\d.]+)$/)
+          num = $1.to_f
+          raise "invalid time string" unless num
+        else
+          case mult
+            when(/^(one|an|a)$/)
+              num = 1
+            when(/^two$/)
+              num = 2
+            when(/^three$/)
+              num = 3
+            when(/^four$/)
+              num = 4
+            when(/^five$/)
+              num = 5
+            when(/^six$/)
+              num = 6
+            when(/^seven$/)
+              num = 7
+            when(/^eight$/)
+              num = 8
+            when(/^nine$/)
+              num = 9
+            when(/^ten$/)
+              num = 10
+            when(/^fifteen$/)
+              num = 15
+            when(/^twenty$/)
+              num = 20
+            when(/^thirty$/)
+              num = 30
+            when(/^sixty$/)
+              num = 60
+            else
+              raise "invalid time string"
+          end
+        end
+        case unit
+          when (/^(s|sec(ond)?s?)$/)
+            return num
+          when (/^(m|min(ute)?s?)$/)
+            return num * 60
+          when (/^(h|h(ou)?rs?)$/)
+            return num * 60 * 60
+          when (/^(d|days?)$/)
+            return num * 60 * 60 * 24
+          else
+            raise "invalid time string"
+        end
+      when (/^(\d+):(\d+):(\d+)$/)
+        hour = $1.to_i
+        min = $2.to_i
+        sec = $3.to_i
+        now = Time.now
+        later = Time.mktime(now.year, now.month, now.day, hour, min, sec)
+        return later - now
+      when (/^(\d+):(\d+)$/)
+        hour = $1.to_i
+        min = $2.to_i
+        now = Time.now
+        later = Time.mktime(now.year, now.month, now.day, hour, min, now.sec)
+        return later - now
+      when (/^(\d+):(\d+)(am|pm)$/)
+        hour = $1.to_i
+        min = $2.to_i
+        ampm = $3
+        if ampm == "pm"
+          hour += 12
+        end
+        now = Time.now
+        later = Time.mktime(now.year, now.month, now.day, hour, min, now.sec)
+        return later - now
+      when (/^(\S+)$/)
+        num = 1
+        unit = $1
+        case unit
+          when (/^(s|sec(ond)?s?)$/)
+            return num
+          when (/^(m|min(ute)?s?)$/)
+            return num * 60
+          when (/^(h|h(ou)?rs?)$/)
+            return num * 60 * 60
+          when (/^(d|days?)$/)
+            return num * 60 * 60 * 24
+          else
+            raise "invalid time string"
+        end
+      else
+        raise "invalid time string"
+    end
+  end
+
   def initialize
     super
     @reminders = Hash.new
@@ -14,15 +116,11 @@ class RemindPlugin < Plugin
     @reminders.clear
   end
   def help(plugin, topic="")
-    if(plugin =~ /^remind\+$/)
-      "see remind. remind+ can be used to remind someone else of something, using <nick> instead of 'me'. However this will generally require a higher auth level than remind."
-    else
-      "remind me [about] <message> in <time>, remind me [about] <message> every <time>, remind me [about] <message> at <time>, remind me no more [about] <message>, remind me no more"
-    end
+    "reminder plugin: remind <who> [about] <message> in <time>, remind <who> [about] <message> every <time>, remind <who> [about] <message> at <time>, remind <who> no more [about] <message>, remind <who> no more. Generally <who> should be 'me', but you can remind others (nick or channel) if you have remind_others auth"
   end
   def add_reminder(who, subject, timestr, repeat=false)
     begin
-      period = Irc::Utils.timestr_offset(timestr)
+      period = timestr_offset(timestr)
     rescue RuntimeError
       return "couldn't parse that time string (#{timestr}) :("
     end
@@ -58,6 +156,9 @@ class RemindPlugin < Plugin
       if(@reminders.has_key?(who) && @reminders[who].has_key?(subject))
         @bot.timer.remove(@reminders[who][subject])
         @reminders[who].delete(subject)
+        return true
+      else
+        return false
       end
     else
       if(@reminders.has_key?(who))
@@ -65,90 +166,63 @@ class RemindPlugin < Plugin
           @bot.timer.remove(v)
         }
         @reminders.delete(who)
+        return true
+      else
+        return false
       end
     end
   end
-  def privmsg(m)
-
-    if(m.params =~ /^(\S+)\s+(?:about\s+)?(.*)\s+in\s+(.*)$/)
-      who = $1
-      subject = $2
-      period = $3
-      if(who =~ /^me$/)
-        who = m.sourcenick
-      else
-        unless(m.plugin =~ /^remind\+$/)
-          m.reply "incorrect usage: use remind+ to remind persons other than yourself"
-          return
-        end
-      end
+  def remind(m, params)
+    who = params.has_key?(:who) ? params[:who] : m.sourcenick
+    string = params[:string].to_s
+    puts "in remind, string is: #{string}"
+    if(string =~ /^(.*)\s+in\s+(.*)$/)
+      subject = $1
+      period = $2
       if(err = add_reminder(who, subject, period))
         m.reply "incorrect usage: " + err
         return
       end
-    elsif(m.params =~ /^(\S+)\s+(?:about\s+)?(.*)\s+every\s+(.*)$/)
-      who = $1
-      subject = $2
-      period = $3
-      if(who =~ /^me$/)
-        who = m.sourcenick
-      else
-        unless(m.plugin =~ /^remind\+$/)
-          m.reply "incorrect usage: use remind+ to remind persons other than yourself"
-          return
-        end
-      end
+    elsif(string =~ /^(.*)\s+every\s+(.*)$/)
+      subject = $1
+      period = $2
       if(err = add_reminder(who, subject, period, true))
         m.reply "incorrect usage: " + err
         return
       end
-    elsif(m.params =~ /^(\S+)\s+(?:about\s+)?(.*)\s+at\s+(.*)$/)
-      who = $1
-      subject = $2
-      time = $3
-      if(who =~ /^me$/)
-        who = m.sourcenick
-      else
-        unless(m.plugin =~ /^remind\+$/)
-          m.reply "incorrect usage: use remind+ to remind persons other than yourself"
-          return
-        end
-      end
+    elsif(string =~ /^(.*)\s+at\s+(.*)$/)
+      subject = $1
+      time = $2
       if(err = add_reminder(who, subject, time))
         m.reply "incorrect usage: " + err
         return
       end
-    elsif(m.params =~ /^(\S+)\s+no\s+more\s+(?:about\s+)?(.*)$/)
-      who = $1
-      subject = $2
-      if(who =~ /^me$/)
-        who = m.sourcenick
-      else
-        unless(m.plugin =~ /^remind\+$/)
-          m.reply "incorrect usage: use remind+ to remind persons other than yourself"
-          return
-        end
-      end
-      del_reminder(who, subject)
-    elsif(m.params =~ /^(\S+)\s+no\s+more$/)
-      who = $1
-      if(who =~ /^me$/)
-        who = m.sourcenick
-      else
-        unless(m.plugin =~ /^remind\+$/)
-          m.reply "incorrect usage: use remind+ to remind persons other than yourself"
-          return
-        end
-      end
-      del_reminder(who)
     else
-      m.reply "incorrect usage: " + help(m.plugin)
+      usage(m)
       return
     end
     m.okay
   end
+  def no_more(m, params)
+    who = params.has_key?(:who) ? params[:who] : m.sourcenick
+    deleted = params.has_key?(:string) ? 
+              del_reminder(who, params[:string].to_s) : del_reminder(who)
+    if deleted
+      m.okay
+    else 
+      m.reply "but I wasn't going to :/"
+    end
+  end
 end
 plugin = RemindPlugin.new
-plugin.register("remind")
-plugin.register("remind+")
+plugin.map 'remind me no more', :action => 'no_more'
+plugin.map 'remind me no more about *string', :action => 'no_more'
+plugin.map 'remind me no more *string', :action => 'no_more'
+plugin.map 'remind me about *string'
+plugin.map 'remind me *string'
+plugin.map 'remind :who no more', :auth => 'remind_other', :action => 'no_more'
+plugin.map 'remind :who no more about *string', :auth => 'remind_other', :action => 'no_more'
+plugin.map 'remind :who no more *string', :auth => 'remind_other', :action => 'no_more'
+plugin.map 'remind :who about *string', :auth => 'remind_other'
+plugin.map 'remind :who *string', :auth => 'remind_other'
 
index 1f367dbda9933ec3881ca10b0b9efd517ec8bf95..28e9243f5fc0a779401fe1d72bf54aae211a3507 100644 (file)
@@ -2,13 +2,9 @@ class RotPlugin < Plugin
   def help(plugin, topic="")
     "rot13 <string> => encode <string> to rot13 or back"
   end
-  def privmsg(m)
-    unless(m.params && m.params =~ /^.+$/)
-      m.reply "incorrect usage: " + help(m.plugin)
-      return
-    end
-    m.reply m.params.tr("A-Za-z", "N-ZA-Mn-za-m");
+  def rot13(m, params)
+    m.reply params[:string].tr("A-Za-z", "N-ZA-Mn-za-m");
   end
 end
 plugin = RotPlugin.new
-plugin.register("rot13")
+plugin.map 'rot13 :string'
index c9d585ea8e7e576687037ab16ad2b7e66b2f2ca2..d57bc621e0b092e1988c31dd7cd92418c448ea33 100644 (file)
@@ -3,40 +3,18 @@ RouletteHistory = Struct.new("RouletteHistory", :games, :shots, :deaths, :misses
 class RoulettePlugin < Plugin
   def initialize
     super
-    reload
+    reset_chambers
+    @players = Array.new
   end
   def help(plugin, topic="")
     "roulette => play russian roulette - starts a new game if one isn't already running. One round in a six chambered gun. Take turns to say roulette to the bot, until somebody dies. roulette reload => force the gun to reload, roulette stats => show stats from all games, roulette stats <player> => show stats for <player>, roulette clearstats => clear stats (config level auth required)"
   end
-  def privmsg(m)
-    if m.params == "reload"
-      @bot.action m.replyto, "reloads"
-      reload
-      # all players win on a reload
-      # (allows you to play 3-shot matches etc)
-      @players.each {|plyr|
-        pdata = @registry[plyr]
-        next if pdata == nil
-        pdata.wins += 1
-        @registry[plyr] = pdata
-      }
-      return
-    elsif m.params == "stats"
-      m.reply stats
-      return
-    elsif m.params =~ /^stats\s+(.+)$/
-      m.reply(playerstats($1))
-      return
-    elsif m.params == "clearstats"
-      if @bot.auth.allow?("config", m.source, m.replyto)
-        @registry.clear
-        m.okay
-      end
-      return
-    elsif m.params
-      m.reply "incorrect usage: " + help(m.plugin)
-      return
-    end
+  def clearstats(m, params)
+    @registry.clear
+    m.okay
+  end
+
+  def roulette(m, params)
     if m.private?
       m.reply "you gotta play roulette in channel dude"
       return
@@ -74,21 +52,36 @@ class RoulettePlugin < Plugin
     @registry[m.sourcenick] = playerdata
     
     if shot || @chambers.empty?
-      @bot.action m.replyto, "reloads"
-      reload
+      reload(m)
     end
   end
-  def reload
+  def reload(m, params = {})
+    @bot.action m.replyto, "reloads"
+    reset_chambers
+    # all players win on a reload
+    # (allows you to play 3-shot matches etc)
+    @players.each {|plyr|
+      pdata = @registry[plyr]
+      next if pdata == nil
+      pdata.wins += 1
+      @registry[plyr] = pdata
+    }
+    @players = Array.new
+  end
+  def reset_chambers
     @chambers = [false, false, false, false, false, false]
     @chambers[rand(@chambers.length)] = true
-    @players = Array.new
   end
-  def playerstats(player)
+  def playerstats(m, params)
+    player = params[:player]
     pstats = @registry[player]
-    return "#{player} hasn't played enough games yet" if pstats.nil?
-    return "#{player} has played #{pstats.games} games, won #{pstats.wins} and lost #{pstats.deaths}. #{player} pulled the trigger #{pstats.shots} times and found the chamber empty on #{pstats.misses} occasions."
+    if pstats.nil?
+      m.reply "#{player} hasn't played enough games yet"
+    else
+      m.reply "#{player} has played #{pstats.games} games, won #{pstats.wins} and lost #{pstats.deaths}. #{player} pulled the trigger #{pstats.shots} times and found the chamber empty on #{pstats.misses} occasions."
+    end
   end
-  def stats
+  def stats(m, params)
     total_players = 0
     total_games = 0
     total_shots = 0
@@ -139,9 +132,16 @@ class RoulettePlugin < Plugin
         won_most[0] << k
       end
     }
-    return "roulette stats: no games played yet" if total_games < 1
-    return "roulette stats: #{total_games} games completed, #{total_shots} shots fired at #{total_players} players. Luckiest: #{h_luck_percent[0].join(',')} (#{sprintf '%.1f', h_luck_percent[1]}% clicks). Unluckiest: #{l_luck_percent[0].join(',')} (#{sprintf '%.1f', l_luck_percent[1]}% clicks). Highest survival rate: #{h_win_percent[0].join(',')} (#{sprintf '%.1f', h_win_percent[1]}%). Lowest survival rate: #{l_win_percent[0].join(',')} (#{sprintf '%.1f', l_win_percent[1]}%). Most wins: #{won_most[0].join(',')} (#{won_most[1]}). Most deaths: #{died_most[0].join(',')} (#{died_most[1]})."
+    if total_games < 1
+      m.reply "roulette stats: no games played yet"
+    else
+      m.reply "roulette stats: #{total_games} games completed, #{total_shots} shots fired at #{total_players} players. Luckiest: #{h_luck_percent[0].join(',')} (#{sprintf '%.1f', h_luck_percent[1]}% clicks). Unluckiest: #{l_luck_percent[0].join(',')} (#{sprintf '%.1f', l_luck_percent[1]}% clicks). Highest survival rate: #{h_win_percent[0].join(',')} (#{sprintf '%.1f', h_win_percent[1]}%). Lowest survival rate: #{l_win_percent[0].join(',')} (#{sprintf '%.1f', l_win_percent[1]}%). Most wins: #{won_most[0].join(',')} (#{won_most[1]}). Most deaths: #{died_most[0].join(',')} (#{died_most[1]})."
+    end
   end
 end
 plugin = RoulettePlugin.new
-plugin.register("roulette")
+plugin.map 'roulette reload', :action => 'reload'
+plugin.map 'roulette stats :player', :action => 'playerstats'
+plugin.map 'roulette stats', :action => 'stats'
+plugin.map 'roulette clearstats', :action => 'clearstats', :auth => 'config'
+plugin.map 'roulette'
index 6bd86a7094a4dc03311a389e8fdf91cf744b50f7..80d52f6595767540124a5214b1a2e90b35501707 100644 (file)
@@ -1,6 +1,23 @@
 Saw = Struct.new("Saw", :nick, :time, :type, :where, :message)
 
 class SeenPlugin < Plugin
+  # turn a number of seconds into a human readable string, e.g
+  # 2 days, 3 hours, 18 minutes, 10 seconds
+  def secs_to_string(secs)
+    ret = ""
+    days = (secs / (60 * 60 * 24)).to_i
+    secs = secs % (60 * 60 * 24)
+    hours = (secs / (60 * 60)).to_i
+    secs = (secs % (60 * 60))
+    mins = (secs / 60).to_i
+    secs = (secs % 60).to_i
+    ret += "#{days} days, " if days > 0
+    ret += "#{hours} hours, " if hours > 0 || days > 0
+    ret += "#{mins} minutes and " if mins > 0 || hours > 0 || days > 0
+    ret += "#{secs} seconds"
+    return ret
+  end
+
   def help(plugin, topic="")
     "seen <nick> => have you seen, or when did you last see <nick>"
   end
@@ -23,7 +40,7 @@ class SeenPlugin < Plugin
   def listen(m)
     # keep database up to date with who last said what
     if m.kind_of?(PrivMessage)
-      return if m.private? || m.address?
+      return if m.private?
       if m.action?
         @registry[m.sourcenick] = Saw.new(m.sourcenick.dup, Time.new, "ACTION", 
                                           m.target, m.message.dup)
@@ -63,7 +80,7 @@ class SeenPlugin < Plugin
     if (ago.to_i == 0)
       ret += "just now, "
     else
-      ret += Utils.secs_to_string(ago) + " ago, "
+      ret += secs_to_string(ago) + " ago, "
     end
 
     case saw.type
index b09ac7a789706a477a88bfa19b3fce935295aed9..1a70de08c4ceb8091a1c6e4b8ee1a19384b3ab70 100644 (file)
@@ -6,23 +6,11 @@ class SlashdotPlugin < Plugin
   def help(plugin, topic="")
     "slashdot search <string> [<max>=4] => search slashdot for <string>, slashdot [<max>=4] => return up to <max> slashdot headlines (use negative max to return that many headlines, but all on one line.)"
   end
-  def privmsg(m)
-    if m.params && m.params =~ /^search\s+(.*)\s+(\d+)$/
-      search = $1
-      limit = $2.to_i
-      search_slashdot m, search, limit
-    elsif m.params && m.params =~ /^search\s+(.*)$/
-      search = $1
-      search_slashdot m, search
-    elsif m.params && m.params =~ /^([-\d]+)$/
-      limit = $1.to_i
-      slashdot m, limit
-    else
-      slashdot m
-    end
-  end
   
-  def search_slashdot(m, search, max=4)
+  def search_slashdot(m, params)
+   max = params[:limit].to_i
+   search = params[:search].to_s
+
     begin
       xml = @bot.httputil.get(URI.parse("http://slashdot.org/search.pl?content_type=rss&query=#{URI.escape(search)}"))
     rescue URI::InvalidURIError, URI::BadURIError => e
@@ -33,6 +21,7 @@ class SlashdotPlugin < Plugin
       m.reply "search for #{search} failed"
       return
     end
+    puts xml.inspect
     begin
       doc = Document.new xml
     rescue REXML::ParseException => e
@@ -44,6 +33,7 @@ class SlashdotPlugin < Plugin
       m.reply "search for #{search} failed"
       return
     end
+    puts doc.inspect
     max = 8 if max > 8
     done = 0
     doc.elements.each("*/item") {|e|
@@ -54,9 +44,15 @@ class SlashdotPlugin < Plugin
       done += 1
       break if done >= max
     }
+    unless done > 0
+      m.reply "search for #{search} failed"
+    end
   end
   
-  def slashdot(m, max=4)
+  def slashdot(m, params)
+    puts params.inspect
+    max = params[:limit].to_i
+    puts "max is #{max}"
     xml = @bot.httputil.get(URI.parse("http://slashdot.org/slashdot.xml"))
     unless xml
       m.reply "slashdot news parse failed"
@@ -92,4 +88,7 @@ class SlashdotPlugin < Plugin
   end
 end
 plugin = SlashdotPlugin.new
-plugin.register("slashdot")
+plugin.map 'slashdot search :limit *search', :action => 'search_slashdot',
+           :defaults => {:limit => 4}, :requirements => {:limit => /^-?\d+$/}
+plugin.map 'slashdot :limit', :defaults => {:limit => 4},
+                              :requirements => {:limit => /^-?\d+$/}
index 77ca5227e9391b2c73fdc6893a5acdb9d0df1e5e..853167184533ffe8330f701850ac15cb6704870f 100644 (file)
@@ -9,16 +9,9 @@ class TubePlugin < Plugin
   def help(plugin, topic="")
   "tube [district|circle|metropolitan|central|jubilee|bakerloo|waterloo_city|hammersmith_city|victoria|eastlondon|northern|piccadilly] => display tube service status for the specified line(Docklands Light Railway is not currently supported), tube stations => list tube stations (not lines) with problems"
   end
-  def privmsg(m)
-  if m.params && m.params =~ /^stations$/
-    check_stations m
-  elsif m.params && m.params =~ /^(.*)$/
-    line = $1.downcase.capitalize
-    check_tube m, line
-  end
-  end
   
-  def check_tube(m, line)
+  def tube(m, params)
+    line = params[:line]
   begin
     tube_page = @bot.httputil.get(URI.parse("http://www.tfl.gov.uk/tfl/service_rt_tube.shtml"), 1, 1)
   rescue URI::InvalidURIError, URI::BadURIError => e
@@ -47,7 +40,7 @@ class TubePlugin < Plugin
   m.reply "No Problems on the #{line} line."
   end
 
-  def check_stations(m)
+  def check_stations(m, params)
     begin
       tube_page = @bot.httputil.get(URI.parse("http://www.tfl.gov.uk/tfl/service_rt_tube.shtml"))
     rescue URI::InvalidURIError, URI::BadURIError => e
@@ -74,4 +67,5 @@ class TubePlugin < Plugin
   end
 end
 plugin = TubePlugin.new
-plugin.register("tube")
+plugin.map 'tube stations', :action => 'check_stations'
+plugin.map 'tube :line'
index ed82d1c19b745461a7792a3d04521425810f8d69..ced921330882844ee405aa74e49d820982788600 100644 (file)
@@ -6,7 +6,7 @@ class UrlPlugin < Plugin
     @registry.set_default(Array.new)
   end
   def help(plugin, topic="")
-    "urls [<max>=4] => list <max> last urls mentioned in current channel, urls <channel> [<max>=4] => list <max> last urls mentioned in <channel>, urls search <regexp> => search for matching urls, urls search <channel> <regexp>, search for matching urls in channel <channel>"
+    "urls [<max>=4] => list <max> last urls mentioned in current channel, urls search [<max>=4] <regexp> => search for matching urls. In a private message, you must specify the channel to query, eg. urls <channel> [max], urls search <channel> [max] <regexp>"
   end
   def listen(m)
     return unless m.kind_of?(PrivMessage)
@@ -14,10 +14,15 @@ class UrlPlugin < Plugin
     # TODO support multiple urls in one line
     if m.message =~ /(f|ht)tps?:\/\//
       if m.message =~ /((f|ht)tps?:\/\/.*?)(?:\s+|$)/
-        url = Url.new(m.target, m.sourcenick, Time.new, $1)
+        urlstr = $1
         list = @registry[m.target]
+        # check to see if this url is already listed
+        return if list.find {|u|
+          u.url == urlstr
+        }
+        url = Url.new(m.target, m.sourcenick, Time.new, urlstr)
         debug "#{list.length} urls so far"
-        if list.length > 50
+        if list.length > 50 # TODO make this configurable
           list.pop
         end
         debug "storing url #{url.url}"
@@ -27,45 +32,10 @@ class UrlPlugin < Plugin
       end
     end
   end
-  def privmsg(m)
-    case m.params
-    when nil
-      if m.public?
-        urls m, m.target
-      else
-        m.reply "in a private message, you need to specify a channel name for urls"
-      end
-    when (/^(\d+)$/)
-      max = $1.to_i
-      if m.public?
-        urls m, m.target, max
-      else
-        m.reply "in a private message, you need to specify a channel name for urls"
-      end
-    when (/^(#.*?)\s+(\d+)$/)
-      channel = $1
-      max = $2.to_i
-      urls m, channel, max
-    when (/^(#.*?)$/)
-      channel = $1
-      urls m, channel
-    when (/^search\s+(#.*?)\s+(.*)$/)
-      channel = $1
-      string = $2
-      search m, channel, string
-    when (/^search\s+(.*)$/)
-      string = $1
-      if m.public?
-        search m, m.target, string
-      else
-        m.reply "in a private message, you need to specify a channel name for urls"
-      end
-    else
-      m.reply "incorrect usage: " + help(m.plugin)
-    end
-  end
 
-  def urls(m, channel, max=4)
+  def urls(m, params)
+    channel = params[:channel] ? params[:channel] : m.target
+    max = params[:limit].to_i
     max = 10 if max > 10
     max = 1 if max < 1
     list = @registry[channel]
@@ -78,7 +48,10 @@ class UrlPlugin < Plugin
     end
   end
 
-  def search(m, channel, string, max=4)
+  def search(m, params)
+    channel = params[:channel] ? params[:channel] : m.target
+    max = params[:limit].to_i
+    string = params[:string]
     max = 10 if max > 10
     max = 1 if max < 1
     regex = Regexp.new(string)
@@ -95,4 +68,17 @@ class UrlPlugin < Plugin
   end
 end
 plugin = UrlPlugin.new
-plugin.register("urls")
+plugin.map 'urls search :channel :limit :string', :action => 'search',
+                          :defaults => {:limit => 4},
+                          :requirements => {:limit => /^\d+$/},
+                          :public => false
+plugin.map 'urls search :limit :string', :action => 'search',
+                          :defaults => {:limit => 4},
+                          :requirements => {:limit => /^\d+$/},
+                          :private => false
+plugin.map 'urls :channel :limit', :defaults => {:limit => 4},
+                          :requirements => {:limit => /^\d+$/},
+                          :public => false
+plugin.map 'urls :limit', :defaults => {:limit => 4},
+                          :requirements => {:limit => /^\d+$/},
+                          :private => false
index 3e4134e4b4c70c88531337ff316976f1f29e35aa..f2c3e3683887fde074eb2267da96af0e47086b5d 100644 (file)
@@ -1,8 +1,605 @@
+# This is nasty-ass. I hate writing parsers.
+class Metar
+  attr_reader :decoded
+  attr_reader :input
+  attr_reader :date
+  attr_reader :nodata
+  def initialize(string)
+    str = nil
+    @nodata = false
+    string.each_line {|l|
+      if str == nil
+        # grab first line (date)
+        @date = l.chomp.strip
+        str = ""
+      else
+        if(str == "")
+          str = l.chomp.strip
+        else
+          str += " " + l.chomp.strip
+        end
+      end
+    }
+    if @date && @date =~ /^(\d+)\/(\d+)\/(\d+) (\d+):(\d+)$/
+      # 2002/02/26 05:00
+      @date = Time.gm($1, $2, $3, $4, $5, 0)
+    else
+      @date = Time.now
+    end
+    @input = str.chomp
+    @cloud_layers = 0
+    @cloud_coverage = {
+      'SKC' => '0',
+      'CLR' => '0',
+      'VV'  => '8/8',
+      'FEW' => '1/8 - 2/8',
+      'SCT' => '3/8 - 4/8',
+      'BKN' => '5/8 - 7/8',
+      'OVC' => '8/8'
+    }
+    @wind_dir_texts = [
+      'North',
+      'North/Northeast',
+      'Northeast',
+      'East/Northeast',
+      'East',
+      'East/Southeast',
+      'Southeast',
+      'South/Southeast',
+      'South',
+      'South/Southwest',
+      'Southwest',
+      'West/Southwest',
+      'West',
+      'West/Northwest',
+      'Northwest',
+      'North/Northwest',
+      'North'
+    ]
+    @wind_dir_texts_short = [
+      'N',
+      'N/NE',
+      'NE',
+      'E/NE',
+      'E',
+      'E/SE',
+      'SE',
+      'S/SE',
+      'S',
+      'S/SW',
+      'SW',
+      'W/SW',
+      'W',
+      'W/NW',
+      'NW',
+      'N/NW',
+      'N'
+    ]
+    @weather_array = {
+      'MI' => 'Mild ',
+      'PR' => 'Partial ',
+      'BC' => 'Patches ',
+      'DR' => 'Low Drifting ',
+      'BL' => 'Blowing ',
+      'SH' => 'Shower(s) ',
+      'TS' => 'Thunderstorm ',
+      'FZ' => 'Freezing',
+      'DZ' => 'Drizzle ',
+      'RA' => 'Rain ',
+      'SN' => 'Snow ',
+      'SG' => 'Snow Grains ',
+      'IC' => 'Ice Crystals ',
+      'PE' => 'Ice Pellets ',
+      'GR' => 'Hail ',
+      'GS' => 'Small Hail and/or Snow Pellets ',
+      'UP' => 'Unknown ',
+      'BR' => 'Mist ',
+      'FG' => 'Fog ',
+      'FU' => 'Smoke ',
+      'VA' => 'Volcanic Ash ',
+      'DU' => 'Widespread Dust ',
+      'SA' => 'Sand ',
+      'HZ' => 'Haze ',
+      'PY' => 'Spray',
+      'PO' => 'Well-Developed Dust/Sand Whirls ',
+      'SQ' => 'Squalls ',
+      'FC' => 'Funnel Cloud Tornado Waterspout ',
+      'SS' => 'Sandstorm/Duststorm '
+    }
+    @cloud_condition_array = {
+      'SKC' => 'clear',
+      'CLR' => 'clear',
+      'VV'  => 'vertical visibility',
+      'FEW' => 'a few',
+      'SCT' => 'scattered',
+      'BKN' => 'broken',
+      'OVC' => 'overcast'
+    }
+    @strings = {
+      'mm_inches'             => '%s mm (%s inches)',
+      'precip_a_trace'        => 'a trace',
+      'precip_there_was'      => 'There was %s of precipitation ',
+      'sky_str_format1'       => 'There were %s at a height of %s meters (%s feet)',
+      'sky_str_clear'         => 'The sky was clear',
+      'sky_str_format2'       => ', %s at a height of %s meter (%s feet) and %s at a height of %s meters (%s feet)',
+      'sky_str_format3'       => ' and %s at a height of %s meters (%s feet)',
+      'clouds'                => ' clouds',
+      'clouds_cb'             => ' cumulonimbus clouds',
+      'clouds_tcu'            => ' towering cumulus clouds',
+      'visibility_format'     => 'The visibility was %s kilometers (%s miles).',
+      'wind_str_format1'      => 'blowing at a speed of %s meters per second (%s miles per hour)',
+      'wind_str_format2'      => ', with gusts to %s meters per second (%s miles per hour),',
+      'wind_str_format3'      => ' from the %s',
+      'wind_str_calm'         => 'calm',
+      'precip_last_hour'      => 'in the last hour. ',
+      'precip_last_6_hours'   => 'in the last 3 to 6 hours. ',
+      'precip_last_24_hours'  => 'in the last 24 hours. ',
+      'precip_snow'           => 'There is %s mm (%s inches) of snow on the ground. ',
+      'temp_min_max_6_hours'  => 'The maximum and minimum temperatures over the last 6 hours were %s and %s degrees Celsius (%s and %s degrees Fahrenheit).',
+      'temp_max_6_hours'      => 'The maximum temperature over the last 6 hours was %s degrees Celsius (%s degrees Fahrenheit). ',
+      'temp_min_6_hours'      => 'The minimum temperature over the last 6 hours was %s degrees Celsius (%s degrees Fahrenheit). ',
+      'temp_min_max_24_hours' => 'The maximum and minimum temperatures over the last 24 hours were %s and %s degrees Celsius (%s and %s degrees Fahrenheit). ',
+      'light'                 => 'Light ',
+      'moderate'              => 'Moderate ',
+      'heavy'                 => 'Heavy ',
+      'mild'                  => 'Mild ',
+      'nearby'                => 'Nearby ',
+      'current_weather'       => 'Current weather is %s. ',
+      'pretty_print_metar'    => '%s on %s, the wind was %s at %s. The temperature was %s degrees Celsius (%s degrees Fahrenheit), and the pressure was %s hPa (%s inHg). The relative humidity was %s%%. %s %s %s %s %s'
+    }
+
+    parse
+  end
+
+  def store_speed(value, windunit, meterspersec, knots, milesperhour)
+    # Helper function to convert and store speed based on unit.
+    # &$meterspersec, &$knots and &$milesperhour are passed on
+    # reference
+    if (windunit == 'KT')
+      # The windspeed measured in knots:
+      @decoded[knots] = sprintf("%.2f", value)
+      # The windspeed measured in meters per second, rounded to one decimal place:
+      @decoded[meterspersec] = sprintf("%.2f", value.to_f * 0.51444)
+      # The windspeed measured in miles per hour, rounded to one decimal place: */
+      @decoded[milesperhour] = sprintf("%.2f", value.to_f * 1.1507695060844667)
+    elsif (windunit == 'MPS')
+      # The windspeed measured in meters per second:
+      @decoded[meterspersec] = sprintf("%.2f", value)
+      # The windspeed measured in knots, rounded to one decimal place:
+      @decoded[knots] = sprintf("%.2f", value.to_f / 0.51444)
+      #The windspeed measured in miles per hour, rounded to one decimal place:
+      @decoded[milesperhour] = sprintf("%.1f", value.to_f / 0.51444 * 1.1507695060844667)
+    elsif (windunit == 'KMH')
+      # The windspeed measured in kilometers per hour:
+      @decoded[meterspersec] = sprintf("%.1f", value.to_f * 1000 / 3600)
+      @decoded[knots] = sprintf("%.1f", value.to_f * 1000 / 3600 / 0.51444)
+      # The windspeed measured in miles per hour, rounded to one decimal place:
+      @decoded[milesperhour] = sprintf("%.1f", knots.to_f * 1.1507695060844667)
+    end
+  end
+  
+  def parse
+    @decoded = Hash.new
+    puts @input
+    @input.split(" ").each {|part|
+      if (part == 'METAR')
+        # Type of Report: METAR
+        @decoded['type'] = 'METAR'
+      elsif (part == 'SPECI')
+        # Type of Report: SPECI
+        @decoded['type'] = 'SPECI'
+      elsif (part == 'AUTO')
+        # Report Modifier: AUTO
+        @decoded['report_mod'] = 'AUTO'
+      elsif (part == 'NIL')
+        @nodata = true
+      elsif (part =~ /^\S{4}$/ && ! (@decoded.has_key?('station')))
+        # Station Identifier
+        @decoded['station'] = part
+      elsif (part =~ /([0-9]{2})([0-9]{2})([0-9]{2})Z/)
+        # ignore this bit, it's useless without month/year. some of these
+        # things are hideously out of date.
+        # now = Time.new
+        # time = Time.gm(now.year, now.month, $1, $2, $3, 0)
+        # Date and Time of Report
+        # @decoded['time'] = time
+      elsif (part == 'COR')
+        # Report Modifier: COR
+        @decoded['report_mod'] = 'COR'
+      elsif (part =~ /([0-9]{3}|VRB)([0-9]{2,3}).*(KT|MPS|KMH)/)
+        # Wind Group
+        windunit = $3
+        # now do ereg to get the actual values
+        part =~ /([0-9]{3}|VRB)([0-9]{2,3})((G[0-9]{2,3})?#{windunit})/
+        if ($1 == 'VRB')
+          @decoded['wind_deg'] = 'variable directions'
+          @decoded['wind_dir_text'] = 'variable directions'
+          @decoded['wind_dir_text_short'] = 'VAR'
+        else
+          @decoded['wind_deg'] = $1
+          @decoded['wind_dir_text'] = @wind_dir_texts[($1.to_i/22.5).round]
+          @decoded['wind_dir_text_short'] = @wind_dir_texts_short[($1.to_i/22.5).round]
+        end
+        store_speed($2, windunit,
+                    'wind_meters_per_second',
+                    'wind_knots',
+                    'wind_miles_per_hour')
+
+        if ($4 != nil)
+          # We have a report with information about the gust.
+          # First we have the gust measured in knots
+    if ($4 =~ /G([0-9]{2,3})/)
+          store_speed($1,windunit,
+                      'wind_gust_meters_per_second',
+                      'wind_gust_knots',
+                      'wind_gust_miles_per_hour')
+    end
+        end
+      elsif (part =~ /([0-9]{3})V([0-9]{3})/)
+        #  Variable wind-direction
+        @decoded['wind_var_beg'] = $1
+        @decoded['wind_var_end'] = $2
+      elsif (part == "9999")
+        # A strange value. When you look at other pages you see it
+        # interpreted like this (where I use > to signify 'Greater
+        # than'):
+        @decoded['visibility_miles'] = '>7';
+        @decoded['visibility_km']    = '>11.3';
+      elsif (part =~ /^([0-9]{4})$/)
+        # Visibility in meters (4 digits only)
+        # The visibility measured in kilometers, rounded to one decimal place.
+        @decoded['visibility_km'] = sprintf("%.1f", $1.to_i / 1000)
+        # The visibility measured in miles, rounded to one decimal place.
+        @decoded['visibility_miles'] = sprintf("%.1f", $1.to_i / 1000 / 1.609344)
+      elsif (part =~ /^[0-9]$/)
+        # Temp Visibility Group, single digit followed by space
+        @decoded['temp_visibility_miles'] = part
+      elsif (@decoded['temp_visibility_miles'] && (@decoded['temp_visibility_miles']+' '+part) =~ /^M?(([0-9]?)[ ]?([0-9])(\/?)([0-9]*))SM$/)
+        # Visibility Group
+        if ($4 == '/')
+          vis_miles = $2.to_i + $3.to_i/$5.to_i
+        else
+          vis_miles = $1.to_i;
+        end
+        if (@decoded['temp_visibility_miles'][0] == 'M')
+          # The visibility measured in miles, prefixed with < to indicate 'Less than'
+          @decoded['visibility_miles'] = '<' + sprintf("%.1f", vis_miles)
+          # The visibility measured in kilometers. The value is rounded
+          # to one decimal place, prefixed with < to indicate 'Less than' */
+          @decoded['visibility_km']    = '<' . sprintf("%.1f", vis_miles * 1.609344)
+        else
+          # The visibility measured in mile.s */
+          @decoded['visibility_miles'] = sprintf("%.1f", vis_miles)
+          # The visibility measured in kilometers, rounded to one decimal place.
+          @decoded['visibility_km']    = sprintf("%.1f", vis_miles * 1.609344)
+        end
+      elsif (part =~ /^(-|\+|VC|MI)?(TS|SH|FZ|BL|DR|BC|PR|RA|DZ|SN|SG|GR|GS|PE|IC|UP|BR|FG|FU|VA|DU|SA|HZ|PY|PO|SQ|FC|SS|DS)+$/)
+        # Current weather-group
+        @decoded['weather'] = '' unless @decoded.has_key?('weather')
+        if (part[0].chr == '-')
+          # A light phenomenon
+          @decoded['weather'] += @strings['light']
+          part = part[1,part.length]
+        elsif (part[0].chr == '+')
+          # A heavy phenomenon
+          @decoded['weather'] += @strings['heavy']
+          part = part[1,part.length]
+        elsif (part[0,2] == 'VC')
+          # Proximity Qualifier
+          @decoded['weather'] += @strings['nearby']
+          part = part[2,part.length]
+        elsif (part[0,2] == 'MI')
+          @decoded['weather'] += @strings['mild']
+          part = part[2,part.length]
+        else
+          # no intensity code => moderate phenomenon
+          @decoded['weather'] += @strings['moderate']
+        end
+        
+        while (part && bite = part[0,2]) do
+          # Now we take the first two letters and determine what they
+          # mean. We append this to the variable so that we gradually
+          # build up a phrase.
+
+          @decoded['weather'] += @weather_array[bite]
+          # Here we chop off the two first letters, so that we can take
+          # a new bite at top of the while-loop.
+          part = part[2,-1]
+        end
+      elsif (part =~ /(SKC|CLR)/)
+        # Cloud-layer-group.
+        # There can be up to three of these groups, so we store them as
+        # cloud_layer1, cloud_layer2 and cloud_layer3.
+        
+        @cloud_layers += 1;
+        # Again we have to translate the code-characters to a
+        # meaningful string.
+        @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_condition']  = @cloud_condition_array[$1]
+        @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_coverage'] = @cloud_coverage[$1]
+      elsif (part =~ /^(VV|FEW|SCT|BKN|OVC)([0-9]{3})(CB|TCU)?$/)
+        # We have found (another) a cloud-layer-group. There can be up
+        # to three of these groups, so we store them as cloud_layer1,
+        # cloud_layer2 and cloud_layer3.
+        @cloud_layers += 1;
+        # Again we have to translate the code-characters to a meaningful string.
+        if ($3 == 'CB')
+          # cumulonimbus (CB) clouds were observed. */
+          @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_condition'] =
+                      @cloud_condition_array[$1] + @strings['clouds_cb']
+        elsif ($3 == 'TCU')
+          # towering cumulus (TCU) clouds were observed.
+          @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_condition'] =
+                      @cloud_condition_array[$1] + @strings['clouds_tcu']
+        else
+          @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_condition'] =
+                      @cloud_condition_array[$1] + @strings['clouds']
+        end
+        @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_coverage'] = @cloud_coverage[$1]
+        @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_altitude_ft'] = $2.to_i * 100
+        @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_altitude_m']  = ($2.to_f * 30.48).round
+      elsif (part =~ /^T([0-9]{4})$/)
+        store_temp($1,'temp_c','temp_f')
+      elsif (part =~ /^T?(M?[0-9]{2})\/(M?[0-9\/]{1,2})?$/)
+        # Temperature/Dew Point Group
+        # The temperature and dew-point measured in Celsius.
+        @decoded['temp_c'] = sprintf("%d", $1.tr('M', '-'))
+        if $2 == "//" || !$2
+          @decoded['dew_c'] = 0
+        else
+          @decoded['dew_c'] = sprintf("%.1f", $2.tr('M', '-'))
+        end
+        # The temperature and dew-point measured in Fahrenheit, rounded to
+        # the nearest degree.
+        @decoded['temp_f'] = ((@decoded['temp_c'].to_f * 9 / 5) + 32).round
+        @decoded['dew_f']  = ((@decoded['dew_c'].to_f * 9 / 5) + 32).round
+      elsif(part =~ /A([0-9]{4})/)
+        # Altimeter
+        # The pressure measured in inHg
+        @decoded['altimeter_inhg'] = sprintf("%.2f", $1.to_i/100)
+        # The pressure measured in mmHg, hPa and atm
+        @decoded['altimeter_mmhg'] = sprintf("%.1f", $1.to_f * 0.254)
+        @decoded['altimeter_hpa']  = sprintf("%d", ($1.to_f * 0.33863881578947).to_i)
+        @decoded['altimeter_atm']  = sprintf("%.3f", $1.to_f * 3.3421052631579e-4)
+      elsif(part =~ /Q([0-9]{4})/)
+        # Altimeter
+        # This is strange, the specification doesnt say anything about
+        # the Qxxxx-form, but it's in the METARs.
+        # The pressure measured in hPa
+        @decoded['altimeter_hpa']  = sprintf("%d", $1.to_i)
+        # The pressure measured in mmHg, inHg and atm
+        @decoded['altimeter_mmhg'] = sprintf("%.1f", $1.to_f * 0.7500616827)
+        @decoded['altimeter_inhg'] = sprintf("%.2f", $1.to_f * 0.0295299875)
+        @decoded['altimeter_atm']  = sprintf("%.3f", $1.to_f * 9.869232667e-4)
+      elsif (part =~ /^T([0-9]{4})([0-9]{4})/)
+        # Temperature/Dew Point Group, coded to tenth of degree.
+        # The temperature and dew-point measured in Celsius.
+        store_temp($1,'temp_c','temp_f')
+        store_temp($2,'dew_c','dew_f')
+      elsif (part =~ /^1([0-9]{4}$)/)
+        # 6 hour maximum temperature Celsius, coded to tenth of degree
+        store_temp($1,'temp_max6h_c','temp_max6h_f')
+      elsif (part =~ /^2([0-9]{4}$)/)
+        # 6 hour minimum temperature Celsius, coded to tenth of degree
+        store_temp($1,'temp_min6h_c','temp_min6h_f')
+      elsif (part =~ /^4([0-9]{4})([0-9]{4})$/)
+        # 24 hour maximum and minimum temperature Celsius, coded to
+        # tenth of degree
+        store_temp($1,'temp_max24h_c','temp_max24h_f')
+        store_temp($2,'temp_min24h_c','temp_min24h_f')
+      elsif (part =~ /^P([0-9]{4})/)
+        # Precipitation during last hour in hundredths of an inch
+        # (store as inches)
+        @decoded['precip_in'] = sprintf("%.2f", $1.to_f/100)
+        @decoded['precip_mm'] = sprintf("%.2f", $1.to_f * 0.254)
+      elsif (part =~ /^6([0-9]{4})/)
+        # Precipitation during last 3 or 6 hours in hundredths of an
+        # inch  (store as inches)
+        @decoded['precip_6h_in'] = sprintf("%.2f", $1.to_f/100)
+        @decoded['precip_6h_mm'] = sprintf("%.2f", $1.to_f * 0.254)
+      elsif (part =~ /^7([0-9]{4})/)
+        # Precipitation during last 24 hours in hundredths of an inch
+        # (store as inches)
+        @decoded['precip_24h_in'] = sprintf("%.2f", $1.to_f/100)
+        @decoded['precip_24h_mm'] = sprintf("%.2f", $1.to_f * 0.254)
+      elsif(part =~ /^4\/([0-9]{3})/)
+        # Snow depth in inches
+        @decoded['snow_in'] = sprintf("%.2f", $1);
+        @decoded['snow_mm'] = sprintf("%.2f", $1.to_f * 25.4)
+      else
+        # If we couldn't match the group, we assume that it was a
+        # remark.
+        @decoded['remarks'] = '' unless @decoded.has_key?("remarks")
+        @decoded['remarks'] += ' ' + part;
+      end
+    }
+    
+    # Relative humidity
+    # p @decoded['dew_c'] # 11.0
+    # p @decoded['temp_c'] # 21.0
+    # => 56.1
+    @decoded['rel_humidity'] = sprintf("%.1f",100 * 
+      (6.11 * (10.0**(7.5 * @decoded['dew_c'].to_f / (237.7 + @decoded['dew_c'].to_f)))) / (6.11 * (10.0 ** (7.5 * @decoded['temp_c'].to_f / (237.7 + @decoded['temp_c'].to_f))))) if @decoded.has_key?('dew_c')
+  end
+
+  def store_temp(temp,temp_cname,temp_fname)
+    # Given a numerical temperature temp in Celsius, coded to tenth of
+    # degree, store in @decoded[temp_cname], convert to Fahrenheit
+    # and store in @decoded[temp_fname]
+    # Note: temp is converted to negative if temp > 100.0 (See
+    # Federal Meteorological Handbook for groups T, 1, 2 and 4)
+
+    # Temperature measured in Celsius, coded to tenth of degree
+    temp = temp.to_f/10
+    if (temp >100.0) 
+        # first digit = 1 means minus temperature
+        temp = -(temp - 100.0)
+    end
+    @decoded[temp_cname] = sprintf("%.1f", temp)
+    # The temperature in Fahrenheit.
+    @decoded[temp_fname] = sprintf("%.1f", (temp * 9 / 5) + 32)        
+  end
+
+    def pretty_print_precip(precip_mm, precip_in)
+      # Returns amount if $precip_mm > 0, otherwise "trace" (see Federal
+      # Meteorological Handbook No. 1 for code groups P, 6 and 7) used in
+      # several places, so standardized in one function.
+      if (precip_mm.to_i > 0)
+        amount = sprintf(@strings['mm_inches'], precip_mm, precip_in)
+      else
+        amount = @strings['a_trace']
+      end
+      return sprintf(@strings['precip_there_was'], amount)
+  end
+
+  def pretty_print
+    if @nodata
+      return "The weather stored for #{@decoded['station']} consists of the string 'NIL' :("
+    end
+
+    ["temp_c", "altimeter_hpa"].each {|key|
+      if !@decoded.has_key?(key)
+        return "The weather stored for #{@decoded['station']} could not be parsed (#{@input})"
+      end
+    }
+    
+    mins_old = ((Time.now - @date.to_i).to_f/60).round
+    if (mins_old <= 60)
+      weather_age = mins_old.to_s + " minutes ago,"
+    elsif (mins_old <= 60 * 25)
+      weather_age = (mins_old / 60).to_s + " hours, "
+      weather_age += (mins_old % 60).to_s + " minutes ago,"
+    else
+      # return "The weather stored for #{@decoded['station']} is hideously out of date :( (Last update #{@date})"
+      weather_age = "The weather stored for #{@decoded['station']} is hideously out of date :( here it is anyway:"
+    end
+    
+    if(@decoded.has_key?("cloud_layer1_altitude_ft"))
+      sky_str = sprintf(@strings['sky_str_format1'],
+                        @decoded["cloud_layer1_condition"],
+                        @decoded["cloud_layer1_altitude_m"],
+                        @decoded["cloud_layer1_altitude_ft"])
+    else
+      sky_str = @strings['sky_str_clear']
+    end
+
+    if(@decoded.has_key?("cloud_layer2_altitude_ft"))
+      if(@decoded.has_key?("cloud_layer3_altitude_ft"))
+        sky_str += sprintf(@strings['sky_str_format2'],
+                          @decoded["cloud_layer2_condition"],
+                          @decoded["cloud_layer2_altitude_m"],
+                          @decoded["cloud_layer2_altitude_ft"],
+                          @decoded["cloud_layer3_condition"],
+                          @decoded["cloud_layer3_altitude_m"],
+                          @decoded["cloud_layer3_altitude_ft"])
+      else
+        sky_str += sprintf(@strings['sky_str_format3'],
+                          @decoded["cloud_layer2_condition"],
+                          @decoded["cloud_layer2_altitude_m"],
+                          @decoded["cloud_layer2_altitude_ft"])
+      end
+    end
+    sky_str += "."
+
+    if(@decoded.has_key?("visibility_miles"))
+      visibility = sprintf(@strings['visibility_format'],
+                          @decoded["visibility_km"],
+                          @decoded["visibility_miles"])
+    else
+      visibility = ""
+    end
+
+    if (@decoded.has_key?("wind_meters_per_second") && @decoded["wind_meters_per_second"].to_i > 0)
+      wind_str = sprintf(@strings['wind_str_format1'],
+                        @decoded["wind_meters_per_second"],
+                        @decoded["wind_miles_per_hour"])
+      if (@decoded.has_key?("wind_gust_meters_per_second") && @decoded["wind_gust_meters_per_second"].to_i > 0)
+        wind_str += sprintf(@strings['wind_str_format2'],
+                            @decoded["wind_gust_meters_per_second"],
+                            @decoded["wind_gust_miles_per_hour"])
+      end
+      wind_str += sprintf(@strings['wind_str_format3'],
+                          @decoded["wind_dir_text"])
+    else
+      wind_str = @strings['wind_str_calm']
+    end
+
+    prec_str = ""
+    if (@decoded.has_key?("precip_in"))
+      prec_str += pretty_print_precip(@decoded["precip_mm"], @decoded["precip_in"]) + @strings['precip_last_hour']
+    end
+    if (@decoded.has_key?("precip_6h_in"))
+      prec_str += pretty_print_precip(@decoded["precip_6h_mm"], @decoded["precip_6h_in"]) + @strings['precip_last_6_hours']
+    end
+    if (@decoded.has_key?("precip_24h_in"))
+      prec_str += pretty_print_precip(@decoded["precip_24h_mm"], @decoded["precip_24h_in"]) + @strings['precip_last_24_hours']
+    end
+    if (@decoded.has_key?("snow_in"))
+      prec_str += sprintf(@strings['precip_snow'], @decoded["snow_mm"], @decoded["snow_in"])
+    end
+
+    temp_str = ""
+    if (@decoded.has_key?("temp_max6h_c") && @decoded.has_key?("temp_min6h_c"))
+      temp_str += sprintf(@strings['temp_min_max_6_hours'],
+                          @decoded["temp_max6h_c"],
+                          @decoded["temp_min6h_c"],
+                          @decoded["temp_max6h_f"],
+                          @decoded["temp_min6h_f"])
+    else
+      if (@decoded.has_key?("temp_max6h_c"))
+        temp_str += sprintf(@strings['temp_max_6_hours'],
+                            @decoded["temp_max6h_c"],
+                            @decoded["temp_max6h_f"])
+      end
+      if (@decoded.has_key?("temp_min6h_c"))
+        temp_str += sprintf(@strings['temp_max_6_hours'],
+                            @decoded["temp_min6h_c"],
+                            @decoded["temp_min6h_f"])
+      end
+    end
+    if (@decoded.has_key?("temp_max24h_c"))
+      temp_str += sprintf(@strings['temp_min_max_24_hours'],
+                          @decoded["temp_max24h_c"],
+                          @decoded["temp_min24h_c"],
+                          @decoded["temp_max24h_f"],
+                          @decoded["temp_min24h_f"])
+    end
+
+    if (@decoded.has_key?("weather"))
+      weather_str = sprintf(@strings['current_weather'], @decoded["weather"])
+    else
+      weather_str = ''
+    end
+
+    return sprintf(@strings['pretty_print_metar'],
+                  weather_age,
+                  @date,
+                  wind_str, @decoded["station"], @decoded["temp_c"],
+                  @decoded["temp_f"], @decoded["altimeter_hpa"],
+                  @decoded["altimeter_inhg"],
+                  @decoded["rel_humidity"], sky_str,
+                  visibility, weather_str, prec_str, temp_str).strip
+  end
+
+  def to_s
+    @input
+  end
+end
+
+
 class WeatherPlugin < Plugin
   
   def help(plugin, topic="")
-    "weather <ICAO> => 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"
+    "weather <ICAO> => display the current weather at the location specified by the ICAO code [Lookup your ICAO code at http://www.nws.noaa.gov/tg/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"
+  end
+
+  def get_metar(station)
+    station.upcase!
+    
+    result = @bot.httputil.get(URI.parse("http://weather.noaa.gov/pub/data/observations/metar/stations/#{station}.TXT"))
+    return nil unless result
+    return Metar.new(result)
   end
+
   
   def initialize
     super
@@ -23,7 +620,7 @@ class WeatherPlugin < Plugin
        Time.now - @metar_cache[where].date < 3600
       met = @metar_cache[where]
     else
-      met = Utils.get_metar(where)
+      met = get_metar(where)
     end
     
     if met
@@ -33,23 +630,20 @@ class WeatherPlugin < Plugin
       m.reply "couldn't find weather data for #{where}"
     end
   end
-  
-  def privmsg(m)
-    case m.params
-    when nil
+
+  def weather(m, params)
+    if params[:where]
+      @registry[m.sourcenick] = params[:where]
+      describe(m,params[:where])
+    else
       if @registry.has_key?(m.sourcenick)
         where = @registry[m.sourcenick]
         describe(m,where)
       else
-        m.reply "I don't know where #{m.sourcenick} is!"
+        m.reply "I don't know where you are yet! Lookup your code at http://www.nws.noaa.gov/tg/siteloc.shtml and tell me 'weather <code>', then I'll know."
       end
-    when (/^(\S{4})$/)
-      where = $1
-      @registry[m.sourcenick] = where
-      describe(m,where)
     end
   end
-  
 end
 plugin = WeatherPlugin.new
-plugin.register("weather")
+plugin.map 'weather :where', :defaults => {:where => false}
index e1fe10bd341f1eafc675659c566612c3d4db8793..fb4738c173a13bcd6e662227478e5c6c97071d34 100644 (file)
@@ -6,14 +6,10 @@ class WserverPlugin < Plugin
   def help(plugin, topic="")
     "wserver <uri> => try and determine what webserver <uri> is using"
   end
-  def privmsg(m)
-    unless(m.params && m.params =~ /^\S+$/)
-      m.reply "incorrect usage: " + help(m.plugins)
-      return
-    end
 
+  def wserver(m, params)
     redirect_count = 0
-    hostname = m.params.dup
+    hostname = params[:host].dup
     hostname = "http://#{hostname}" unless hostname =~ /:\/\//
     begin
       if(redirect_count > 3)
@@ -24,7 +20,7 @@ class WserverPlugin < Plugin
       begin
         uri = URI.parse(hostname)
       rescue URI::InvalidURIError => err
-        m.reply "#{m.params} is not a valid URI"
+        m.reply "#{hostname} is not a valid URI"
         return
       end
       
@@ -72,4 +68,4 @@ class WserverPlugin < Plugin
   end
 end
 plugin = WserverPlugin.new
-plugin.register("wserver")
+plugin.map 'wserver :host'
index d985fa35d7d2d8e50e8cc94d8ef3cee389355c9e..3648b1e9ade7787a7b2d4d3d3ee4c9f2f619e5c6 100644 (file)
@@ -1,4 +1,4 @@
 lb<=is=>http://linuxbrit.co.uk
 offended<=is=><reply><who> is offended!
-giblet<=is=>My master!
+giblet<=is=><reply>daddy!
 rbot<=is=><reply>That's me! :-))
index ce338e3be8b46739923a17d5444cb29e105ec7d5..a445d5bed0deebe7a6a23dfd2b2e8fd60498cbf1 100644 (file)
@@ -10,7 +10,7 @@
 50 join
 15 delquote
 12 msginsult
-12 remind+
+12 remind_other
 5 rmlart
 5 rmpraise
 5 keycmd
index e93af811e51b00129db9bc9862bf0b58d09ed155..19506ab274db3ca3569ec3df47f6e99a66571127 100644 (file)
@@ -34,6 +34,11 @@ module Irc
       @config['server.sendq_burst'] = 4
       @config['keyword.address'] = true
       @config['keyword.listen'] = false
+      @config['http.proxy'] = false
+      @config['http.proxy_include'] = false
+      @config['http.proxy_exclude'] = false
+      @config['http.proxy_user'] = false
+      @config['http.proxy_pass'] = false
 
       # TODO
       # have this class persist key/values in hash using yaml as it kinda
index ff3216a6bd62df9efac78d3bb2ff5cf276792a9c..85b56be48ba348dfefd07c4b0dda27a7f604bff5 100644 (file)
@@ -25,17 +25,19 @@ class HttpUtil
     if (ENV['http_proxy'])
       proxy = URI.parse ENV['http_proxy']
     end
-    if (@bot.config["http_proxy"])
+    if (@bot.config["http.proxy"])
       proxy = URI.parse ENV['http_proxy']
     end
 
     # if http_proxy_include or http_proxy_exclude are set, then examine the
     # uri to see if this is a proxied uri
+    # the excludes are a list of regexps, and each regexp is checked against
+    # the server name, and its IP addresses
     if uri
-      if @bot.config["http_proxy_exclude"]
+      if @bot.config["http.proxy_exclude"]
         # TODO
       end
-      if @bot.config["http_proxy_include"]
+      if @bot.config["http.proxy_include"]
       end
     end
     
@@ -43,10 +45,10 @@ class HttpUtil
     proxy_port = nil
     proxy_user = nil
     proxy_pass = nil
-    if @bot.config["http_proxy_user"]
-      proxy_user = @bot.config["http_proxy_user"]
-      if @bot.config["http_proxy_pass"]
-        proxy_pass = @bot.config["http_proxy_pass"]
+    if @bot.config["http.proxy_user"]
+      proxy_user = @bot.config["http.proxy_user"]
+      if @bot.config["http.proxy_pass"]
+        proxy_pass = @bot.config["http.proxy_pass"]
       end
     end
     if proxy
index 42563d2305a2a3d131b57d1e8148e9de49e7bd90..f83fafb2902d5c7e36fd78c54f502cb1a4d24365 100644 (file)
@@ -106,8 +106,7 @@ module Irc
             options[item] = value
           else
             if @defaults.has_key?(item)
-              debug "item #{item} doesn't pass reqs but has a default of #{@defaults[item]}"
-              options[item] = @defaults[item].clone
+              options[item] = @defaults[item]
               # push the test-failed component back on the stack
               components.unshift value
             else
index 8d9dcfc9f9736ca5ba25243b4eb1997b9571f5ad..d4e5be9fa614bbef4ee35f3b6542897dc701aa7d 100644 (file)
@@ -133,7 +133,7 @@ module Irc
 
     # default usage method provided as a utility for simple plugins. The
     # MessageMapper uses 'usage' as its default fallback method.
-    def usage(m, params)
+    def usage(m, params = {})
       m.reply "incorrect usage, ask for help using '#{@bot.nick}: help #{m.plugin}'"
     end
 
@@ -175,12 +175,14 @@ module Irc
       dirs.each {|dir|
         if(FileTest.directory?(dir))
           d = Dir.new(dir)
-          d.each {|file|
+          d.sort.each {|file|
             next if(file =~ /^\./)
             next unless(file =~ /\.rb$/)
             @tmpfilename = "#{dir}/#{file}"
 
             # create a new, anonymous module to "house" the plugin
+            # the idea here is to prevent namespace pollution. perhaps there
+            # is another way?
             plugin_module = Module.new
             
             begin
@@ -198,28 +200,12 @@ module Irc
 
     # call the save method for each active plugin
     def save
-      @@plugins.values.uniq.each {|p|
-        next unless(p.respond_to?("save"))
-        begin
-          p.save
-        rescue StandardError, NameError, SyntaxError => err
-          puts "plugin #{p.name} save() failed: " + err
-          puts err.backtrace.join("\n")
-        end
-      }
+      delegate 'save'
     end
 
     # call the cleanup method for each active plugin
     def cleanup
-      @@plugins.values.uniq.each {|p|
-        next unless(p.respond_to?("cleanup"))
-        begin
-          p.cleanup
-        rescue StandardError, NameError, SyntaxError => err
-          puts "plugin #{p.name} cleanup() failed: " + err
-          puts err.backtrace.join("\n")
-        end
-      }
+      delegate 'cleanup'
     end
 
     # drop all plugins and rescan plugins on disk
@@ -265,11 +251,11 @@ module Irc
     
     # see if each plugin handles +method+, and if so, call it, passing
     # +message+ as a parameter
-    def delegate(method, message)
+    def delegate(method, *args)
       @@plugins.values.uniq.each {|p|
         if(p.respond_to? method)
           begin
-            p.send method, message
+            p.send method, *args
           rescue StandardError, NameError, SyntaxError => err
             puts "plugin #{p.name} #{method}() failed: " + err
             puts err.backtrace.join("\n")
index b22a417d13d3e1dd79868df8372528bb4f0330d4..4c474ae4f246291c596c92533d18597a948e3445 100644 (file)
@@ -5,124 +5,6 @@ module Irc
 
   # miscellaneous useful functions
   module Utils
-    # read a time in string format, turn it into "seconds from now".
-    # example formats handled are "5 minutes", "2 days", "five hours",
-    # "11:30", "15:45:11", "one day", etc.
-    #
-    # Throws:: RunTimeError "invalid time string" on parse failure
-    def Utils.timestr_offset(timestr)
-      case timestr
-        when (/^(\S+)\s+(\S+)$/)
-          mult = $1
-          unit = $2
-          if(mult =~ /^([\d.]+)$/)
-            num = $1.to_f
-            raise "invalid time string" unless num
-          else
-            case mult
-              when(/^(one|an|a)$/)
-                num = 1
-              when(/^two$/)
-                num = 2
-              when(/^three$/)
-                num = 3
-              when(/^four$/)
-                num = 4
-              when(/^five$/)
-                num = 5
-              when(/^six$/)
-                num = 6
-              when(/^seven$/)
-                num = 7
-              when(/^eight$/)
-                num = 8
-              when(/^nine$/)
-                num = 9
-              when(/^ten$/)
-                num = 10
-              when(/^fifteen$/)
-                num = 15
-              when(/^twenty$/)
-                num = 20
-              when(/^thirty$/)
-                num = 30
-              when(/^sixty$/)
-                num = 60
-              else
-                raise "invalid time string"
-            end
-          end
-          case unit
-            when (/^(s|sec(ond)?s?)$/)
-              return num
-            when (/^(m|min(ute)?s?)$/)
-              return num * 60
-            when (/^(h|h(ou)?rs?)$/)
-              return num * 60 * 60
-            when (/^(d|days?)$/)
-              return num * 60 * 60 * 24
-            else
-              raise "invalid time string"
-          end
-        when (/^(\d+):(\d+):(\d+)$/)
-          hour = $1.to_i
-          min = $2.to_i
-          sec = $3.to_i
-          now = Time.now
-          later = Time.mktime(now.year, now.month, now.day, hour, min, sec)
-          return later - now
-        when (/^(\d+):(\d+)$/)
-          hour = $1.to_i
-          min = $2.to_i
-          now = Time.now
-          later = Time.mktime(now.year, now.month, now.day, hour, min, now.sec)
-          return later - now
-        when (/^(\d+):(\d+)(am|pm)$/)
-          hour = $1.to_i
-          min = $2.to_i
-          ampm = $3
-          if ampm == "pm"
-            hour += 12
-          end
-          now = Time.now
-          later = Time.mktime(now.year, now.month, now.day, hour, min, now.sec)
-          return later - now
-        when (/^(\S+)$/)
-          num = 1
-          unit = $1
-          case unit
-            when (/^(s|sec(ond)?s?)$/)
-              return num
-            when (/^(m|min(ute)?s?)$/)
-              return num * 60
-            when (/^(h|h(ou)?rs?)$/)
-              return num * 60 * 60
-            when (/^(d|days?)$/)
-              return num * 60 * 60 * 24
-            else
-              raise "invalid time string"
-          end
-        else
-          raise "invalid time string"
-      end
-    end
-
-    # turn a number of seconds into a human readable string, e.g
-    # 2 days, 3 hours, 18 minutes, 10 seconds
-    def Utils.secs_to_string(secs)
-      ret = ""
-      days = (secs / (60 * 60 * 24)).to_i
-      secs = secs % (60 * 60 * 24)
-      hours = (secs / (60 * 60)).to_i
-      secs = (secs % (60 * 60))
-      mins = (secs / 60).to_i
-      secs = (secs % 60).to_i
-      ret += "#{days} days, " if days > 0
-      ret += "#{hours} hours, " if hours > 0 || days > 0
-      ret += "#{mins} minutes and " if mins > 0 || hours > 0 || days > 0
-      ret += "#{secs} seconds"
-      return ret
-    end
 
     def Utils.safe_exec(command, *args)
       IO.popen("-") {|p|
@@ -179,600 +61,5 @@ module Irc
         return nil
       end
     end
-
-    # This is nasty-ass. I hate writing parsers.
-    class Metar
-      attr_reader :decoded
-      attr_reader :input
-      attr_reader :date
-      attr_reader :nodata
-      def initialize(string)
-        str = nil
-        @nodata = false
-        string.each_line {|l|
-          if str == nil
-            # grab first line (date)
-            @date = l.chomp.strip
-            str = ""
-          else
-            if(str == "")
-              str = l.chomp.strip
-            else
-              str += " " + l.chomp.strip
-            end
-          end
-        }
-        if @date && @date =~ /^(\d+)\/(\d+)\/(\d+) (\d+):(\d+)$/
-          # 2002/02/26 05:00
-          @date = Time.gm($1, $2, $3, $4, $5, 0)
-        else
-          @date = Time.now
-        end
-        @input = str.chomp
-        @cloud_layers = 0
-        @cloud_coverage = {
-          'SKC' => '0',
-          'CLR' => '0',
-          'VV'  => '8/8',
-          'FEW' => '1/8 - 2/8',
-          'SCT' => '3/8 - 4/8',
-          'BKN' => '5/8 - 7/8',
-          'OVC' => '8/8'
-        }
-        @wind_dir_texts = [
-          'North',
-          'North/Northeast',
-          'Northeast',
-          'East/Northeast',
-          'East',
-          'East/Southeast',
-          'Southeast',
-          'South/Southeast',
-          'South',
-          'South/Southwest',
-          'Southwest',
-          'West/Southwest',
-          'West',
-          'West/Northwest',
-          'Northwest',
-          'North/Northwest',
-          'North'
-        ]
-        @wind_dir_texts_short = [
-          'N',
-          'N/NE',
-          'NE',
-          'E/NE',
-          'E',
-          'E/SE',
-          'SE',
-          'S/SE',
-          'S',
-          'S/SW',
-          'SW',
-          'W/SW',
-          'W',
-          'W/NW',
-          'NW',
-          'N/NW',
-          'N'
-        ]
-        @weather_array = {
-          'MI' => 'Mild ',
-          'PR' => 'Partial ',
-          'BC' => 'Patches ',
-          'DR' => 'Low Drifting ',
-          'BL' => 'Blowing ',
-          'SH' => 'Shower(s) ',
-          'TS' => 'Thunderstorm ',
-          'FZ' => 'Freezing',
-          'DZ' => 'Drizzle ',
-          'RA' => 'Rain ',
-          'SN' => 'Snow ',
-          'SG' => 'Snow Grains ',
-          'IC' => 'Ice Crystals ',
-          'PE' => 'Ice Pellets ',
-          'GR' => 'Hail ',
-          'GS' => 'Small Hail and/or Snow Pellets ',
-          'UP' => 'Unknown ',
-          'BR' => 'Mist ',
-          'FG' => 'Fog ',
-          'FU' => 'Smoke ',
-          'VA' => 'Volcanic Ash ',
-          'DU' => 'Widespread Dust ',
-          'SA' => 'Sand ',
-          'HZ' => 'Haze ',
-          'PY' => 'Spray',
-          'PO' => 'Well-Developed Dust/Sand Whirls ',
-          'SQ' => 'Squalls ',
-          'FC' => 'Funnel Cloud Tornado Waterspout ',
-          'SS' => 'Sandstorm/Duststorm '
-        }
-        @cloud_condition_array = {
-          'SKC' => 'clear',
-          'CLR' => 'clear',
-          'VV'  => 'vertical visibility',
-          'FEW' => 'a few',
-          'SCT' => 'scattered',
-          'BKN' => 'broken',
-          'OVC' => 'overcast'
-        }
-        @strings = {
-          'mm_inches'             => '%s mm (%s inches)',
-          'precip_a_trace'        => 'a trace',
-          'precip_there_was'      => 'There was %s of precipitation ',
-          'sky_str_format1'       => 'There were %s at a height of %s meters (%s feet)',
-          'sky_str_clear'         => 'The sky was clear',
-          'sky_str_format2'       => ', %s at a height of %s meter (%s feet) and %s at a height of %s meters (%s feet)',
-          'sky_str_format3'       => ' and %s at a height of %s meters (%s feet)',
-          'clouds'                => ' clouds',
-          'clouds_cb'             => ' cumulonimbus clouds',
-          'clouds_tcu'            => ' towering cumulus clouds',
-          'visibility_format'     => 'The visibility was %s kilometers (%s miles).',
-          'wind_str_format1'      => 'blowing at a speed of %s meters per second (%s miles per hour)',
-          'wind_str_format2'      => ', with gusts to %s meters per second (%s miles per hour),',
-          'wind_str_format3'      => ' from the %s',
-          'wind_str_calm'         => 'calm',
-          'precip_last_hour'      => 'in the last hour. ',
-          'precip_last_6_hours'   => 'in the last 3 to 6 hours. ',
-          'precip_last_24_hours'  => 'in the last 24 hours. ',
-          'precip_snow'           => 'There is %s mm (%s inches) of snow on the ground. ',
-          'temp_min_max_6_hours'  => 'The maximum and minimum temperatures over the last 6 hours were %s and %s degrees Celsius (%s and %s degrees Fahrenheit).',
-          'temp_max_6_hours'      => 'The maximum temperature over the last 6 hours was %s degrees Celsius (%s degrees Fahrenheit). ',
-          'temp_min_6_hours'      => 'The minimum temperature over the last 6 hours was %s degrees Celsius (%s degrees Fahrenheit). ',
-          'temp_min_max_24_hours' => 'The maximum and minimum temperatures over the last 24 hours were %s and %s degrees Celsius (%s and %s degrees Fahrenheit). ',
-          'light'                 => 'Light ',
-          'moderate'              => 'Moderate ',
-          'heavy'                 => 'Heavy ',
-          'mild'                  => 'Mild ',
-          'nearby'                => 'Nearby ',
-          'current_weather'       => 'Current weather is %s. ',
-          'pretty_print_metar'    => '%s on %s, the wind was %s at %s. The temperature was %s degrees Celsius (%s degrees Fahrenheit), and the pressure was %s hPa (%s inHg). The relative humidity was %s%%. %s %s %s %s %s'
-        }
-
-        parse
-      end
-
-      def store_speed(value, windunit, meterspersec, knots, milesperhour)
-        # Helper function to convert and store speed based on unit.
-        # &$meterspersec, &$knots and &$milesperhour are passed on
-        # reference
-        if (windunit == 'KT')
-          # The windspeed measured in knots:
-          @decoded[knots] = sprintf("%.2f", value)
-          # The windspeed measured in meters per second, rounded to one decimal place:
-          @decoded[meterspersec] = sprintf("%.2f", value.to_f * 0.51444)
-          # The windspeed measured in miles per hour, rounded to one decimal place: */
-          @decoded[milesperhour] = sprintf("%.2f", value.to_f * 1.1507695060844667)
-        elsif (windunit == 'MPS')
-          # The windspeed measured in meters per second:
-          @decoded[meterspersec] = sprintf("%.2f", value)
-          # The windspeed measured in knots, rounded to one decimal place:
-          @decoded[knots] = sprintf("%.2f", value.to_f / 0.51444)
-          #The windspeed measured in miles per hour, rounded to one decimal place:
-          @decoded[milesperhour] = sprintf("%.1f", value.to_f / 0.51444 * 1.1507695060844667)
-        elsif (windunit == 'KMH')
-          # The windspeed measured in kilometers per hour:
-          @decoded[meterspersec] = sprintf("%.1f", value.to_f * 1000 / 3600)
-          @decoded[knots] = sprintf("%.1f", value.to_f * 1000 / 3600 / 0.51444)
-          # The windspeed measured in miles per hour, rounded to one decimal place:
-          @decoded[milesperhour] = sprintf("%.1f", knots.to_f * 1.1507695060844667)
-        end
-      end
-      
-      def parse
-        @decoded = Hash.new
-        puts @input
-        @input.split(" ").each {|part|
-          if (part == 'METAR')
-            # Type of Report: METAR
-            @decoded['type'] = 'METAR'
-          elsif (part == 'SPECI')
-            # Type of Report: SPECI
-            @decoded['type'] = 'SPECI'
-          elsif (part == 'AUTO')
-            # Report Modifier: AUTO
-            @decoded['report_mod'] = 'AUTO'
-          elsif (part == 'NIL')
-            @nodata = true
-          elsif (part =~ /^\S{4}$/ && ! (@decoded.has_key?('station')))
-            # Station Identifier
-            @decoded['station'] = part
-          elsif (part =~ /([0-9]{2})([0-9]{2})([0-9]{2})Z/)
-            # ignore this bit, it's useless without month/year. some of these
-            # things are hideously out of date.
-            # now = Time.new
-            # time = Time.gm(now.year, now.month, $1, $2, $3, 0)
-            # Date and Time of Report
-            # @decoded['time'] = time
-          elsif (part == 'COR')
-            # Report Modifier: COR
-            @decoded['report_mod'] = 'COR'
-          elsif (part =~ /([0-9]{3}|VRB)([0-9]{2,3}).*(KT|MPS|KMH)/)
-            # Wind Group
-            windunit = $3
-            # now do ereg to get the actual values
-            part =~ /([0-9]{3}|VRB)([0-9]{2,3})((G[0-9]{2,3})?#{windunit})/
-            if ($1 == 'VRB')
-              @decoded['wind_deg'] = 'variable directions'
-              @decoded['wind_dir_text'] = 'variable directions'
-              @decoded['wind_dir_text_short'] = 'VAR'
-            else
-              @decoded['wind_deg'] = $1
-              @decoded['wind_dir_text'] = @wind_dir_texts[($1.to_i/22.5).round]
-              @decoded['wind_dir_text_short'] = @wind_dir_texts_short[($1.to_i/22.5).round]
-            end
-            store_speed($2, windunit,
-                        'wind_meters_per_second',
-                        'wind_knots',
-                        'wind_miles_per_hour')
-
-            if ($4 != nil)
-              # We have a report with information about the gust.
-              # First we have the gust measured in knots
-                         if ($4 =~ /G([0-9]{2,3})/)
-              store_speed($1,windunit,
-                          'wind_gust_meters_per_second',
-                          'wind_gust_knots',
-                          'wind_gust_miles_per_hour')
-                               end
-            end
-          elsif (part =~ /([0-9]{3})V([0-9]{3})/)
-            #  Variable wind-direction
-            @decoded['wind_var_beg'] = $1
-            @decoded['wind_var_end'] = $2
-          elsif (part == "9999")
-            # A strange value. When you look at other pages you see it
-            # interpreted like this (where I use > to signify 'Greater
-            # than'):
-            @decoded['visibility_miles'] = '>7';
-            @decoded['visibility_km']    = '>11.3';
-          elsif (part =~ /^([0-9]{4})$/)
-            # Visibility in meters (4 digits only)
-            # The visibility measured in kilometers, rounded to one decimal place.
-            @decoded['visibility_km'] = sprintf("%.1f", $1.to_i / 1000)
-            # The visibility measured in miles, rounded to one decimal place.
-            @decoded['visibility_miles'] = sprintf("%.1f", $1.to_i / 1000 / 1.609344)
-          elsif (part =~ /^[0-9]$/)
-            # Temp Visibility Group, single digit followed by space
-            @decoded['temp_visibility_miles'] = part
-          elsif (@decoded['temp_visibility_miles'] && (@decoded['temp_visibility_miles']+' '+part) =~ /^M?(([0-9]?)[ ]?([0-9])(\/?)([0-9]*))SM$/)
-            # Visibility Group
-            if ($4 == '/')
-              vis_miles = $2.to_i + $3.to_i/$5.to_i
-            else
-              vis_miles = $1.to_i;
-            end
-            if (@decoded['temp_visibility_miles'][0] == 'M')
-              # The visibility measured in miles, prefixed with < to indicate 'Less than'
-              @decoded['visibility_miles'] = '<' + sprintf("%.1f", vis_miles)
-              # The visibility measured in kilometers. The value is rounded
-              # to one decimal place, prefixed with < to indicate 'Less than' */
-              @decoded['visibility_km']    = '<' . sprintf("%.1f", vis_miles * 1.609344)
-            else
-              # The visibility measured in mile.s */
-              @decoded['visibility_miles'] = sprintf("%.1f", vis_miles)
-              # The visibility measured in kilometers, rounded to one decimal place.
-              @decoded['visibility_km']    = sprintf("%.1f", vis_miles * 1.609344)
-            end
-          elsif (part =~ /^(-|\+|VC|MI)?(TS|SH|FZ|BL|DR|BC|PR|RA|DZ|SN|SG|GR|GS|PE|IC|UP|BR|FG|FU|VA|DU|SA|HZ|PY|PO|SQ|FC|SS|DS)+$/)
-            # Current weather-group
-            @decoded['weather'] = '' unless @decoded.has_key?('weather')
-            if (part[0].chr == '-')
-              # A light phenomenon
-              @decoded['weather'] += @strings['light']
-              part = part[1,part.length]
-            elsif (part[0].chr == '+')
-              # A heavy phenomenon
-              @decoded['weather'] += @strings['heavy']
-              part = part[1,part.length]
-            elsif (part[0,2] == 'VC')
-              # Proximity Qualifier
-              @decoded['weather'] += @strings['nearby']
-              part = part[2,part.length]
-            elsif (part[0,2] == 'MI')
-              @decoded['weather'] += @strings['mild']
-              part = part[2,part.length]
-            else
-              # no intensity code => moderate phenomenon
-              @decoded['weather'] += @strings['moderate']
-            end
-            
-            while (part && bite = part[0,2]) do
-              # Now we take the first two letters and determine what they
-              # mean. We append this to the variable so that we gradually
-              # build up a phrase.
-
-              @decoded['weather'] += @weather_array[bite]
-              # Here we chop off the two first letters, so that we can take
-              # a new bite at top of the while-loop.
-              part = part[2,-1]
-            end
-          elsif (part =~ /(SKC|CLR)/)
-            # Cloud-layer-group.
-            # There can be up to three of these groups, so we store them as
-            # cloud_layer1, cloud_layer2 and cloud_layer3.
-            
-            @cloud_layers += 1;
-            # Again we have to translate the code-characters to a
-            # meaningful string.
-            @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_condition']  = @cloud_condition_array[$1]
-            @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_coverage'] = @cloud_coverage[$1]
-          elsif (part =~ /^(VV|FEW|SCT|BKN|OVC)([0-9]{3})(CB|TCU)?$/)
-            # We have found (another) a cloud-layer-group. There can be up
-            # to three of these groups, so we store them as cloud_layer1,
-            # cloud_layer2 and cloud_layer3.
-            @cloud_layers += 1;
-            # Again we have to translate the code-characters to a meaningful string.
-            if ($3 == 'CB')
-              # cumulonimbus (CB) clouds were observed. */
-              @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_condition'] =
-                          @cloud_condition_array[$1] + @strings['clouds_cb']
-            elsif ($3 == 'TCU')
-              # towering cumulus (TCU) clouds were observed.
-              @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_condition'] =
-                          @cloud_condition_array[$1] + @strings['clouds_tcu']
-            else
-              @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_condition'] =
-                          @cloud_condition_array[$1] + @strings['clouds']
-            end
-            @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_coverage'] = @cloud_coverage[$1]
-            @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_altitude_ft'] = $2.to_i * 100
-            @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_altitude_m']  = ($2.to_f * 30.48).round
-          elsif (part =~ /^T([0-9]{4})$/)
-            store_temp($1,'temp_c','temp_f')
-          elsif (part =~ /^T?(M?[0-9]{2})\/(M?[0-9\/]{1,2})?$/)
-            # Temperature/Dew Point Group
-            # The temperature and dew-point measured in Celsius.
-            @decoded['temp_c'] = sprintf("%d", $1.tr('M', '-'))
-            if $2 == "//" || !$2
-              @decoded['dew_c'] = 0
-            else
-              @decoded['dew_c'] = sprintf("%.1f", $2.tr('M', '-'))
-            end
-            # The temperature and dew-point measured in Fahrenheit, rounded to
-            # the nearest degree.
-            @decoded['temp_f'] = ((@decoded['temp_c'].to_f * 9 / 5) + 32).round
-            @decoded['dew_f']  = ((@decoded['dew_c'].to_f * 9 / 5) + 32).round
-          elsif(part =~ /A([0-9]{4})/)
-            # Altimeter
-            # The pressure measured in inHg
-            @decoded['altimeter_inhg'] = sprintf("%.2f", $1.to_i/100)
-            # The pressure measured in mmHg, hPa and atm
-            @decoded['altimeter_mmhg'] = sprintf("%.1f", $1.to_f * 0.254)
-            @decoded['altimeter_hpa']  = sprintf("%d", ($1.to_f * 0.33863881578947).to_i)
-            @decoded['altimeter_atm']  = sprintf("%.3f", $1.to_f * 3.3421052631579e-4)
-          elsif(part =~ /Q([0-9]{4})/)
-            # Altimeter
-            # This is strange, the specification doesnt say anything about
-            # the Qxxxx-form, but it's in the METARs.
-            # The pressure measured in hPa
-            @decoded['altimeter_hpa']  = sprintf("%d", $1.to_i)
-            # The pressure measured in mmHg, inHg and atm
-            @decoded['altimeter_mmhg'] = sprintf("%.1f", $1.to_f * 0.7500616827)
-            @decoded['altimeter_inhg'] = sprintf("%.2f", $1.to_f * 0.0295299875)
-            @decoded['altimeter_atm']  = sprintf("%.3f", $1.to_f * 9.869232667e-4)
-          elsif (part =~ /^T([0-9]{4})([0-9]{4})/)
-            # Temperature/Dew Point Group, coded to tenth of degree.
-            # The temperature and dew-point measured in Celsius.
-            store_temp($1,'temp_c','temp_f')
-            store_temp($2,'dew_c','dew_f')
-          elsif (part =~ /^1([0-9]{4}$)/)
-            # 6 hour maximum temperature Celsius, coded to tenth of degree
-            store_temp($1,'temp_max6h_c','temp_max6h_f')
-          elsif (part =~ /^2([0-9]{4}$)/)
-            # 6 hour minimum temperature Celsius, coded to tenth of degree
-            store_temp($1,'temp_min6h_c','temp_min6h_f')
-          elsif (part =~ /^4([0-9]{4})([0-9]{4})$/)
-            # 24 hour maximum and minimum temperature Celsius, coded to
-            # tenth of degree
-            store_temp($1,'temp_max24h_c','temp_max24h_f')
-            store_temp($2,'temp_min24h_c','temp_min24h_f')
-          elsif (part =~ /^P([0-9]{4})/)
-            # Precipitation during last hour in hundredths of an inch
-            # (store as inches)
-            @decoded['precip_in'] = sprintf("%.2f", $1.to_f/100)
-            @decoded['precip_mm'] = sprintf("%.2f", $1.to_f * 0.254)
-          elsif (part =~ /^6([0-9]{4})/)
-            # Precipitation during last 3 or 6 hours in hundredths of an
-            # inch  (store as inches)
-            @decoded['precip_6h_in'] = sprintf("%.2f", $1.to_f/100)
-            @decoded['precip_6h_mm'] = sprintf("%.2f", $1.to_f * 0.254)
-          elsif (part =~ /^7([0-9]{4})/)
-            # Precipitation during last 24 hours in hundredths of an inch
-            # (store as inches)
-            @decoded['precip_24h_in'] = sprintf("%.2f", $1.to_f/100)
-            @decoded['precip_24h_mm'] = sprintf("%.2f", $1.to_f * 0.254)
-          elsif(part =~ /^4\/([0-9]{3})/)
-            # Snow depth in inches
-            @decoded['snow_in'] = sprintf("%.2f", $1);
-            @decoded['snow_mm'] = sprintf("%.2f", $1.to_f * 25.4)
-          else
-            # If we couldn't match the group, we assume that it was a
-            # remark.
-            @decoded['remarks'] = '' unless @decoded.has_key?("remarks")
-            @decoded['remarks'] += ' ' + part;
-          end
-        }
-        
-        # Relative humidity
-        # p @decoded['dew_c'] # 11.0
-        # p @decoded['temp_c'] # 21.0
-        # => 56.1
-        @decoded['rel_humidity'] = sprintf("%.1f",100 * 
-          (6.11 * (10.0**(7.5 * @decoded['dew_c'].to_f / (237.7 + @decoded['dew_c'].to_f)))) / (6.11 * (10.0 ** (7.5 * @decoded['temp_c'].to_f / (237.7 + @decoded['temp_c'].to_f))))) if @decoded.has_key?('dew_c')
-      end
-
-      def store_temp(temp,temp_cname,temp_fname)
-        # Given a numerical temperature temp in Celsius, coded to tenth of
-        # degree, store in @decoded[temp_cname], convert to Fahrenheit
-        # and store in @decoded[temp_fname]
-        # Note: temp is converted to negative if temp > 100.0 (See
-        # Federal Meteorological Handbook for groups T, 1, 2 and 4)
-
-        # Temperature measured in Celsius, coded to tenth of degree
-        temp = temp.to_f/10
-        if (temp >100.0) 
-           # first digit = 1 means minus temperature
-           temp = -(temp - 100.0)
-        end
-        @decoded[temp_cname] = sprintf("%.1f", temp)
-        # The temperature in Fahrenheit.
-        @decoded[temp_fname] = sprintf("%.1f", (temp * 9 / 5) + 32)        
-      end
-
-       def pretty_print_precip(precip_mm, precip_in)
-         # Returns amount if $precip_mm > 0, otherwise "trace" (see Federal
-         # Meteorological Handbook No. 1 for code groups P, 6 and 7) used in
-         # several places, so standardized in one function.
-         if (precip_mm.to_i > 0)
-           amount = sprintf(@strings['mm_inches'], precip_mm, precip_in)
-         else
-           amount = @strings['a_trace']
-         end
-         return sprintf(@strings['precip_there_was'], amount)
-      end
-
-      def pretty_print
-       if @nodata
-         return "The weather stored for #{@decoded['station']} consists of the string 'NIL' :("
-       end
-
-       ["temp_c", "altimeter_hpa"].each {|key|
-         if !@decoded.has_key?(key)
-           return "The weather stored for #{@decoded['station']} could not be parsed (#{@input})"
-         end
-       }
-       
-       mins_old = ((Time.now - @date.to_i).to_f/60).round
-       if (mins_old <= 60)
-         weather_age = mins_old.to_s + " minutes ago,"
-       elsif (mins_old <= 60 * 25)
-         weather_age = (mins_old / 60).to_s + " hours, "
-         weather_age += (mins_old % 60).to_s + " minutes ago,"
-       else
-         # return "The weather stored for #{@decoded['station']} is hideously out of date :( (Last update #{@date})"
-         weather_age = "The weather stored for #{@decoded['station']} is hideously out of date :( here it is anyway:"
-       end
-        
-       if(@decoded.has_key?("cloud_layer1_altitude_ft"))
-         sky_str = sprintf(@strings['sky_str_format1'],
-                           @decoded["cloud_layer1_condition"],
-                           @decoded["cloud_layer1_altitude_m"],
-                           @decoded["cloud_layer1_altitude_ft"])
-       else
-         sky_str = @strings['sky_str_clear']
-       end
-
-       if(@decoded.has_key?("cloud_layer2_altitude_ft"))
-         if(@decoded.has_key?("cloud_layer3_altitude_ft"))
-           sky_str += sprintf(@strings['sky_str_format2'],
-                              @decoded["cloud_layer2_condition"],
-                              @decoded["cloud_layer2_altitude_m"],
-                              @decoded["cloud_layer2_altitude_ft"],
-                              @decoded["cloud_layer3_condition"],
-                              @decoded["cloud_layer3_altitude_m"],
-                              @decoded["cloud_layer3_altitude_ft"])
-         else
-           sky_str += sprintf(@strings['sky_str_format3'],
-                              @decoded["cloud_layer2_condition"],
-                              @decoded["cloud_layer2_altitude_m"],
-                              @decoded["cloud_layer2_altitude_ft"])
-         end
-       end
-       sky_str += "."
-
-       if(@decoded.has_key?("visibility_miles"))
-         visibility = sprintf(@strings['visibility_format'],
-                              @decoded["visibility_km"],
-                              @decoded["visibility_miles"])
-       else
-         visibility = ""
-       end
-
-       if (@decoded.has_key?("wind_meters_per_second") && @decoded["wind_meters_per_second"].to_i > 0)
-         wind_str = sprintf(@strings['wind_str_format1'],
-                            @decoded["wind_meters_per_second"],
-                            @decoded["wind_miles_per_hour"])
-         if (@decoded.has_key?("wind_gust_meters_per_second") && @decoded["wind_gust_meters_per_second"].to_i > 0)
-           wind_str += sprintf(@strings['wind_str_format2'],
-                               @decoded["wind_gust_meters_per_second"],
-                               @decoded["wind_gust_miles_per_hour"])
-         end
-         wind_str += sprintf(@strings['wind_str_format3'],
-                             @decoded["wind_dir_text"])
-       else
-         wind_str = @strings['wind_str_calm']
-       end
-
-       prec_str = ""
-       if (@decoded.has_key?("precip_in"))
-         prec_str += pretty_print_precip(@decoded["precip_mm"], @decoded["precip_in"]) + @strings['precip_last_hour']
-       end
-       if (@decoded.has_key?("precip_6h_in"))
-         prec_str += pretty_print_precip(@decoded["precip_6h_mm"], @decoded["precip_6h_in"]) + @strings['precip_last_6_hours']
-       end
-       if (@decoded.has_key?("precip_24h_in"))
-         prec_str += pretty_print_precip(@decoded["precip_24h_mm"], @decoded["precip_24h_in"]) + @strings['precip_last_24_hours']
-       end
-       if (@decoded.has_key?("snow_in"))
-         prec_str += sprintf(@strings['precip_snow'], @decoded["snow_mm"], @decoded["snow_in"])
-       end
-
-       temp_str = ""
-       if (@decoded.has_key?("temp_max6h_c") && @decoded.has_key?("temp_min6h_c"))
-         temp_str += sprintf(@strings['temp_min_max_6_hours'],
-                             @decoded["temp_max6h_c"],
-                             @decoded["temp_min6h_c"],
-                             @decoded["temp_max6h_f"],
-                             @decoded["temp_min6h_f"])
-       else
-         if (@decoded.has_key?("temp_max6h_c"))
-           temp_str += sprintf(@strings['temp_max_6_hours'],
-                               @decoded["temp_max6h_c"],
-                               @decoded["temp_max6h_f"])
-         end
-         if (@decoded.has_key?("temp_min6h_c"))
-           temp_str += sprintf(@strings['temp_max_6_hours'],
-                               @decoded["temp_min6h_c"],
-                               @decoded["temp_min6h_f"])
-         end
-       end
-       if (@decoded.has_key?("temp_max24h_c"))
-         temp_str += sprintf(@strings['temp_min_max_24_hours'],
-                             @decoded["temp_max24h_c"],
-                             @decoded["temp_min24h_c"],
-                             @decoded["temp_max24h_f"],
-                             @decoded["temp_min24h_f"])
-       end
-
-       if (@decoded.has_key?("weather"))
-         weather_str = sprintf(@strings['current_weather'], @decoded["weather"])
-       else
-         weather_str = ''
-       end
-
-       return sprintf(@strings['pretty_print_metar'],
-                      weather_age,
-                      @date,
-                      wind_str, @decoded["station"], @decoded["temp_c"],
-                      @decoded["temp_f"], @decoded["altimeter_hpa"],
-                      @decoded["altimeter_inhg"],
-                      @decoded["rel_humidity"], sky_str,
-                      visibility, weather_str, prec_str, temp_str).strip
-      end
-  
-      def to_s
-        @input
-      end
-    end
-
-    def Utils.get_metar(station)
-      station.upcase!
-      
-      result = Utils.http_get("http://weather.noaa.gov/pub/data/observations/metar/stations/#{station}.TXT")
-      return nil unless result
-      return Metar.new(result)
-    end
   end
 end