X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=lib%2Frbot%2Fircbot.rb;h=87d4f3eb40edfcb702941b809380afb2919395b2;hb=4a86158144a13bc901222442ccd2db9c2bbd6bb0;hp=a22079bfe7c8013b98c775542ddd0b1b67814cfd;hpb=3ac610a592b8cc877edf10b7fcc7f55162a9136a;p=user%2Fhenk%2Fcode%2Fruby%2Frbot.git diff --git a/lib/rbot/ircbot.rb b/lib/rbot/ircbot.rb index a22079bf..87d4f3eb 100644 --- a/lib/rbot/ircbot.rb +++ b/lib/rbot/ircbot.rb @@ -10,9 +10,26 @@ $daemonize = false unless $daemonize $dateformat = "%Y/%m/%d %H:%M:%S" $logger = Logger.new($stderr) $logger.datetime_format = $dateformat -$logger.level = $cl_loglevel if $cl_loglevel +$logger.level = $cl_loglevel if defined? $cl_loglevel $logger.level = 0 if $debug +require 'pp' + +unless Kernel.instance_methods.include?("pretty_inspect") + def pretty_inspect + PP.pp(self, '') + end + public :pretty_inspect +end + +class Exception + def pretty_print(q) + q.group(1, "#<%s: %s" % [self.class, self.message], ">") { + q.seplist(self.backtrace, lambda { "\n" }) { |v| v } if self.backtrace + } + end +end + def rawlog(level, message=nil, who_pos=1) call_stack = caller if call_stack.length > who_pos @@ -23,7 +40,14 @@ def rawlog(level, message=nil, who_pos=1) # Output each line. To distinguish between separate messages and multi-line # messages originating at the same time, we blank #{who} after the first message # is output. - message.to_s.each_line { |l| + # Also, we output strings as-is but for other objects we use pretty_inspect + case message + when String + str = message + else + str = message.pretty_inspect + end + str.each_line { |l| $logger.add(level, l.chomp, who) who.gsub!(/./," ") } @@ -68,6 +92,7 @@ $interrupted = 0 # these first require 'rbot/rbotconfig' +require 'rbot/load-gettext' require 'rbot/config' # require 'rbot/utils' @@ -193,7 +218,7 @@ class Bot :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 BotConfigArrayValue.new('irc.ignore_users', - :default => [], + :default => [], :desc => "Which users to ignore input from. This is mainly to avoid bot-wars triggered by creative people") BotConfig.register BotConfigIntegerValue.new('core.save_every', @@ -257,7 +282,7 @@ class Bot }, :desc => "String used to replace newlines when send.newlines is set to join") BotConfig.register BotConfigIntegerValue.new('send.max_lines', - :default => 0, + :default => 5, :validate => Proc.new { |v| v >= 0 }, :on_change => Proc.new { |bot, v| bot.set_default_send_options :max_lines => v @@ -289,6 +314,7 @@ class Bot :desc => "When truncating overlong messages (see send.overlong) or when sending too many lines per message (see send.max_lines) replace the end of the last line with this text") @argv = params[:argv] + @run_dir = params[:run_dir] || Dir.pwd unless FileTest.directory? Config::coredir error "core directory '#{Config::coredir}' not found, did you setup.rb?" @@ -343,9 +369,8 @@ class Bot begin @config = BotConfig.configmanager @config.bot_associate(self) - rescue => e - fatal e.inspect - fatal e.backtrace.join("\n") + rescue Exception => e + fatal e log_session_end exit 2 end @@ -360,7 +385,7 @@ class Bot end # See http://blog.humlab.umu.se/samuel/archives/000107.html - # for the backgrounding code + # for the backgrounding code if $daemonize begin exit if fork @@ -368,8 +393,10 @@ class Bot exit if fork rescue NotImplementedError warning "Could not background, fork not supported" - rescue => e - warning "Could not background. #{e.inspect}" + rescue SystemExit + exit 0 + rescue Exception => e + warning "Could not background. #{e.pretty_inspect}" end Dir.chdir botclass # File.umask 0000 # Ensure sensible umask. Adjust as needed. @@ -398,11 +425,15 @@ class Bot $logger = Logger.new(@logfile, @config['log.keep'], @config['log.max_size']*1024*1024) $logger.datetime_format= $dateformat $logger.level = @config['log.level'] - $logger.level = $cl_loglevel if $cl_loglevel + $logger.level = $cl_loglevel if defined? $cl_loglevel $logger.level = 0 if $debug log_session_start + File.open($opts['pidfile'] || "#{@botclass}/rbot.pid", 'w') do |pf| + pf << "#{$$}\n" + end + @registry = BotRegistry.new self @timer = Timer::Timer.new(1.0) # only need per-second granularity @@ -423,9 +454,8 @@ class Bot @auth = Auth::authmanager @auth.bot_associate(self) # @auth.load("#{botclass}/botusers.yaml") - rescue => e - fatal e.inspect - fatal e.backtrace.join("\n") + rescue Exception => e + fatal e log_session_end exit 2 end @@ -526,7 +556,7 @@ class Bot } } @client[:nicktaken] = proc { |data| - new = "#{data[:nick]}_" + new = "#{data[:nick]}_" nickchg new # If we're setting our nick at connection because our choice was taken, # we have to fix our nick manually, because there will be no NICK message @@ -538,7 +568,7 @@ class Bot @plugins.delegate "nicktaken", data[:nick] } @client[:badnick] = proc {|data| - arning "bad nick (#{data[:nick]})" + warning "bad nick (#{data[:nick]})" } @client[:ping] = proc {|data| sendq "PONG #{data[:pingid]}" @@ -581,6 +611,7 @@ class Bot @plugins.delegate("listen", m) @plugins.delegate("join", m) + sendq "WHO #{data[:channel]}", data[:channel], 2 } @client[:part] = proc {|data| m = PartMessage.new(self, server, data[:source], data[:channel], data[:message]) @@ -711,9 +742,9 @@ class Bot trap("SIGTERM") { got_sig("SIGTERM") } trap("SIGHUP") { got_sig("SIGHUP") } rescue ArgumentError => e - debug "failed to trap signals (#{e.inspect}): running on Windows?" - rescue => e - debug "failed to trap signals: #{e.inspect}" + debug "failed to trap signals (#{e.pretty_inspect}): running on Windows?" + rescue Exception => e + debug "failed to trap signals: #{e.pretty_inspect}" end begin quit if $interrupted > 0 @@ -724,7 +755,7 @@ class Bot quit if $interrupted > 0 realname = @config['irc.name'].clone || 'Ruby bot' - realname << ' ' + COPYRIGHT_NOTICE if @config['irc.name_copyright'] + realname << ' ' + COPYRIGHT_NOTICE if @config['irc.name_copyright'] @socket.emergency_puts "PASS " + @config['server.password'] if @config['server.password'] @socket.emergency_puts "NICK #{@config['irc.nick']}\nUSER #{@config['irc.user']} 4 #{@socket.server_uri.host} :#{realname}" @@ -765,24 +796,20 @@ class Bot log_session_end exit 0 rescue Errno::ETIMEDOUT, Errno::ECONNABORTED, TimeoutError, SocketError => e - error "network exception: #{e.class}: #{e}" - debug e.backtrace.join("\n") + error "network exception: #{e.pretty_inspect}" quit_msg = e.to_s rescue BDB::Fatal => e - fatal "fatal bdb error: #{e.class}: #{e}" - fatal e.backtrace.join("\n") + fatal "fatal bdb error: #{e.pretty_inspect}" DBTree.stats # Why restart? DB problems are serious stuff ... # restart("Oops, we seem to have registry problems ...") log_session_end exit 2 rescue Exception => e - error "non-net exception: #{e.class}: #{e}" - error e.backtrace.join("\n") + error "non-net exception: #{e.pretty_inspect}" quit_msg = e.to_s rescue => e - fatal "unexpected exception: #{e.class}: #{e}" - fatal e.backtrace.join("\n") + fatal "unexpected exception: #{e.pretty_inspect}" log_session_end exit 2 end @@ -827,16 +854,16 @@ class Bot end end - message = original_message.to_s.gsub(/[\r\n]+/, "\n") + multi_line = original_message.to_s.gsub(/[\r\n]+/, "\n") + messages = Array.new case opts[:newlines] when :join - lines = [message.gsub("\n", opts[:join_with])] + messages << [multi_line.gsub("\n", opts[:join_with])] when :split - lines = Array.new - message.each_line { |line| + multi_line.each_line { |line| line.chomp! next unless(line.size > 0) - lines << line + messages << line } else raise "Unknown :newlines option #{opts[:newlines]} while sending #{original_message.inspect}" @@ -865,56 +892,47 @@ class Bot # And this is what's left left = max_len - fixed.size - case opts[:overlong] - when :split - truncate = false - split_at = opts[:split_at] - when :truncate - truncate = opts[:truncate_text] - truncate = @default_send_options[:truncate_text] if truncate.size > left - truncate = "" if truncate.size > left + truncate = opts[:truncate_text] + truncate = @default_send_options[:truncate_text] if truncate.size > left + truncate = "" if truncate.size > left + + all_lines = messages.map { |line| + if line.size < left + line + else + case opts[:overlong] + when :split + msg = line.dup + sub_lines = Array.new + begin + sub_lines << msg.slice!(0, left) + break if msg.empty? + lastspace = sub_lines.last.rindex(opts[:split_at]) + if lastspace + msg.replace sub_lines.last.slice!(lastspace, sub_lines.last.size) + msg + msg.gsub!(/^#{opts[:split_at]}/, "") if opts[:purge_split] + end + end until msg.empty? + sub_lines + when :truncate + line.slice(0, left - truncate.size) << truncate + else + raise "Unknown :overlong option #{opts[:overlong]} while sending #{original_message.inspect}" + end + end + }.flatten + + if opts[:max_lines] > 0 and all_lines.length > opts[:max_lines] + lines = all_lines[0...opts[:max_lines]] + new_last = lines.last.slice(0, left - truncate.size) << truncate + lines.last.replace(new_last) else - raise "Unknown :overlong option #{opts[:overlong]} while sending #{original_message.inspect}" + lines = all_lines end - # Counter to check the number of lines sent by this command - cmd_lines = 0 - max_lines = opts[:max_lines] - maxed = false - line = String.new - lines.each { |msg| - begin - if max_lines > 0 and cmd_lines == max_lines - 1 - truncate = opts[:truncate_text] - truncate = @default_send_options[:truncate_text] if truncate.size > left - truncate = "" if truncate.size > left - maxed = true - end - if(left >= msg.size) and not maxed - sendq "#{fixed}#{msg}", chan, ring - log_sent(type, where, msg) - cmd_lines += 1 - break - end - if truncate - line.replace msg.slice(0, left-truncate.size) - # line.sub!(/\s+\S*$/, truncate) - line << truncate - raise "PROGRAMMER ERROR! #{line.inspect} of size #{line.size} > #{left}" if line.size > left - sendq "#{fixed}#{line}", chan, ring - log_sent(type, where, line) - return - end - line.replace msg.slice!(0, left) - lastspace = line.rindex(opts[:split_at]) - if(lastspace) - msg.replace line.slice!(lastspace, line.size) + msg - msg.gsub!(/^#{opts[:split_at]}/, "") if opts[:purge_split] - end - sendq "#{fixed}#{line}", chan, ring - log_sent(type, where, line) - cmd_lines += 1 - end while(msg.size > 0) + lines.each { |line| + sendq "#{fixed}#{line}", chan, ring + log_sent(type, where, line) } end @@ -994,8 +1012,8 @@ class Bot sendq "TOPIC #{where} :#{topic}", where, 2 end - def disconnect(message = nil) - message = @lang.get("quit") if (message.nil? || message.empty?) + def disconnect(message=nil) + message = @lang.get("quit") if (!message || message.empty?) if @socket.connected? debug "Clearing socket" @socket.clearq @@ -1015,9 +1033,9 @@ class Bot end # disconnect from the server and cleanup all plugins and modules - def shutdown(message = nil) + def shutdown(message=nil) @quit_mutex.synchronize do - debug "Shutting down ..." + debug "Shutting down: #{message}" ## No we don't restore them ... let everything run through # begin # trap("SIGINT", "DEFAULT") @@ -1026,16 +1044,19 @@ class Bot # rescue => e # debug "failed to restore signals: #{e.inspect}\nProbably running on windows?" # end - disconnect - debug "Saving" + debug "\tdisconnecting..." + disconnect(message) + debug "\tsaving ..." save - debug "Cleaning up" + debug "\tcleaning up ..." @save_mutex.synchronize do @plugins.cleanup end + debug "\tstopping timers ..." + @timer.stop # debug "Closing registries" # @registry.close - debug "Cleaning up the db environment" + debug "\t\tcleaning up the db environment ..." DBTree.cleanup_env log "rbot quit (#{message})" end @@ -1052,13 +1073,22 @@ class Bot end # totally shutdown and respawn the bot - def restart(message = false) - msg = message ? message : "restarting, back in #{@config['server.reconnect_wait']}..." - shutdown(msg) + def restart(message=nil) + message = "restarting, back in #{@config['server.reconnect_wait']}..." if (!message || message.empty?) + shutdown(message) sleep @config['server.reconnect_wait'] - # now we re-exec - # Note, this fails on Windows - exec($0, *@argv) + begin + # now we re-exec + # Note, this fails on Windows + debug "going to exec #{$0} #{@argv.inspect} from #{@run_dir}" + Dir.chdir(@run_dir) + exec($0, *@argv) + rescue Errno::ENOENT + exec("ruby", *(@argv.unshift $0)) + rescue Exception => e + $interrupted += 1 + raise e + end end # call the save method for all of the botmodules @@ -1115,12 +1145,12 @@ class Bot topic = nil if topic == "" case topic when nil - helpstr = "help topics: " + helpstr = _("help topics: ") helpstr += @plugins.helptopics - helpstr += " (help for more info)" + helpstr += _(" (help for more info)") else unless(helpstr = @plugins.help(topic)) - helpstr = "no help for topic #{topic}" + helpstr = _("no help for topic %{topic}") % { :topic => topic } end end return helpstr @@ -1131,7 +1161,11 @@ class Bot secs_up = Time.new - @startup_time uptime = Utils.secs_to_string secs_up # return "Uptime #{uptime}, #{@plugins.length} plugins active, #{@registry.length} items stored in registry, #{@socket.lines_sent} lines sent, #{@socket.lines_received} received." - return "Uptime #{uptime}, #{@plugins.length} plugins active, #{@socket.lines_sent} lines sent, #{@socket.lines_received} received." + return (_("Uptime %{up}, %{plug} plugins active, %{sent} lines sent, %{recv} received.") % + { + :up => uptime, :plug => @plugins.length, + :sent => @socket.lines_sent, :recv => @socket.lines_received + }) end # We want to respond to a hung server in a timely manner. If nothing was received