-# Copyright (C) 2002 Tom Gilbert.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to
-# deal in the Software without restriction, including without limitation the
-# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-# sell copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies of the Software and its documentation and acknowledgment shall be
-# given in the documentation and software packages that this Software was
-# used.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
-# THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
require 'thread'
require 'etc'
require 'fileutils'
+$debug = false unless $debug
+# print +message+ if debugging is enabled
+def debug(message=nil)
+ print "DEBUG: #{message}\n" if($debug && message)
+ #yield
+end
+
# these first
require 'rbot/rbotconfig'
require 'rbot/config'
module Irc
-# Main bot class, which receives messages, handles them or passes them to
-# plugins, and stores runtime data
+# Main bot class, which manages the various components, receives messages,
+# handles them or passes them to plugins, and contains core functionality.
class IrcBot
# the bot's current nickname
attr_reader :nick
# channel info for channels the bot is in
attr_reader :channels
+ # bot's irc socket
+ attr_reader :socket
+
# bot's object registry, plugins get an interface to this for persistant
# storage (hash interface tied to a bdb file, plugins use Accessors to store
# and restore objects in their own namespaces.)
attr_reader :httputil
# create a new IrcBot with botclass +botclass+
- def initialize(botclass)
+ def initialize(botclass, params = {})
# BotConfig for the core bot
BotConfig.register BotConfigStringValue.new('server.name',
:default => "localhost", :requires_restart => true,
: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 {|bot, v| bot.socket.sendq_burst = v })
- unless FileTest.directory? Config::DATADIR
- puts "data directory '#{Config::DATADIR}' not found, did you install.rb?"
+ @argv = params[:argv]
+
+ unless FileTest.directory? Config::datadir
+ puts "data directory '#{Config::datadir}' not found, did you install.rb?"
exit 2
end
puts "Error: file #{botclass} exists but isn't a directory"
exit 2
end
- FileUtils.cp_r Config::DATADIR+'/templates', botclass
+ FileUtils.cp_r Config::datadir+'/templates', botclass
end
Dir.mkdir("#{botclass}/logs") unless File.exist?("#{botclass}/logs")
@startup_time = Time.new
- @config = Irc::BotConfig.new(self)
+ @config = BotConfig.new(self)
@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
@logs = Hash.new
- @httputil = Irc::HttpUtil.new(self)
- @lang = Irc::Language.new(@config['core.language'])
- @keywords = Irc::Keywords.new(self)
- @auth = Irc::IrcAuth.new(self)
+ @httputil = Utils::HttpUtil.new(self)
+ @lang = Language::Language.new(@config['core.language'])
+ @keywords = Keywords.new(self)
+ @auth = IrcAuth.new(self)
Dir.mkdir("#{botclass}/plugins") unless File.exist?("#{botclass}/plugins")
- @plugins = Irc::Plugins.new(self, ["#{botclass}/plugins"])
+ @plugins = Plugins::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'])
+ @socket = IrcSocket.new(@config['server.name'], @config['server.port'], @config['server.bindhost'], @config['server.sendq_delay'], @config['server.sendq_burst'])
@nick = @config['irc.nick']
if @config['core.address_prefix']
@addressing_prefixes = @config['core.address_prefix'].split(" ")
@addressing_prefixes = Array.new
end
- @client = Irc::IrcClient.new
+ @client = IrcClient.new
@client["PRIVMSG"] = proc { |data|
message = PrivMessage.new(self, data["SOURCE"], data["TARGET"], data["MESSAGE"])
onprivmsg(message)
@client.process reply
end
end
+ rescue TimeoutError, SocketError => e
+ puts "network exception: connection closed: #{e}"
+ puts e.backtrace.join("\n")
+ @socket.close # now we reconnect
rescue => e # TODO be selective, only grab Network errors
- puts "connection closed: #{e}"
+ puts "unexpected exception: connection closed: #{e}"
puts e.backtrace.join("\n")
+ exit 2
end
puts "disconnected"
end while(message.length > 0)
end
+ # queue an arbitraty message for the server
def sendq(message="")
# temporary
@socket.queue(message)
def topic(where, topic)
sendq "TOPIC #{where} :#{topic}"
end
-
- # message:: optional IRC quit message
- # quit IRC, shutdown the bot
- def quit(message=nil)
+
+ # disconnect from the server and cleanup all plugins and modules
+ def shutdown(message = nil)
trap("SIGTERM", "DEFAULT")
trap("SIGHUP", "DEFAULT")
trap("SIGINT", "DEFAULT")
- message = @lang.get("quit") if (!message || message.length < 1)
+ message = @lang.get("quit") if (message.nil? || message.empty?)
@socket.clearq
save
@plugins.cleanup
@socket.shutdown
@registry.close
puts "rbot quit (#{message})"
+ end
+
+ # message:: optional IRC quit message
+ # quit IRC, shutdown the bot
+ def quit(message=nil)
+ shutdown(message)
exit 0
end
+ # totally shutdown and respawn the bot
+ def restart
+ shutdown("restarting, back in #{@config['server.reconnect_wait']}...")
+ sleep @config['server.reconnect_wait']
+ # now we re-exec
+ exec($0, *@argv)
+ end
+
# call the save method for bot's config, keywords, auth and all plugins
def save
@registry.flush
return helpstr
end
+ # returns a string describing the current status of the bot (uptime etc)
def status
secs_up = Time.new - @startup_time
uptime = Utils.secs_to_string secs_up
case topic
when "quit"
return "quit [<message>] => quit IRC with message <message>"
+ when "restart"
+ return "restart => completely stop and restart the bot (including reconnect)"
when "join"
return "join <channel> [<key>] => join channel <channel> with secret key <key> if specified. #{@nick} also responds to invites if you have the required access level"
when "part"
when "hello"
return "hello|hi|hey|yo [#{@nick}] => greet the bot"
else
- return "Core help topics: quit, join, part, hide, save, rescan, nick, say, action, topic, quiet, talk, version, botsnack, hello"
+ return "Core help topics: quit, restart, config, join, part, hide, save, rescan, nick, say, action, topic, quiet, talk, version, botsnack, hello"
end
end
part $1 if(@auth.allow?("join", m.source, m.replyto))
when (/^quit(?:\s+(.*))?$/i)
quit $1 if(@auth.allow?("quit", m.source, m.replyto))
+ when (/^restart$/i)
+ restart if(@auth.allow?("quit", m.source, m.replyto))
when (/^hide$/i)
join 0 if(@auth.allow?("join", m.source, m.replyto))
when (/^save$/i)
@channels[where].quiet = false if(@channels.has_key?(where))
m.okay
end
- # TODO break this out into a config module
- when (/^options get sendq_delay$/i)
- if auth.allow?("config", m.source, m.replyto)
- m.reply "options->sendq_delay = #{@socket.sendq_delay}"
- end
- when (/^options get sendq_burst$/i)
- if auth.allow?("config", m.source, m.replyto)
- m.reply "options->sendq_burst = #{@socket.sendq_burst}"
- end
- when (/^options set sendq_burst (.*)$/i)
- num = $1.to_i
- if auth.allow?("config", m.source, m.replyto)
- @socket.sendq_burst = num
- @config['irc.sendq_burst'] = num
- m.okay
- end
- when (/^options set sendq_delay (.*)$/i)
- freq = $1.to_f
- if auth.allow?("config", m.source, m.replyto)
- @socket.sendq_delay = freq
- @config['irc.sendq_delay'] = freq
- m.okay
- end
when (/^status\??$/i)
m.reply status if auth.allow?("status", m.source, m.replyto)
when (/^registry stats$/i)