]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/commitdiff
Sat Jul 30 22:33:36 BST 2005 Tom Gilbert <tom@linuxbrit.co.uk>
authorTom Gilbert <tom@linuxbrit.co.uk>
Sat, 30 Jul 2005 21:35:57 +0000 (21:35 +0000)
committerTom Gilbert <tom@linuxbrit.co.uk>
Sat, 30 Jul 2005 21:35:57 +0000 (21:35 +0000)
  * Config items are now objects, various types are available.
* The config wizard will now use registered config items if :wizard is set
    to true for those items. It will ask questions in the order they were
registered.
* The config module now works for doing runtime configuration.
* misc refactoring

ChangeLog
data/rbot/plugins/nickserv.rb
data/rbot/plugins/seen.rb
data/rbot/plugins/url.rb
lib/rbot/auth.rb
lib/rbot/config.rb
lib/rbot/httputil.rb
lib/rbot/ircbot.rb
lib/rbot/keywords.rb
lib/rbot/language.rb
lib/rbot/utils.rb

index 68e85fca96f984925f02a2c92eae586596adf7be..af545d9ad6102089f82641bead64acadb52c8b30 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+Sat Jul 30 22:33:36 BST 2005  Tom Gilbert <tom@linuxbrit.co.uk>
+
+  * Config items are now objects, various types are available.
+       * The config wizard will now use registered config items if :wizard is set
+    to true for those items. It will ask questions in the order they were
+               registered.
+       * The config module now works for doing runtime configuration.
+       * misc refactoring
+
 Sat Jul 30 01:19:32 BST 2005  Tom Gilbert <tom@linuxbrit.co.uk>
 
   * config module for configuring the running bot via IRC
index 246f253ca3ba8dfad4a072fbc035729c2a2d9cb1..976bb8f8a765d2e545edb902ba796f41022dc44f 100644 (file)
@@ -73,7 +73,7 @@ class NickServPlugin < Plugin
     return unless(m.kind_of? NoticeMessage)
 
     if (m.sourcenick == "NickServ" && m.message =~ /This nickname is owned by someone else/)
-      puts "nickserv asked us to identify for nick #{@bot.nick}"
+      debug "nickserv asked us to identify for nick #{@bot.nick}"
       if @registry.has_key?(@bot.nick)
         @bot.sendmsg "PRIVMSG", "NickServ", "IDENTIFY " + @registry[@bot.nick]
       end
index 80d52f6595767540124a5214b1a2e90b35501707..a8dc1af7b8afc3bdc298f9b6a77f30111b373c34 100644 (file)
@@ -1,23 +1,6 @@
 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
@@ -80,7 +63,7 @@ class SeenPlugin < Plugin
     if (ago.to_i == 0)
       ret += "just now, "
     else
-      ret += secs_to_string(ago) + " ago, "
+      ret += Utils.secs_to_string(ago) + " ago, "
     end
 
     case saw.type
index 5629e30adfa14f0fe8bcdc194e3cc5159bf01668..7972037adf68d81123bd46bd9e486fe2ff890c14 100644 (file)
@@ -1,7 +1,8 @@
 Url = Struct.new("Url", :channel, :nick, :time, :url)
 
 class UrlPlugin < Plugin
-  BotConfig.register('url.max_urls', :type => :integer, :default => 100,
+  BotConfig.register BotConfigIntegerValue.new('url.max_urls',
+    :default => 100, :validate => Proc.new{|v| v > 0},
     :desc => "Maximum number of urls to store. New urls replace oldest ones.")
   
   def initialize
index 41890f7ee2ccda0932fe3eaf7685d728a578c214..ae7199e169607473e46cedc9d6c2e78a2b770beb 100644 (file)
@@ -19,7 +19,8 @@ module Irc
   # User-level authentication to allow/disallow access to bot commands based
   # on hostmask and userlevel.
   class IrcAuth
-    BotConfig.register('auth.password', :type => BotConfig::Password, :default => "Your password for maxing your auth with the bot (used to associate new hostmasks with your owner-status etc)")
+    BotConfig.register BotConfigStringValue.new('auth.password',
+      :default => "Your password for maxing your auth with the bot (used to associate new hostmasks with your owner-status etc)")
     
     # create a new IrcAuth instance.
     # bot:: associated bot class
index f6ff76911f2556833ca0e736d2e42b88cbc6d7ae..a452292651d74896ce38bec46172c21af984c196 100644 (file)
@@ -3,12 +3,23 @@ module Irc
   require 'yaml'
   require 'rbot/messagemapper'
 
-  class BotConfigItem
+  class BotConfigValue
+    # allow the definition order to be preserved so that sorting by
+    # definition order is possible. The BotConfigWizard does this to allow
+    # the :wizard questions to be in a sensible order.
+    @@order = 0
     attr_reader :type
     attr_reader :desc
     attr_reader :key
-    attr_reader :values
+    attr_reader :wizard
+    attr_reader :requires_restart
+    attr_reader :order
     def initialize(key, params)
+      unless key =~ /^.+\..+$/
+        raise ArgumentError,"key must be of the form 'module.name'"
+      end
+      @order = @@order
+      @@order += 1
       @key = key
       if params.has_key? :default
         @default = params[:default]
@@ -17,40 +28,130 @@ module Irc
       end
       @desc = params[:desc]
       @type = params[:type] || String
-      @values = params[:values]
       @on_change = params[:on_change]
+      @validate = params[:validate]
+      @wizard = params[:wizard]
+      @requires_restart = params[:requires_restart]
     end
     def default
-      if @default.class == Proc
+      if @default.instance_of?(Proc)
         @default.call
       else
         @default
       end
     end
-    def on_change(newvalue)
-      return unless @on_change
-      @on_change.call(newvalue)
+    def get
+      return BotConfig.config[@key] if BotConfig.config.has_key?(@key)
+      return @default
+    end
+    alias :value :get
+    def set(value, on_change = true)
+      BotConfig.config[@key] = value
+      @on_change.call(BotConfig.bot, value) if on_change && @on_change
+    end
+    def unset
+      BotConfig.config.delete(@key)
+    end
+
+    # set string will raise ArgumentErrors on failed parse/validate
+    def set_string(string, on_change = true)
+      value = parse string
+      if validate value
+        set value, on_change
+      else
+        raise ArgumentError, "invalid value: #{string}"
+      end
+    end
+    
+    # override this. the default will work for strings only
+    def parse(string)
+      string
+    end
+
+    def to_s
+      get.to_s
+    end
+
+    private
+    def validate(value)
+      return true unless @validate
+      if @validate.instance_of?(Proc)
+        return @validate.call(value)
+      elsif @validate.instance_of?(Regexp)
+        raise ArgumentError, "validation via Regexp only supported for strings!" unless value.instance_of? String
+        return @validate.match(value)
+      else
+        raise ArgumentError, "validation type #{@validate.class} not supported"
+      end
+    end
+  end
+
+  class BotConfigStringValue < BotConfigValue
+  end
+  class BotConfigBooleanValue < BotConfigValue
+    def parse(string)
+      return true if string == "true"
+      return false if string == "false"
+      raise ArgumentError, "#{string} does not match either 'true' or 'false'"
+    end
+  end
+  class BotConfigIntegerValue < BotConfigValue
+    def parse(string)
+      raise ArgumentError, "not an integer: #{string}" unless string =~ /^-?\d+$/
+      string.to_i
+    end
+  end
+  class BotConfigFloatValue < BotConfigValue
+    def parse(string)
+      raise ArgumentError, "not a float #{string}" unless string =~ /^-?[\d.]+$/
+      string.to_f
+    end
+  end
+  class BotConfigArrayValue < BotConfigValue
+    def parse(string)
+      string.split(/,\s+/)
+    end
+    def to_s
+      get.join(", ")
+    end
+  end
+  class BotConfigEnumValue < BotConfigValue
+    def initialize(key, params)
+      super
+      @values = params[:values]
+    end
+    def parse(string)
+      unless @values.include?(string)
+        raise ArgumentError, "invalid value #{string}, allowed values are: " + @values.join(", ")
+      end
+      string
+    end
+    def desc
+      "#{@desc} [valid values are: " + @values.join(", ") + "]"
     end
   end
 
   # container for bot configuration
   class BotConfig
-    class Enum
+    # Array of registered BotConfigValues for defaults, types and help
+    @@items = Hash.new
+    def BotConfig.items
+      @@items
     end
-    class Password
+    # Hash containing key => value pairs for lookup and serialisation
+    @@config = Hash.new(false)
+    def BotConfig.config
+      @@config
     end
-    class Boolean
+    def BotConfig.bot
+      @@bot
     end
     
-    attr_reader :items
-    @@items = Hash.new
-    
-    def BotConfig.register(key, params)
-      unless params.nil? || params.instance_of?(Hash)
-        raise ArgumentError,"params must be a hash"
+    def BotConfig.register(item)
+      unless item.kind_of?(BotConfigValue)
+        raise ArgumentError,"item must be a BotConfigValue"
       end
-      raise ArgumentError,"params must contain a period" unless key =~ /^.+\..+$/
-      @@items[key] = BotConfigItem.new(key, params)
+      @@items[item.key] = item
     end
 
     # currently we store values in a hash but this could be changed in the
@@ -58,14 +159,19 @@ module Irc
     # components that register their config keys and setup defaults are
     # supported via []
     def [](key)
-      return @config[key] if @config.has_key?(key)
-      return @@items[key].default if @@items.has_key?(key)
+      return @@items[key].value if @@items.has_key?(key)
+      # try to still support unregistered lookups
+      return @@config[key] if @@config.has_key?(key)
       return false
     end
+
+    # TODO should I implement this via BotConfigValue or leave it direct?
+    #    def []=(key, value)
+    #    end
     
-    # pass everything through to the hash
+    # pass everything else through to the hash
     def method_missing(method, *args, &block)
-      return @config.send(method, *args, &block)
+      return @@config.send(method, *args, &block)
     end
 
     def handle_list(m, params)
@@ -74,12 +180,12 @@ module Irc
         @@items.each_key do |key|
           mod, name = key.split('.')
           next unless mod == params[:module]
-          modules.push name unless modules.include?(name)
+          modules.push key unless modules.include?(name)
         end
         if modules.empty?
           m.reply "no such module #{params[:module]}"
         else
-          m.reply "module #{params[:module]} contains: " + modules.join(", ")
+          m.reply modules.join(", ")
         end
       else
         @@items.each_key do |key|
@@ -94,13 +200,9 @@ module Irc
       key = params[:key]
       unless @@items.has_key?(key)
         m.reply "no such config key #{key}"
+        return
       end
-      value = self[key]
-      if @@items[key].type == :array
-        value = self[key].join(", ")
-      elsif @@items[key].type == :password && !m.private
-        value = "******"
-      end
+      value = @@items[key].to_s
       m.reply "#{key}: #{value}"
     end
 
@@ -109,6 +211,7 @@ module Irc
       unless @@items.has_key?(key)
         m.reply "no such config key #{key}"
       end
+      puts @@items[key].inspect
       m.reply "#{key}: #{@@items[key].desc}"
     end
 
@@ -117,7 +220,7 @@ module Irc
       unless @@items.has_key?(key)
         m.reply "no such config key #{key}"
       end
-      @config.delete(key)
+      @@items[key].unset
       handle_get(m, params)
     end
 
@@ -127,54 +230,57 @@ module Irc
       unless @@items.has_key?(key)
         m.reply "no such config key #{key}"
       end
-      item = @@items[key]
-      puts "item type is #{item.type}"
-      case item.type
-        when :string
-          @config[key] = value
-        when :password
-          @config[key] = value
-        when :integer
-          @config[key] = value.to_i
-        when :float
-          @config[key] = value.to_f
-        when :array
-          @config[key] = value.split(/,\s*/)
-        when :boolean
-          if value == "true"
-            @config[key] = true
-          else
-            @config[key] = false
-          end
-        when :enum
-          unless item.values.include?(value)
-            m.reply "invalid value #{value}, allowed values are: " + item.values.join(", ")
-            return
-          end
-          @config[key] = value
-        else
-          puts "ACK, unsupported type #{item.type}"
-          exit 2
+      begin
+        @@items[key].set_string(value)
+      rescue ArgumentError => e
+        m.reply "failed to set #{key}: #{e.message}"
+        return
+      end
+      if @@items[key].requires_restart
+        m.reply "this config change will take effect on the next restart"
+      else
+        m.okay
       end
-      item.on_change(@config[key])
-      m.okay
+    end
+
+    def handle_help(m, params)
+      topic = params[:topic]
+      case topic
+      when false
+        m.reply "config module - bot configuration. usage: list, desc, get, set, unset"
+      when "list"
+        m.reply "config list => list configuration modules, config list <module> => list configuration keys for module <module>"
+      when "get"
+        m.reply "config get <key> => get configuration value for key <key>"
+      when "unset"
+        m.reply "reset key <key> to the default"
+      when "set"
+        m.reply "config set <key> <value> => set configuration value for key <key> to <value>"
+      when "desc"
+        m.reply "config desc <key> => describe what key <key> configures"
+      else
+        m.reply "no help for config #{topic}"
+      end
+    end
+    def usage(m,params)
+      m.reply "incorrect usage, try '#{@bot.nick}: help config'"
     end
 
     # bot:: parent bot class
     # create a new config hash from #{botclass}/conf.rbot
     def initialize(bot)
-      @bot = bot
-      @config = Hash.new(false)
+      @@bot = bot
 
       # respond to config messages, to provide runtime configuration
       # management
       # messages will be:
-      #  get (implied)
+      #  get
       #  set
       #  unset
+      #  desc
       #  and for arrays:
-      #    add
-      #    remove
+      #    add TODO
+      #    remove TODO
       @handler = MessageMapper.new(self)
       @handler.map 'config list :module', :action => 'handle_list',
                    :defaults => {:module => false}
@@ -183,48 +289,26 @@ module Irc
       @handler.map 'config describe :key', :action => 'handle_desc'
       @handler.map 'config set :key *value', :action => 'handle_set'
       @handler.map 'config unset :key', :action => 'handle_unset'
+      @handler.map 'config help :topic', :action => 'handle_help',
+                   :defaults => {:topic => false}
+      @handler.map 'help config :topic', :action => 'handle_help',
+                   :defaults => {:topic => false}
       
-      # TODO
-      # have this class persist key/values in hash using yaml as it kinda
-      # already does.
-      # have other users of the class describe config to it on init, like:
-      # @config.add(:key => 'server.name', :type => 'string',
-      #             :default => 'localhost', :restart => true,
-      #             :help => 'irc server to connect to')
-      # that way the config module doesn't have to know about all the other
-      # classes but can still provide help and defaults.
-      # Classes don't have to add keys, they can just use config as a
-      # persistent hash, but then they won't be presented by the config
-      # module for runtime display/changes.
-      # (:restart, if true, makes the bot reply to changes with "this change
-      # will take effect after the next restart)
-      #  :proc => Proc.new {|newvalue| ...}
-      # (:proc, proc to run on change of setting)
-      #  or maybe, @config.add_key(...) do |newvalue| .... end
-      #  :validate => /regex/
-      # (operates on received string before conversion)
-      # Special handling for arrays so the config module can be used to
-      # add/remove elements as well as changing the whole thing
-      # Allow config options to list possible valid values (if type is enum,
-      # for example). Then things like the language module can list the
-      # available languages for choosing.
-      
-      if(File.exist?("#{@bot.botclass}/conf.yaml"))
-        newconfig = YAML::load_file("#{@bot.botclass}/conf.yaml")
-        @config.update(newconfig)
+      if(File.exist?("#{@@bot.botclass}/conf.yaml"))
+        newconfig = YAML::load_file("#{@@bot.botclass}/conf.yaml")
+        @@config.update newconfig
       else
         # first-run wizard!
-        wiz = BotConfigWizard.new(@bot)
-        newconfig = wiz.run(@config)
-        @config.update(newconfig)
+        BotConfigWizard.new(@@bot).run
+        # save newly created config
+        save
       end
     end
 
     # write current configuration to #{botclass}/conf.rbot
     def save
-      Dir.mkdir("#{@bot.botclass}") if(!File.exist?("#{@bot.botclass}"))
-      File.open("#{@bot.botclass}/conf.yaml", "w") do |file|
-        file.puts @config.to_yaml
+      File.open("#{@@bot.botclass}/conf.yaml", "w") do |file|
+        file.puts @@config.to_yaml
       end
     end
 
@@ -233,77 +317,13 @@ module Irc
     end
   end
 
-  # I don't see a nice way to avoid the first start wizard knowing way too
-  # much about other modules etc, because it runs early and stuff it
-  # configures is used to initialise the other modules...
-  # To minimise this we'll do as little as possible and leave the rest to
-  # online modification
   class BotConfigWizard
-
-    # TODO things to configure..
-    # config directory (botclass) - people don't realise they should set
-    # this. The default... isn't good.
-    # users? - default *!*@* to 10
-    # levels? - need a way to specify a default level, methinks, for
-    # unconfigured items.
-    #
     def initialize(bot)
       @bot = bot
-      @questions = [
-        {
-          :question => "What server should the bot connect to?",
-          :key => "server.name",
-          :type => :string,
-        },
-        {
-          :question => "What port should the bot connect to?",
-          :key => "server.port",
-          :type => :number,
-        },
-        {
-          :question => "Does this IRC server require a password for access? Leave blank if not.",
-          :key => "server.password",
-          :type => :password,
-        },
-        {
-          :question => "Would you like rbot to bind to a specific local host or IP? Leave blank if not.",
-          :key => "server.bindhost",
-          :type => :string,
-        },
-        {
-          :question => "What IRC nickname should the bot attempt to use?",
-          :key => "irc.nick",
-          :type => :string,
-        },
-        {
-          :question => "What local user should the bot appear to be?",
-          :key => "irc.user",
-          :type => :string,
-        },
-        {
-          :question => "What channels should the bot always join at startup? List multiple channels using commas to separate. If a channel requires a password, use a space after the channel name. e.g: '#chan1, #chan2, #secretchan secritpass, #chan3'",
-          :prompt => "Channels",
-          :key => "irc.join_channels",
-          :type => :string,
-        },
-        {
-          :question => "Which language file should the bot use?",
-          :key => "core.language",
-          :type => :enum,
-          :items => Dir.new(Config::DATADIR + "/languages").collect {|f|
-            f =~ /\.lang$/ ? f.gsub(/\.lang$/, "") : nil
-          }.compact
-        },
-        {
-          :question => "Enter your password for maxing your auth with the bot (used to associate new hostmasks with your owner-status etc)",
-          :key => "auth.password",
-          :type => :password,
-        },
-      ]
+      @questions = BotConfig.items.values.find_all {|i| i.wizard }
     end
     
-    def run(defaults)
-      config = defaults.clone
+    def run()
       puts "First time rbot configuration wizard"
       puts "===================================="
       puts "This is the first time you have run rbot with a config directory of:"
@@ -313,39 +333,23 @@ module Irc
       puts "rbot is connected and you are auth'd."
       puts "-----------------------------------"
 
-      @questions.each do |q|
-        puts q[:question]
+      return unless @questions
+      @questions.sort{|a,b| a.order <=> b.order }.each do |q|
+        puts q.desc
         begin
-          key = q[:key]
-          if q[:type] == :enum
-            puts "valid values are: " + q[:items].join(", ")
-          end
-          if (defaults.has_key?(key))
-            print q[:key] + " [#{defaults[key]}]: "
-          else
-            print q[:key] + " []: "
-          end
+          print q.key + " [#{q.to_s}]: "
           response = STDIN.gets
           response.chop!
-          response = defaults[key] if response == "" && defaults.has_key?(key)
-          case q[:type]
-            when :string
-            when :number
-              raise "value '#{response}' is not a number" unless (response.class == Fixnum || response =~ /^\d+$/)
-              response = response.to_i
-            when :password
-            when :enum
-              raise "selected value '#{response}' is not one of the valid values" unless q[:items].include?(response)
+          unless response.empty?
+            q.set_string response, false
           end
-          config[key] = response
-          puts "configured #{key} => #{config[key]}"
+          puts "configured #{q.key} => #{q.to_s}"
           puts "-----------------------------------"
-        rescue RuntimeError => e
-          puts e.message
+        rescue ArgumentError => e
+          puts "failed to set #{q.key}: #{e.message}"
           retry
         end
       end
-      return config
     end
   end
 end
index 56de73492cbcfcf13f6194c46e5ff7eb71c6a771..b49a42b1293fd46d4badf2e1b7e9f2fca24aae22 100644 (file)
@@ -8,15 +8,20 @@ Net::HTTP.version_1_2
 # this class can check the bot proxy configuration to determine if a proxy
 # needs to be used, which includes support for per-url proxy configuration.
 class HttpUtil
-    BotConfig.register('http.proxy', :default => false,
+    BotConfig.register BotConfigBooleanValue.new('http.use_proxy',
+      :default => false, :desc => "should a proxy be used for HTTP requests?")
+    BotConfig.register BotConfigStringValue.new('http.proxy_uri', :default => false,
       :desc => "Proxy server to use for HTTP requests (URI, e.g http://proxy.host:port)")
-    BotConfig.register('http.proxy_user', :default => false,
+    BotConfig.register BotConfigStringValue.new('http.proxy_user',
+      :default => nil,
       :desc => "User for authenticating with the http proxy (if required)")
-    BotConfig.register('http.proxy_pass', :default => false,
+    BotConfig.register BotConfigStringValue.new('http.proxy_pass',
+      :default => nil,
       :desc => "Password for authenticating with the http proxy (if required)")
-    BotConfig.register('http.proxy_include', :type => :array, :default => [],
+    BotConfig.register BotConfigArrayValue.new('http.proxy_include',
+      :default => [],
       :desc => "List of regexps to check against a URI's hostname/ip to see if we should use the proxy to access this URI. All URIs are proxied by default if the proxy is set, so this is only required to re-include URIs that might have been excluded by the exclude list. e.g. exclude /.*\.foo\.com/, include bar\.foo\.com")
-    BotConfig.register('http.proxy_exclude', :type => :array, :default => [],
+    BotConfig.register BotConfigArrayValue.new('http.proxy_exclude',
       :desc => "List of regexps to check against a URI's hostname/ip to see if we should use avoid the proxy to access this URI and access it directly")
 
   def initialize(bot)
@@ -77,31 +82,27 @@ class HttpUtil
   # +http_proxy_include/exclude+ options.
   def get_proxy(uri)
     proxy = nil
-    if (ENV['http_proxy'])
-      proxy = URI.parse ENV['http_proxy']
-    end
-    if (@bot.config["http.proxy"])
-      proxy = URI.parse ENV['http_proxy']
-    end
-
-    if proxy
-      debug "proxy is set to #{proxy.uri}"
-      proxy = nil unless proxy_required(uri)
-    end
-    
     proxy_host = nil
     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.use_proxy"]
+      if (ENV['http_proxy'])
+        proxy = URI.parse ENV['http_proxy']
+      end
+      if (@bot.config["http.proxy_uri"])
+        proxy = URI.parse ENV['http_proxy_uri']
+      end
+      if proxy
+        debug "proxy is set to #{proxy.uri}"
+        if proxy_required(uri)
+          proxy_host = proxy.host
+          proxy_port = proxy.port
+          proxy_user = @bot.config["http.proxy_user"]
+          proxy_pass = @bot.config["http.proxy_pass"]
+        end
       end
-    end
-    if proxy
-      proxy_host = proxy.host
-      proxy_port = proxy.port
     end
     
     return Net::HTTP.new(uri.host, uri.port, proxy_host, proxy_port, proxy_user, proxy_port)
index 29debaaa6fc8ab9a81dcafe3daf76c56f8465541..d0010c2ae42ba4620d6a0fffdde664cb98597a9d 100644 (file)
@@ -83,42 +83,46 @@ class IrcBot
   # create a new IrcBot with botclass +botclass+
   def initialize(botclass)
     # BotConfig for the core bot
-    BotConfig.register('server.name',
+    BotConfig.register BotConfigStringValue.new('server.name',
       :default => "localhost", :requires_restart => true,
       :desc => "What server should the bot connect to?",
       :wizard => true)
-    BotConfig.register('server.port',
+    BotConfig.register BotConfigIntegerValue.new('server.port',
       :default => 6667, :type => :integer, :requires_restart => true,
-      :desc => "What port should the bot connect to?",
-      :wizard => true)
-    BotConfig.register('server.password',
-      :default => false, :requires_restart => true, :type => :password, 
+      :desc => "What port should the bot connect to?", 
+      :validate => Proc.new {|v| v > 0}, :wizard => true)
+    BotConfig.register BotConfigStringValue.new('server.password',
+      :default => false, :requires_restart => true,
       :desc => "Password for connecting to this server (if required)",
       :wizard => true)
-    BotConfig.register('server.bindhost',
+    BotConfig.register BotConfigStringValue.new('server.bindhost',
       :default => false, :requires_restart => true,
       :desc => "Specific local host or IP for the bot to bind to (if required)",
       :wizard => true)
-    BotConfig.register('server.reconnect_wait',
-      :default => 5, :type => :integer,
+    BotConfig.register BotConfigIntegerValue.new('server.reconnect_wait',
+      :default => 5, :validate => Proc.new{|v| v >= 0},
       :desc => "Seconds to wait before attempting to reconnect, on disconnect")
-    BotConfig.register('irc.nick', :default => "rbot",
+    BotConfig.register BotConfigStringValue.new('irc.nick', :default => "rbot",
       :desc => "IRC nickname the bot should attempt to use", :wizard => true,
-      :on_change => Proc.new{|v| sendq "NICK #{v}" })
-    BotConfig.register('irc.user', :default => "rbot",
+      :on_change => Proc.new{|bot, v| bot.sendq "NICK #{v}" })
+    BotConfig.register BotConfigStringValue.new('irc.user', :default => "rbot",
       :requires_restart => true,
       :desc => "local user the bot should appear to be", :wizard => true)
-    BotConfig.register('irc.join_channels', :default => [], :type => :array,
-      :desc => "What channels the bot should always join at startup. List multiple channels using commas to separate. If a channel requires a password, use a space after the channel name. e.g: '#chan1, #chan2, #secretchan secritpass, #chan3'", :wizard => true)
-    BotConfig.register('core.save_every', :default => 60, 
+    BotConfig.register BotConfigArrayValue.new('irc.join_channels',
+      :default => [], :wizard => true,
+      :desc => "What channels the bot should always join at startup. List multiple channels using commas to separate. If a channel requires a password, use a space after the channel name. e.g: '#chan1, #chan2, #secretchan secritpass, #chan3'")
+    BotConfig.register BotConfigIntegerValue.new('core.save_every',
+      :default => 60, :validate => Proc.new{|v| v >= 0},
       # TODO change timer via on_change proc
       :desc => "How often the bot should persist all configuration to disk (in case of a server crash, for example")
-    BotConfig.register('server.sendq_delay', :default => 2.0, :type => :float,
+    BotConfig.register BotConfigFloatValue.new('server.sendq_delay',
+      :default => 2.0, :validate => Proc.new{|v| v >= 0},
       :desc => "(flood prevention) the delay between sending messages to the server (in seconds)",
-      :on_change => Proc.new {|v| @socket.sendq_delay = v })
-    BotConfig.register('server.sendq_burst', :default => 4, :type => :integer,
+      :on_change => Proc.new {|bot, v| bot.socket.sendq_delay = v })
+    BotConfig.register BotConfigIntegerValue.new('server.sendq_burst',
+      :default => 4, :validate => Proc.new{|v| v >= 0},
       :desc => "(flood prevention) max lines to burst to the server before throttling. Most ircd's allow bursts of up 5 lines, with non-burst limits of 512 bytes/2 seconds",
-      :on_change => Proc.new {|v| @socket.sendq_burst = v })
+      :on_change => Proc.new {|bot, v| bot.socket.sendq_burst = v })
 
     unless FileTest.directory? Config::DATADIR
       puts "data directory '#{Config::DATADIR}' not found, did you install.rb?"
@@ -335,7 +339,7 @@ class IrcBot
             @client.process reply
           end
         end
-      rescue => e
+      rescue => e # TODO be selective, only grab Network errors
         puts "connection closed: #{e}"
         puts e.backtrace.join("\n")
       end
@@ -490,6 +494,11 @@ class IrcBot
   end
 
   # attempt to change bot's nick to +name+
+  # FIXME
+  # if rbot is already taken, this happens:
+  #   <giblet> rbot_, nick rbot
+  #   --- rbot_ is now known as rbot__
+  # he should of course just keep his existing nick and report the error :P
   def nickchg(name)
       sendq "NICK #{name}"
   end
@@ -685,12 +694,14 @@ class IrcBot
             @config['irc.sendq_delay'] = freq
             m.okay
           end
-        when (/^status$/i)
+        when (/^status\??$/i)
           m.reply status if auth.allow?("status", m.source, m.replyto)
         when (/^registry stats$/i)
           if auth.allow?("config", m.source, m.replyto)
             m.reply @registry.stat.inspect
           end
+        when (/^(help\s+)?config(\s+|$)/)
+          @config.privmsg(m)
         when (/^(version)|(introduce yourself)$/i)
           say m.replyto, "I'm a v. #{$version} rubybot, (c) Tom Gilbert - http://linuxbrit.co.uk/rbot/"
         when (/^help(?:\s+(.*))?$/i)
@@ -702,15 +713,13 @@ class IrcBot
         when (/^(hello|howdy|hola|salut|bonjour|sup|niihau|hey|hi(\W|$)|yo(\W|$)).*/i)
           say m.replyto, @lang.get("hello_X") % m.sourcenick if(m.public?)
           say m.replyto, @lang.get("hello") if(m.private?)
-        when (/^config\s+/)
-          @config.privmsg(m)
         else
           delegate_privmsg(m)
       end
     else
       # stuff to handle when not addressed
       case m.message
-        when (/^\s*(hello|howdy|hola|salut|bonjour|sup|niihau|hey|hi(\W|$)|yo(\W|$))\s+#{@nick}$/i)
+        when (/^\s*(hello|howdy|hola|salut|bonjour|sup|niihau|hey|hi(\W|$)|yo(\W|$))[\s,-.]+#{@nick}$/i)
           say m.replyto, @lang.get("hello_X") % m.sourcenick
         when (/^#{@nick}!*$/)
           say m.replyto, @lang.get("hello_X") % m.sourcenick
index 70802e35971555fcc64d26abb2772a9b4a2a12c6..daf3b40b011f2399be63914cfea6af1b23924932 100644 (file)
@@ -81,9 +81,11 @@ module Irc
   # handle it, checks for a keyword command or lookup, otherwise the message
   # is delegated to plugins
   class Keywords
-    BotConfig.register('keyword.listen', :type => :boolean, :default => false,
+    BotConfig.register BotConfigBooleanValue.new('keyword.listen',
+      :default => false,
       :desc => "Should the bot listen to all chat and attempt to automatically detect keywords? (e.g. by spotting someone say 'foo is bar')")
-    BotConfig.register('keyword.address', :type => :boolean, :default => true,
+    BotConfig.register BotConfigBooleanValue.new('keyword.address',
+      :default => true,
       :desc => "Should the bot require that keyword lookups are addressed to it? If not, the bot will attempt to lookup foo if someone says 'foo?' in channel")
     
     # create a new Keywords instance, associated to bot +bot+
index ee6746d6709c139965bd3b047570e0fff8233dfe..d48607b83bef5dd4636bffa3fd9244a4da316f77 100644 (file)
@@ -1,21 +1,24 @@
 module Irc
 
   class Language
-    BotConfig.register('core.language', 
-      :default => "english", :type => :enum,
+    BotConfig.register BotConfigEnumValue.new('core.language', 
+      :default => "english", :wizard => true,
       :values => Dir.new(Config::DATADIR + "/languages").collect {|f|
                    f =~ /\.lang$/ ? f.gsub(/\.lang$/, "") : nil
                  }.compact,   
+      :on_change => Proc.new {|bot, v| bot.lang.set_language v},
       :desc => "Which language file the bot should use")
     
-    def initialize(language, file="")
-      @language = language
-      if file.empty?
-        file = Config::DATADIR + "/languages/#{@language}.lang"
-      end
+    def initialize(language)
+      set_language language
+    end
+
+    def set_language(language)
+      file = Config::DATADIR + "/languages/#{language}.lang"
       unless(FileTest.exist?(file))
-        raise "no such language: #{@language} (no such file #{file})"
+        raise "no such language: #{language} (no such file #{file})"
       end
+      @language = language
       @file = file
       scan
     end
index 4c474ae4f246291c596c92533d18597a948e3445..a1a8c4843670872516755c1d7fd6278458eadd11 100644 (file)
@@ -6,6 +6,24 @@ module Irc
   # miscellaneous useful functions
   module Utils
 
+    # 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|
         if(p)