$debug = false unless $debug
$daemonize = false unless $daemonize
+# TODO we should use the actual Logger class
def rawlog(code="", message=nil)
if !code || code.empty?
c = " "
else
c = code.to_s[0,1].upcase + ":"
end
+ call_stack = caller
+ case call_stack.length
+ when 0
+ $stderr.puts "ERROR IN THE LOGGING SYSTEM, THIS CAN'T HAPPEN"
+ who = "WTF1?? "
+ when 1
+ $stderr.puts "ERROR IN THE LOGGING SYSTEM, THIS CAN'T HAPPEN"
+ who = "WTF2?? "
+ else
+ who = call_stack[1].sub(%r{(?:.+)/([^/]+):(\d+)(:in .*)?}) { "#{$1}:#{$2}#{$3}" }
+ end
stamp = Time.now.strftime("%Y/%m/%d %H:%M:%S")
message.to_s.each_line { |l|
- $stdout.puts "#{c} [#{stamp}] #{l}"
+ $stdout.puts "#{c} [#{stamp}] #{who} -- #{l}"
}
$stdout.flush
end
end
def log_session_end
- log("\n=== #{botclass} session ended ===") if $daemonize
+ rawlog("", "\n=== #{botclass} session ended ===") if $daemonize
end
def debug(message=nil)
rawlog("E", message)
end
+debug "debug test"
+log "log test"
+warning "warning test"
+error "error test"
+
# The following global is used for the improved signal handling.
$interrupted = 0
# bot's Language data
attr_reader :lang
+ # capabilities info for the server
+ attr_reader :capabilities
+
# channel info for channels the bot is in
attr_reader :channels
# and restore objects in their own namespaces.)
attr_reader :registry
+ # bot's plugins. This is an instance of class Plugins
+ attr_reader :plugins
+
# bot's httputil help object, for fetching resources via http. Sets up
# proxies etc as defined by the bot configuration/environment
attr_reader :httputil
:desc => "(flood prevention) max bytes/seconds rate to send the server. Most ircd's have limits of 512 bytes/2 seconds",
:on_change => Proc.new {|bot, v| bot.socket.byterate = v })
BotConfig.register BotConfigIntegerValue.new('server.ping_timeout',
- :default => 10, :validate => Proc.new{|v| v >= 0},
+ :default => 30, :validate => Proc.new{|v| v >= 0},
:on_change => Proc.new {|bot, v| bot.start_server_pings},
:desc => "reconnect if server doesn't respond to PING within this many seconds (set to 0 to disable)")
exit 2
end
- botclass = "#{Etc.getpwuid(Process::Sys.geteuid)[:dir]}/.rbot" unless botclass
- #botclass = "#{ENV['HOME']}/.rbot" unless botclass
+ unless botclass and not botclass.empty?
+ # We want to find a sensible default.
+ # * On POSIX systems we prefer ~/.rbot for the effective uid of the process
+ # * On Windows (at least the NT versions) we want to put our stuff in the
+ # Application Data folder.
+ # We don't use any particular O/S detection magic, exploiting the fact that
+ # Etc.getpwuid is nil on Windows
+ if Etc.getpwuid(Process::Sys.geteuid)
+ botclass = Etc.getpwuid(Process::Sys.geteuid)[:dir].dup
+ else
+ if ENV.has_key?('APPDATA')
+ botclass = ENV['APPDATA'].dup
+ botclass.gsub!("\\","/")
+ end
+ end
+ botclass += "/.rbot"
+ end
botclass = File.expand_path(botclass)
@botclass = botclass.gsub(/\/$/, "")
@nick = @config['irc.nick']
@client = IrcClient.new
+ @client[:isupport] = proc { |data|
+ if data[:capab]
+ sendq "CAPAB IDENTIFY-MSG"
+ end
+ }
+ @client[:datastr] = proc { |data|
+ debug data.inspect
+ if data[:text] == "IDENTIFY-MSG"
+ @capabilities["identify-msg".to_sym] = true
+ else
+ debug "Not handling RPL_DATASTR #{data[:servermessage]}"
+ end
+ }
@client[:privmsg] = proc { |data|
message = PrivMessage.new(self, data[:source], data[:target], data[:message])
onprivmsg(message)
warning "bad nick (#{data[:nick]})"
}
@client[:ping] = proc {|data|
- # (jump the queue for pongs)
- @socket.puts "PONG #{data[:pingid]}"
+ @socket.queue "PONG #{data[:pingid]}"
}
@client[:pong] = proc {|data|
@last_ping = nil
debug "failed to trap signals: #{e.inspect}"
end
begin
- @socket.connect unless $interrupted > 0
+ quit if $interrupted > 0
+ @socket.connect
rescue => e
raise e.class, "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['irc.user']} 4 #{@config['server.name']} :Ruby bot. (c) Tom Gilbert"
+ @socket.emergency_puts "PASS " + @config['server.password'] if @config['server.password']
+ @socket.emergency_puts "NICK #{@nick}\nUSER #{@config['irc.user']} 4 #{@config['server.name']} :Ruby bot. (c) Tom Gilbert"
+ @capabilities = Hash.new
start_server_pings
end
def mainloop
while true
begin
+ quit if $interrupted > 0
connect
@timer.start
break unless reply = @socket.gets
@client.process reply
end
- quit if $interrupted > 0
+ quit if $interrupted > 0
end
# I despair of this. Some of my users get "connection reset by peer"
rescue SystemExit
log_session_end
exit 0
- rescue TimeoutError, SocketError => e
+ rescue Errno::ETIMEDOUT, TimeoutError, SocketError => e
error "network exception: #{e.class}: #{e}"
- debug e.inspect
debug e.backtrace.join("\n")
rescue BDB::Fatal => e
error "fatal bdb error: #{e.class}: #{e}"
- error e.inspect
error e.backtrace.join("\n")
DBTree.stats
restart("Oops, we seem to have registry problems ...")
rescue Exception => e
error "non-net exception: #{e.class}: #{e}"
- error e.inspect
error e.backtrace.join("\n")
- @socket.shutdown # now we reconnect
rescue => e
error "unexpected exception: #{e.class}: #{e}"
- error e.inspect
error e.backtrace.join("\n")
log_session_end
exit 2
end
- log "disconnected"
-
stop_server_pings
@channels.clear
- @socket.clearq
+ if @socket.connected?
+ @socket.clearq
+ @socket.shutdown
+ end
+
+ log "disconnected"
+
+ quit if $interrupted > 0
log "waiting to reconnect"
sleep @config['server.reconnect_wait']
# Type can be PRIVMSG, NOTICE, etc, but those you should really use the
# relevant say() or notice() methods. This one should be used for IRCd
# extensions you want to use in modules.
- def sendmsg(type, where, message)
+ def sendmsg(type, where, message, chan=nil, ring=0)
# limit it according to the byterate, splitting the message
# taking into consideration the actual message length
# and all the extra stuff
left = @socket.bytes_per - type.length - where.length - 4
begin
if(left >= message.length)
- sendq("#{type} #{where} :#{message}")
+ sendq "#{type} #{where} :#{message}", chan, ring
log_sent(type, where, message)
return
end
message = line.slice!(lastspace, line.length) + message
message.gsub!(/^\s+/, "")
end
- sendq("#{type} #{where} :#{line}")
+ sendq "#{type} #{where} :#{line}", chan, ring
log_sent(type, where, line)
end while(message.length > 0)
end
# queue an arbitraty message for the server
- def sendq(message="")
+ def sendq(message="", chan=nil, ring=0)
# temporary
- @socket.queue(message)
+ @socket.queue(message, chan, ring)
end
# send a notice message to channel/nick +where+
- def notice(where, message)
+ def notice(where, message, mchan=nil, mring=-1)
+ if mchan == ""
+ chan = where
+ else
+ chan = mchan
+ end
+ if mring < 0
+ if where =~ /^#/
+ ring = 2
+ else
+ ring = 1
+ end
+ else
+ ring = mring
+ end
message.each_line { |line|
line.chomp!
next unless(line.length > 0)
- sendmsg("NOTICE", where, line)
+ sendmsg "NOTICE", where, line, chan, ring
}
end
# say something (PRIVMSG) to channel/nick +where+
- def say(where, message)
+ def say(where, message, mchan="", mring=-1)
+ if mchan == ""
+ chan = where
+ else
+ chan = mchan
+ end
+ if mring < 0
+ if where =~ /^#/
+ ring = 2
+ else
+ ring = 1
+ end
+ else
+ ring = mring
+ end
message.to_s.gsub(/[\r\n]+/, "\n").each_line { |line|
line.chomp!
next unless(line.length > 0)
unless((where =~ /^#/) && (@channels.has_key?(where) && @channels[where].quiet))
- sendmsg("PRIVMSG", where, line)
+ sendmsg "PRIVMSG", where, line, chan, ring
end
}
end
# perform a CTCP action with message +message+ to channel/nick +where+
- def action(where, message)
- sendq("PRIVMSG #{where} :\001ACTION #{message}\001")
+ def action(where, message, mchan="", mring=-1)
+ if mchan == ""
+ chan = where
+ else
+ chan = mchan
+ end
+ if mring < 0
+ if where =~ /^#/
+ ring = 2
+ else
+ ring = 1
+ end
+ else
+ ring = mring
+ end
+ sendq "PRIVMSG #{where} :\001ACTION #{message}\001", chan, ring
if(where =~ /^#/)
irclog "* #{@nick} #{message}", where
elsif (where =~ /^(\S*)!.*$/)
- irclog "* #{@nick}[#{where}] #{message}", $1
+ irclog "* #{@nick}[#{where}] #{message}", $1
else
- irclog "* #{@nick}[#{where}] #{message}", where
+ irclog "* #{@nick}[#{where}] #{message}", where
end
end
# set topic of channel +where+ to +topic+
def topic(where, topic)
- sendq "TOPIC #{where} :#{topic}"
+ sendq "TOPIC #{where} :#{topic}", where, 2
end
# disconnect from the server and cleanup all plugins and modules
debug "Clearing socket"
@socket.clearq
debug "Sending quit message"
- @socket.puts "QUIT :#{message}"
+ @socket.emergency_puts "QUIT :#{message}"
debug "Flushing socket"
@socket.flush
debug "Shutting down socket"
# join a channel
def join(channel, key=nil)
if(key)
- sendq "JOIN #{channel} :#{key}"
+ sendq "JOIN #{channel} :#{key}", channel, 2
else
- sendq "JOIN #{channel}"
+ sendq "JOIN #{channel}", channel, 2
end
end
# part a channel
def part(channel, message="")
- sendq "PART #{channel} :#{message}"
+ sendq "PART #{channel} :#{message}", channel, 2
end
# attempt to change bot's nick to +name+
# changing mode
def mode(channel, mode, target)
- sendq "MODE #{channel} #{mode} #{target}"
+ sendq "MODE #{channel} #{mode} #{target}", channel, 2
end
# m:: message asking for help
# we want to respond to a hung server within 30 secs or so
@ping_timer = @timer.add(30) {
@last_ping = Time.now
- @socket.puts "PING :rbot"
+ @socket.queue "PING :rbot"
}
@pong_timer = @timer.add(10) {
unless @last_ping.nil?