]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blobdiff - lib/rbot/ircbot.rb
Sat Jul 30 01:19:32 BST 2005 Tom Gilbert <tom@linuxbrit.co.uk>
[user/henk/code/ruby/rbot.git] / lib / rbot / ircbot.rb
index 844231ddeddb19512467a2c1f0df7475f2e9f8fb..29debaaa6fc8ab9a81dcafe3daf76c56f8465541 100644 (file)
 # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 require 'thread'
+require 'etc'
+require 'fileutils'
+
+# these first
+require 'rbot/rbotconfig'
+require 'rbot/config'
+require 'rbot/utils'
 
 require 'rbot/rfc2812'
 require 'rbot/keywords'
-require 'rbot/config'
 require 'rbot/ircsocket'
 require 'rbot/auth'
 require 'rbot/timer'
 require 'rbot/plugins'
 require 'rbot/channel'
-require 'rbot/utils'
 require 'rbot/message'
 require 'rbot/language'
 require 'rbot/dbhash'
@@ -77,14 +82,66 @@ class IrcBot
 
   # create a new IrcBot with botclass +botclass+
   def initialize(botclass)
+    # BotConfig for the core bot
+    BotConfig.register('server.name',
+      :default => "localhost", :requires_restart => true,
+      :desc => "What server should the bot connect to?",
+      :wizard => true)
+    BotConfig.register('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 => "Password for connecting to this server (if required)",
+      :wizard => true)
+    BotConfig.register('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,
+      :desc => "Seconds to wait before attempting to reconnect, on disconnect")
+    BotConfig.register('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",
+      :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, 
+      # 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,
+      :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,
+      :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 })
+
+    unless FileTest.directory? Config::DATADIR
+      puts "data directory '#{Config::DATADIR}' not found, did you install.rb?"
+      exit 2
+    end
+    
+    botclass = "/home/#{Etc.getlogin}/.rbot" unless botclass
     @botclass = botclass.gsub(/\/$/, "")
-    @startup_time = Time.new
+
+    unless FileTest.directory? botclass
+      puts "no #{botclass} directory found, creating from templates.."
+      if FileTest.exist? botclass
+        puts "Error: file #{botclass} exists but isn't a directory"
+        exit 2
+      end
+      FileUtils.cp_r Config::DATADIR+'/templates', botclass
+    end
     
-    Dir.mkdir("#{botclass}") if(!File.exist?("#{botclass}"))
-    Dir.mkdir("#{botclass}/logs") if(!File.exist?("#{botclass}/logs"))
+    Dir.mkdir("#{botclass}/logs") unless File.exist?("#{botclass}/logs")
 
+    @startup_time = Time.new
     @config = Irc::BotConfig.new(self)
-    @timer = Timer::Timer.new
+    @timer = Timer::Timer.new(1.0) # only need per-second granularity
     @registry = BotRegistry.new self
     @timer.add(@config['core.save_every']) { save } if @config['core.save_every']
     @channels = Hash.new
@@ -94,6 +151,8 @@ class IrcBot
     @lang = Irc::Language.new(@config['core.language'])
     @keywords = Irc::Keywords.new(self)
     @auth = Irc::IrcAuth.new(self)
+
+    Dir.mkdir("#{botclass}/plugins") unless File.exist?("#{botclass}/plugins")
     @plugins = Irc::Plugins.new(self, ["#{botclass}/plugins"])
 
     @socket = Irc::IrcSocket.new(@config['server.name'], @config['server.port'], @config['server.bindhost'], @config['server.sendq_delay'], @config['server.sendq_burst'])
@@ -180,20 +239,19 @@ class IrcBot
         @nick = data['NICK']
       end
       if(@config['irc.quser'])
-        puts "authing with Q using  #{@config['quakenet.user']} #{@config['quakenet.auth']}"
+        # TODO move this to a plugin
+        debug "authing with Q using  #{@config['quakenet.user']} #{@config['quakenet.auth']}"
         @socket.puts "PRIVMSG Q@CServe.quakenet.org :auth #{@config['quakenet.user']} #{@config['quakenet.auth']}"
       end
 
-      if(@config['irc.join_channels'])
-        @config['irc.join_channels'].split(", ").each {|c|
-          puts "autojoining channel #{c}"
-          if(c =~ /^(\S+)\s+(\S+)$/i)
-            join $1, $2
-          else
-            join c if(c)
-          end
-        }
-      end
+      @config['irc.join_channels'].each {|c|
+        debug "autojoining channel #{c}"
+        if(c =~ /^(\S+)\s+(\S+)$/i)
+          join $1, $2
+        else
+          join c if(c)
+        end
+      }
     }
     @client["JOIN"] = proc {|data|
       m = JoinMessage.new(self, data["SOURCE"], data["CHANNEL"], data["MESSAGE"])
@@ -261,24 +319,21 @@ class IrcBot
       raise "failed to connect to IRC server at #{@config['server.name']} #{@config['server.port']}: " + e
     end
     @socket.puts "PASS " + @config['server.password'] if @config['server.password']
-    @socket.puts "NICK #{@nick}\nUSER #{@config['server.user']} 4 #{@config['server.name']} :Ruby bot. (c) Tom Gilbert"
+    @socket.puts "NICK #{@nick}\nUSER #{@config['irc.user']} 4 #{@config['server.name']} :Ruby bot. (c) Tom Gilbert"
   end
 
   # begin event handling loop
   def mainloop
-    socket_timeout = 0.2
-    reconnect_wait = 5
-    
     while true
       connect
+      @timer.start
       
       begin
         while true
-          if @socket.select socket_timeout
+          if @socket.select
             break unless reply = @socket.gets
             @client.process reply
           end
-          @timer.tick
         end
       rescue => e
         puts "connection closed: #{e}"
@@ -290,7 +345,7 @@ class IrcBot
       @socket.clearq
       
       puts "waiting to reconnect"
-      sleep reconnect_wait
+      sleep @config['server.reconnect_wait']
     end
   end
   
@@ -640,12 +695,15 @@ class IrcBot
           say m.replyto, "I'm a v. #{$version} rubybot, (c) Tom Gilbert - http://linuxbrit.co.uk/rbot/"
         when (/^help(?:\s+(.*))?$/i)
           say m.replyto, help($1)
+          #TODO move these to a "chatback" plugin
         when (/^(botsnack|ciggie)$/i)
           say m.replyto, @lang.get("thanks_X") % m.sourcenick if(m.public?)
           say m.replyto, @lang.get("thanks") if(m.private?)
         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
@@ -687,8 +745,8 @@ class IrcBot
   def onjoin(m)
     @channels[m.channel] = IRCChannel.new(m.channel) unless(@channels.has_key?(m.channel))
     if(m.address?)
+      debug "joined channel #{m.channel}"
       log "@ Joined channel #{m.channel}", m.channel
-      puts "joined channel #{m.channel}"
     else
       log "@ #{m.sourcenick} joined channel #{m.channel}", m.channel
       @channels[m.channel].users[m.sourcenick] = Hash.new
@@ -701,9 +759,9 @@ class IrcBot
 
   def onpart(m)
     if(m.address?)
+      debug "left channel #{m.channel}"
       log "@ Left channel #{m.channel} (#{m.message})", m.channel
       @channels.delete(m.channel)
-      puts "left channel #{m.channel}"
     else
       log "@ #{m.sourcenick} left channel #{m.channel} (#{m.message})", m.channel
       @channels[m.channel].users.delete(m.sourcenick)
@@ -717,9 +775,9 @@ class IrcBot
   # respond to being kicked from a channel
   def onkick(m)
     if(m.address?)
+      debug "kicked from channel #{m.channel}"
       @channels.delete(m.channel)
       log "@ You have been kicked from #{m.channel} by #{m.sourcenick} (#{m.message})", m.channel
-      puts "kicked from channel #{m.channel}"
     else
       @channels[m.channel].users.delete(m.sourcenick)
       log "@ #{m.target} has been kicked from #{m.channel} by #{m.sourcenick} (#{m.message})", m.channel