# 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'
# 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
@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'])
@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"])
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}"
@socket.clearq
puts "waiting to reconnect"
- sleep reconnect_wait
+ sleep @config['server.reconnect_wait']
end
end
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
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
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)
# 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