class Irc::Bot

Main bot class, which manages the various components, receives messages, handles them or passes them to plugins, and contains core functionality.

Constants

SOURCE_URL

Attributes

agent[RW]

mechanize agent factory

auth[R]

the bot’s Auth data

botclass[R]

the botclass for this bot (determines configdir among other things)

config[R]

the bot’s Config data

httputil[RW]

bot’s httputil helper object, for fetching resources via http. Sets up proxies etc as defined by the bot configuration/environment

lang[R]

bot’s Language data

plugins[R]

bot’s plugins. This is an instance of class Plugins

registry_factory[RW]

loads and opens new registry databases, used by the plugins

save_mutex[R]

synchronize with this mutex while touching permanent data files: saving, flushing, cleaning up …

socket[R]

bot’s irc socket TODO multiserver

timer[R]

used to perform actions periodically (saves configuration once per minute by default)

webservice[RW]

web service

Public Class Methods

new(botclass, params = {}) click to toggle source

create a new Bot with botclass botclass

# File lib/rbot/ircbot.rb, line 166
def initialize(botclass, params = {})
  # Config for the core bot
  # TODO should we split socket stuff into ircsocket, etc?
  Config.register Config::ArrayValue.new('server.list',
    :default => ['irc://localhost'], :wizard => true,
    :requires_restart => true,
    :desc => "List of irc servers rbot should try to connect to. Use comma to separate values. Servers are in format 'server.doma.in:port'. If port is not specified, default value (6667) is used.")
  Config.register Config::BooleanValue.new('server.ssl',
    :default => false, :requires_restart => true, :wizard => true,
    :desc => "Use SSL to connect to this server?")
  Config.register Config::BooleanValue.new('server.ssl_verify',
    :default => false, :requires_restart => true,
    :desc => "Verify the SSL connection?",
    :wizard => true)
  Config.register Config::StringValue.new('server.ssl_cert',
    :requires_restart => true,
    :desc => "The cert file used to authenticate to the server.",
    :wizard => true)
  Config.register Config::StringValue.new('server.ssl_ca_file',
    :default => default_ssl_ca_file, :requires_restart => true,
    :desc => "The CA file used to verify the SSL connection.",
    :wizard => true)
  Config.register Config::StringValue.new('server.ssl_ca_path',
    :default => default_ssl_ca_path, :requires_restart => true,
    :desc => "Alternatively a directory that includes CA PEM files used to verify the SSL connection.",
    :wizard => true)
  Config.register Config::StringValue.new('server.password',
    :default => false, :requires_restart => true,
    :desc => "Password for connecting to this server (if required)",
    :wizard => true)
  Config.register Config::StringValue.new('server.bindhost',
    :default => false, :requires_restart => true,
    :desc => "Specific local host or IP for the bot to bind to (if required)",
    :wizard => true)
  Config.register Config::IntegerValue.new('server.reconnect_wait',
    :default => 5, :validate => Proc.new{|v| v >= 0},
    :desc => "Seconds to wait before attempting to reconnect, on disconnect")
  Config.register Config::IntegerValue.new('server.ping_timeout',
    :default => 30, :validate => Proc.new{|v| v >= 0},
    :desc => "reconnect if server doesn't respond to PING within this many seconds (set to 0 to disable)")
  Config.register Config::ArrayValue.new('server.nocolor_modes',
    :default => ['c'], :wizard => false,
    :requires_restart => false,
    :desc => "List of channel modes that require messages to be without colors")

  Config.register Config::StringValue.new('irc.nick', :default => "rbot",
    :desc => "IRC nickname the bot should attempt to use", :wizard => true,
    :on_change => Proc.new{|bot, v| bot.sendq "NICK #{v}" })
  Config.register Config::StringValue.new('irc.name',
    :default => "Ruby bot", :requires_restart => true,
    :desc => "IRC realname the bot should use")
  Config.register Config::BooleanValue.new('irc.name_copyright',
    :default => true, :requires_restart => true,
    :desc => "Append copyright notice to bot realname? (please don't disable unless it's really necessary)")
  Config.register Config::StringValue.new('irc.user', :default => "rbot",
    :requires_restart => true,
    :desc => "local user the bot should appear to be", :wizard => true)
  Config.register Config::ArrayValue.new('irc.join_channels',
    :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'")
  Config.register Config::ArrayValue.new('irc.ignore_users',
    :default => [],
    :desc => "Which users to ignore input from. This is mainly to avoid bot-wars triggered by creative people")
  Config.register Config::ArrayValue.new('irc.ignore_channels',
    :default => [],
    :desc => "Which channels to ignore input in. This is mainly to turn the bot into a logbot that doesn't interact with users in any way (in the specified channels)")

  Config.register Config::IntegerValue.new('core.save_every',
    :default => 60, :validate => Proc.new{|v| v >= 0},
    :on_change => Proc.new { |bot, v|
      if @save_timer
        if v > 0
          @timer.reschedule(@save_timer, v)
          @timer.unblock(@save_timer)
        else
          @timer.block(@save_timer)
        end
      else
        if v > 0
          @save_timer = @timer.add(v) { bot.save }
        end
        # Nothing to do when v == 0
      end
    },
    :desc => "How often the bot should persist all configuration to disk (in case of a server crash, for example)")

  Config.register Config::BooleanValue.new('core.run_as_daemon',
    :default => false, :requires_restart => true,
    :desc => "Should the bot run as a daemon?")

  Config.register Config::StringValue.new('log.file',
    :default => false, :requires_restart => true,
    :desc => "Name of the logfile to which console messages will be redirected when the bot is run as a daemon")
  Config.register Config::IntegerValue.new('log.level',
    :default => 1, :requires_restart => false,
    :validate => Proc.new { |v| (0..5).include?(v) },
    :on_change => Proc.new { |bot, v|
      LoggerManager.instance.set_level(v)
    },
    :desc => "The minimum logging level (0=DEBUG,1=INFO,2=WARN,3=ERROR,4=FATAL) for console messages")
  Config.register Config::IntegerValue.new('log.keep',
    :default => 1, :requires_restart => true,
    :validate => Proc.new { |v| v >= 0 },
    :desc => "How many old console messages logfiles to keep")
  Config.register Config::IntegerValue.new('log.max_size',
    :default => 10, :requires_restart => true,
    :validate => Proc.new { |v| v > 0 },
    :desc => "Maximum console messages logfile size (in megabytes)")

  Config.register Config::ArrayValue.new('plugins.path',
    :wizard => true, :default => ['(default)', '(default)/games', '(default)/contrib'],
    :requires_restart => false,
    :on_change => Proc.new { |bot, v| bot.setup_plugins_path },
    :desc => "Where the bot should look for plugins. List multiple directories using commas to separate. Use '(default)' for default prepackaged plugins collection, '(default)/contrib' for prepackaged unsupported plugins collection")

  Config.register Config::EnumValue.new('send.newlines',
    :values => ['split', 'join'], :default => 'split',
    :on_change => Proc.new { |bot, v|
      bot.set_default_send_options :newlines => v.to_sym
    },
    :desc => "When set to split, messages with embedded newlines will be sent as separate lines. When set to join, newlines will be replaced by the value of join_with")
  Config.register Config::StringValue.new('send.join_with',
    :default => ' ',
    :on_change => Proc.new { |bot, v|
      bot.set_default_send_options :join_with => v.dup
    },
    :desc => "String used to replace newlines when send.newlines is set to join")
  Config.register Config::IntegerValue.new('send.max_lines',
    :default => 5,
    :validate => Proc.new { |v| v >= 0 },
    :on_change => Proc.new { |bot, v|
      bot.set_default_send_options :max_lines => v
    },
    :desc => "Maximum number of IRC lines to send for each message (set to 0 for no limit)")
  Config.register Config::EnumValue.new('send.overlong',
    :values => ['split', 'truncate'], :default => 'split',
    :on_change => Proc.new { |bot, v|
      bot.set_default_send_options :overlong => v.to_sym
    },
    :desc => "When set to split, messages which are too long to fit in a single IRC line are split into multiple lines. When set to truncate, long messages are truncated to fit the IRC line length")
  Config.register Config::StringValue.new('send.split_at',
    :default => '\s+',
    :on_change => Proc.new { |bot, v|
      bot.set_default_send_options :split_at => Regexp.new(v)
    },
    :desc => "A regular expression that should match the split points for overlong messages (see send.overlong)")
  Config.register Config::BooleanValue.new('send.purge_split',
    :default => true,
    :on_change => Proc.new { |bot, v|
      bot.set_default_send_options :purge_split => v
    },
    :desc => "Set to true if the splitting boundary (set in send.split_at) should be removed when splitting overlong messages (see send.overlong)")
  Config.register Config::StringValue.new('send.truncate_text',
    :default => "#{Reverse}...#{Reverse}",
    :on_change => Proc.new { |bot, v|
      bot.set_default_send_options :truncate_text => v.dup
    },
    :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")
  Config.register Config::IntegerValue.new('send.penalty_pct',
    :default => 100,
    :validate => Proc.new { |v| v >= 0 },
    :on_change => Proc.new { |bot, v|
      bot.socket.penalty_pct = v
    },
    :desc => "Percentage of IRC penalty to consider when sending messages to prevent being disconnected for excess flood. Set to 0 to disable penalty control.")
  Config.register Config::StringValue.new('core.db',
    :default => default_db, :store_default => true,
    :wizard => true,
    :validate => Proc.new { |v| Registry::formats.include? v },
    :requires_restart => true,
    :desc => "DB adaptor to use for storing the plugin data/registries. Options: " + Registry::formats.join(', '))

  @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?"
    exit 2
  end

  unless FileTest.directory? Config::datadir
    error "data directory '#{Config::datadir}' not found, did you setup.rb?"
    exit 2
  end

  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 = File.join(botclass, ".rbot")
  end
  botclass = File.expand_path(botclass)
  @botclass = botclass.gsub(/\/$/, "")

  repopulate_botclass_directory

  # Time at which the last PING was sent
  @last_ping = nil
  # Time at which the last line was RECV'd from the server
  @last_rec = nil

  @startup_time = Time.new

  begin
    @config = Config.manager
    @config.bot_associate(self)
  rescue Exception => e
    fatal e
    exit 2
  end

  if @config['core.run_as_daemon']
    $daemonize = true
  end

  @registry_factory = Registry.new @config['core.db']
  @registry_factory.migrate_registry_folder(path)

  @logfile = @config['log.file']
  if @logfile.class != String || @logfile.empty?
    logfname =  File.basename(botclass).gsub(/^\.+/,'')
    logfname << ".log"
    @logfile = File.join(botclass, logfname)
    debug "Using `#{@logfile}' as debug log"
  end

  # setup logger based on bot configuration, if not set from the command line
  loglevel_set = $opts.has_key?('debug') or $opts.has_key?('loglevel')
  LoggerManager.instance.set_level(@config['log.level']) unless loglevel_set

  # Set the logfile
  LoggerManager.instance.set_logfile(@logfile, @config['log.keep'], @config['log.max_size'])

  if $daemonize
    log "Redirecting standard input/output/error, console logger disabled"
    LoggerManager.instance.flush
    LoggerManager.instance.disable_console_logger

    [$stdin, $stdout, $stderr].each do |fd|
      begin
        fd.reopen "/dev/null"
      rescue Errno::ENOENT
        # On Windows, there's not such thing as /dev/null
        fd.reopen "NUL"
      end
    end

    def $stdout.write(*args)
      str = args.map { |s| s.to_s }.join("")
      log str, 2
      return str.bytesize
    end
    def $stderr.write(*args)
      str = args.map { |s| s.to_s }.join("")
      if str.to_s.match(/:\d+: warning:/)
        warning str, 2
      else
        error str, 2
      end
      return str.bytesize
    end

    # See http://blog.humlab.umu.se/samuel/archives/000107.html
    # for the backgrounding code
    begin
      exit if fork
      Process.setsid
      exit if fork
    rescue NotImplementedError
      warning "Could not background, fork not supported"
    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.
  end

  LoggerManager.instance.log_session_start

  File.open($opts['pidfile'] || File.join(@botclass, 'rbot.pid'), 'w') do |pf|
    pf << "#{$$}\n"
  end

  @timer = Timer.new
  @save_mutex = Mutex.new
  if @config['core.save_every'] > 0
    @save_timer = @timer.add(@config['core.save_every']) { save }
  else
    @save_timer = nil
  end
  @quit_mutex = Mutex.new

  @plugins = nil
  @lang = Language.new(self, @config['core.language'])
  @httputil = Utils::HttpUtil.new(self)

  begin
    @auth = Auth::manager
    @auth.bot_associate(self)
    # @auth.load("#{botclass}/botusers.yaml")
  rescue Exception => e
    fatal e
    exit 2
  end
  @auth.everyone.set_default_permission("*", true)
  @auth.botowner.password= @config['auth.password']

  @plugins = Plugins::manager
  @plugins.bot_associate(self)
  setup_plugins_path()

  if @config['server.name']
      debug "upgrading configuration (server.name => server.list)"
      srv_uri = 'irc://' + @config['server.name']
      srv_uri += ":#{@config['server.port']}" if @config['server.port']
      @config.items['server.list'.to_sym].set_string(srv_uri)
      @config.delete('server.name'.to_sym)
      @config.delete('server.port'.to_sym)
      debug "server.list is now #{@config['server.list'].inspect}"
  end

  @socket = Irc::Socket.new(@config['server.list'], @config['server.bindhost'], 
                            :ssl => @config['server.ssl'],
                            :ssl_verify => @config['server.ssl_verify'],
                            :ssl_cert => @config['server.ssl_cert'],
                            :ssl_ca_file => @config['server.ssl_ca_file'],
                            :ssl_ca_path => @config['server.ssl_ca_path'],
                            :penalty_pct => @config['send.penalty_pct'])
  @client = Client.new

  @plugins.scan

  # Channels where we are quiet
  # Array of channels names where the bot should be quiet
  # '*' means all channels
  #
  @quiet = Set.new
  # but we always speak here
  @not_quiet = Set.new

  # the nick we want, if it's different from the irc.nick config value
  # (e.g. as set by a !nick command)
  @wanted_nick = nil

  @client[:welcome] = proc {|data|
    m = WelcomeMessage.new(self, server, data[:source], data[:target], data[:message])

    @plugins.delegate("welcome", m)
    @plugins.delegate("connect")
  }

  # TODO the next two @client should go into rfc2812.rb, probably
  # Since capabs are two-steps processes, server.supports[:capab]
  # should be a three-state: nil, [], [....]
  asked_for = { :"identify-msg" => false }
  @client[:isupport] = proc { |data|
    if server.supports[:capab] and !asked_for[:"identify-msg"]
      sendq "CAPAB IDENTIFY-MSG"
      asked_for[:"identify-msg"] = true
    end
  }
  @client[:datastr] = proc { |data|
    if data[:text] == "IDENTIFY-MSG"
      server.capabilities[:"identify-msg"] = true
    else
      debug "Not handling RPL_DATASTR #{data[:servermessage]}"
    end
  }

  @client[:privmsg] = proc { |data|
    m = PrivMessage.new(self, server, data[:source], data[:target], data[:message], :handle_id => true)
    # debug "Message source is #{data[:source].inspect}"
    # debug "Message target is #{data[:target].inspect}"
    # debug "Bot is #{myself.inspect}"

    @config['irc.ignore_channels'].each { |channel|
      if m.target.downcase == channel.downcase
        m.ignored = true
        break
      end
    }
    @config['irc.ignore_users'].each { |mask|
      if m.source.matches?(server.new_netmask(mask))
        m.ignored = true
        break
      end
    } unless m.ignored

    @plugins.irc_delegate('privmsg', m)
  }
  @client[:notice] = proc { |data|
    message = NoticeMessage.new(self, server, data[:source], data[:target], data[:message], :handle_id => true)
    # pass it off to plugins that want to hear everything
    @plugins.irc_delegate "notice", message
  }
  @client[:motd] = proc { |data|
    m = MotdMessage.new(self, server, data[:source], data[:target], data[:motd])
    @plugins.delegate "motd", m
  }
  @client[:nicktaken] = proc { |data|
    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
    # to inform us that our nick has been changed.
    if data[:target] == '*'
      debug "setting my connection nick to #{new}"
      @client.user.nick = new
    end
    @plugins.delegate "nicktaken", data[:nick]
  }
  @client[:badnick] = proc {|data|
    warning "bad nick (#{data[:nick]})"
  }
  @client[:ping] = proc {|data|
    sendq "PONG #{data[:pingid]}"
  }
  @client[:pong] = proc {|data|
    @last_ping = nil
  }
  @client[:nick] = proc {|data|
    # debug "Message source is #{data[:source].inspect}"
    # debug "Bot is #{myself.inspect}"
    source = data[:source]
    old = data[:oldnick]
    new = data[:newnick]
    m = NickMessage.new(self, server, source, old, new)
    m.is_on = data[:is_on]
    if source == myself
      debug "my nick is now #{new}"
    end
    @plugins.irc_delegate("nick", m)
  }
  @client[:quit] = proc {|data|
    source = data[:source]
    message = data[:message]
    m = QuitMessage.new(self, server, source, source, message)
    m.was_on = data[:was_on]
    @plugins.irc_delegate("quit", m)
  }
  @client[:mode] = proc {|data|
    m = ModeChangeMessage.new(self, server, data[:source], data[:target], data[:modestring])
    m.modes = data[:modes]
    @plugins.delegate "modechange", m
  }
  @client[:whois] = proc {|data|
    source = data[:source]
    target = server.get_user(data[:whois][:nick])
    m = WhoisMessage.new(self, server, source, target, data[:whois])
    @plugins.delegate "whois", m
  }
  @client[:list] = proc {|data|
    source = data[:source]
    m = ListMessage.new(self, server, source, source, data[:list])
    @plugins.delegate "irclist", m
  }
  @client[:join] = proc {|data|
    m = JoinMessage.new(self, server, data[:source], data[:channel], data[:message])
    sendq("MODE #{data[:channel]}", nil, 0) if m.address?
    @plugins.irc_delegate("join", m)
    sendq("WHO #{data[:channel]}", data[:channel], 2) if m.address?
  }
  @client[:part] = proc {|data|
    m = PartMessage.new(self, server, data[:source], data[:channel], data[:message])
    @plugins.irc_delegate("part", m)
  }
  @client[:kick] = proc {|data|
    m = KickMessage.new(self, server, data[:source], data[:target], data[:channel],data[:message])
    @plugins.irc_delegate("kick", m)
  }
  @client[:invite] = proc {|data|
    m = InviteMessage.new(self, server, data[:source], data[:target], data[:channel])
    @plugins.irc_delegate("invite", m)
  }
  @client[:changetopic] = proc {|data|
    m = TopicMessage.new(self, server, data[:source], data[:channel], data[:topic])
    m.info_or_set = :set
    @plugins.irc_delegate("topic", m)
  }
  # @client[:topic] = proc { |data|
  #   irclog "@ Topic is \"#{data[:topic]}\"", data[:channel]
  # }
  @client[:topicinfo] = proc { |data|
    channel = data[:channel]
    topic = channel.topic
    m = TopicMessage.new(self, server, data[:source], channel, topic)
    m.info_or_set = :info
    @plugins.irc_delegate("topic", m)
  }
  @client[:names] = proc { |data|
    m = NamesMessage.new(self, server, server, data[:channel])
    m.users = data[:users]
    @plugins.delegate "names", m
  }
  @client[:banlist] = proc { |data|
    m = BanlistMessage.new(self, server, server, data[:channel])
    m.bans = data[:bans]
    @plugins.delegate "banlist", m
  }
  @client[:nosuchtarget] = proc { |data|
    m = NoSuchTargetMessage.new(self, server, server, data[:target], data[:message])
    @plugins.delegate "nosuchtarget", m
  }
  @client[:error] = proc { |data|
    raise ServerError, data[:message]
  }
  @client[:unknown] = proc { |data|
    #debug "UNKNOWN: #{data[:serverstring]}"
    m = UnknownMessage.new(self, server, server, nil, data[:serverstring])
    @plugins.delegate "unknown_message", m
  }

  set_default_send_options :newlines => @config['send.newlines'].to_sym,
    :join_with => @config['send.join_with'].dup,
    :max_lines => @config['send.max_lines'],
    :overlong => @config['send.overlong'].to_sym,
    :split_at => Regexp.new(@config['send.split_at']),
    :purge_split => @config['send.purge_split'],
    :truncate_text => @config['send.truncate_text'].dup

  trap_signals
end

Public Instance Methods

action(where, message, options={}) click to toggle source

perform a CTCP action with message message to channel/nick where

# File lib/rbot/ircbot.rb, line 1174
def action(where, message, options={})
  ctcp_say(where, 'ACTION', message, options)
end
channels() click to toggle source

bot channels in the client/server connection

# File lib/rbot/ircbot.rb, line 113
def channels
  myself.channels
end
clear_filters() click to toggle source

This method clears the filter list and installs the identity filter

# File lib/rbot/core/utils/filters.rb, line 142
def clear_filters
  @filters ||= {}
  @filters.clear

  @filter_group ||= {}
  @filter_group.clear

  register_filter(:identity) { |stream| stream }
end
connect() click to toggle source

connect the bot to IRC

# File lib/rbot/ircbot.rb, line 865
def connect
  # make sure we don't have any spurious ping checks running
  # (and initialize the vars if this is the first time we connect)
  stop_server_pings
  begin
    quit if $interrupted > 0
    @socket.connect
    @last_rec = Time.now
  rescue Exception => e
    uri = @socket.server_uri || '<unknown>'
    error "failed to connect to IRC server at #{uri}"
    error e
    raise
  end
  quit if $interrupted > 0

  realname = @config['irc.name'].clone || 'Ruby bot'
  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}"
  quit if $interrupted > 0
  myself.nick = @config['irc.nick']
  myself.user = @config['irc.user']
end
ctcp_notice(where, command, message, options={}) click to toggle source
# File lib/rbot/ircbot.rb, line 1163
def ctcp_notice(where, command, message, options={})
  return if where.kind_of?(Channel) and quiet_on?(where)
  sendmsg "NOTICE", where, "\001#{command} #{message}\001", options
end
ctcp_say(where, command, message, options={}) click to toggle source
# File lib/rbot/ircbot.rb, line 1168
def ctcp_say(where, command, message, options={})
  return if where.kind_of?(Channel) and quiet_on?(where)
  sendmsg "PRIVMSG", where, "\001#{command} #{message}\001", options
end
default_db() click to toggle source

Determine if tokyocabinet is installed, if it is use it as a default.

# File lib/rbot/ircbot.rb, line 719
def default_db
  begin
    require 'tokyocabinet'
    return 'tc'
  rescue LoadError
    require 'sqlite3'
    return 'sqlite'
  rescue LoadError
    return 'dbm'
  end
end
default_ssl_ca_file() click to toggle source

Determine (if possible) a valid path to a CA certificate bundle.

# File lib/rbot/ircbot.rb, line 703
def default_ssl_ca_file
  [ '/etc/ssl/certs/ca-certificates.crt', # Ubuntu/Debian
    '/etc/ssl/certs/ca-bundle.crt', # Amazon Linux
    '/etc/ssl/ca-bundle.pem', # OpenSUSE
    '/etc/pki/tls/certs/ca-bundle.crt' # Fedora/RHEL
  ].find do |file|
    File.readable? file
  end
end
default_ssl_ca_path() click to toggle source
# File lib/rbot/ircbot.rb, line 713
def default_ssl_ca_path
  file = default_ssl_ca_file
  File.dirname file if file
end
disconnect(message=nil) click to toggle source
# File lib/rbot/ircbot.rb, line 1194
def disconnect(message=nil)
  message = @lang.get("quit") if (!message || message.empty?)
  if @socket.connected?
    begin
      debug "Clearing socket"
      @socket.clearq
      debug "Sending quit message"
      @socket.emergency_puts "QUIT :#{message}"
      debug "Logging quits"
      delegate_sent('QUIT', myself, message)
      debug "Flushing socket"
      @socket.flush
    rescue SocketError => e
      error "error while disconnecting socket: #{e.pretty_inspect}"
    end
    debug "Shutting down socket"
    @socket.shutdown
  end
  stop_server_pings
  @client.reset
end
filter(filter1, filter2, ..., filterN, stream) → stream click to toggle source
filter(filter1, filter2, ..., filterN, text, hash) → stream
filter(filter1, filter2, ..., filterN, hash) → stream

This method processes the DataStream stream with the filters filter1, filter2, …, filterN, in sequence (the output of each filter is used as input for the next one. stream can be provided either as a DataStream or as a String and a Hash (see DataStream.new).

# File lib/rbot/core/utils/filters.rb, line 63
def filter(*args)
  @filters ||= {}
  if Hash === args.last
    # the stream is a Hash, check if the previous element is not a Symbol
    if Symbol === args[-2]
      ds = DataStream.new(args.pop)
    else
      ds = DataStream.new(*args.slice!(-2, 2))
    end
  else
    # the stream is just whatever else
    ds = DataStream.new(args.pop)
  end
  names = args.dup
  return ds if names.empty?
  # check if filters exist
  missing = names - @filters.keys
  raise "Missing filters: #{missing.join(', ')}" unless missing.empty?
  fs = @filters.values_at(*names)
  fs.inject(ds) { |mid, f| mid = f.call(mid) }
end
filter_groups() click to toggle source

This method is used to retrieve the filter group names

# File lib/rbot/core/utils/filters.rb, line 136
def filter_groups
  return [] unless defined? @filter_group
  return @filter_group.keys
end
filter_names(group=nil) click to toggle source

This method is used to retrieve the filter names (in a given group)

# File lib/rbot/core/utils/filters.rb, line 124
def filter_names(group=nil)
  if group
    gkey = group.to_sym
    return [] unless defined? @filter_group and @filter_group.key?(gkey)
    return @filter_group[gkey].keys
  else
    return [] unless defined? @filters
    return @filters.keys
  end
end
global_filter_name(name, group=nil) click to toggle source

This method returns the global filter name for filter name in group group

# File lib/rbot/core/utils/filters.rb, line 87
def global_filter_name(name, group=nil)
  (group ? "#{group}.#{name}" : name.to_s).intern
end
handle_signal(sig) click to toggle source

things to do when we receive a signal

# File lib/rbot/ircbot.rb, line 831
def handle_signal(sig)
  func = case sig
         when 'SIGHUP'
           :restart
         when 'SIGUSR1'
           :reconnect
         else
           :quit
         end
  debug "received #{sig}, queueing #{func}"
  # this is not an interruption if we just need to reconnect
  $interrupted += 1 unless func == :reconnect
  self.send(func) unless @quit_mutex.locked?
  debug "interrupted #{$interrupted} times"
  if $interrupted >= 3
    debug "drastic!"
    exit 2
  end
end
has_filter?(name, group=nil) click to toggle source

This method checks if the bot has a filter named name (in group group)

# File lib/rbot/core/utils/filters.rb, line 93
def has_filter?(name, group=nil)
  @filters.key?(global_filter_name(name, group))
end
has_filter_group?(name) click to toggle source

This method checks if the bot has a filter group named name

# File lib/rbot/core/utils/filters.rb, line 98
def has_filter_group?(name)
  @filter_group.key?(name)
end
help(topic=nil) click to toggle source
m

message asking for help

topic

optional topic help is requested for

respond to online help requests

# File lib/rbot/ircbot.rb, line 1349
def help(topic=nil)
  topic = nil if topic == ""
  case topic
  when nil
    helpstr = _("help topics: ")
    helpstr += @plugins.helptopics
    helpstr += _(" (help <topic> for more info)")
  else
    unless(helpstr = @plugins.help(topic))
      helpstr = _("no help for topic %{topic}") % { :topic => topic }
    end
  end
  return helpstr
end
inspect() click to toggle source

bot inspection TODO multiserver

# File lib/rbot/ircbot.rb, line 135
def inspect
  ret = self.to_s[0..-2]
  ret << ' version=' << $version.inspect
  ret << ' botclass=' << botclass.inspect
  ret << ' lang="' << lang.language
  if defined?(GetText)
    ret << '/' << locale
  end
  ret << '"'
  ret << ' nick=' << nick.inspect
  ret << ' server='
  if server
    ret << (server.to_s + (socket ?
      ' [' << socket.server_uri.to_s << ']' : '')).inspect
    unless server.channels.empty?
      ret << " channels="
      ret << server.channels.map { |c|
        "%s%s" % [c.modes_of(nick).map { |m|
          server.prefix_for_mode(m)
        }, c.name]
      }.inspect
    end
  else
    ret << '(none)'
  end
  ret << ' plugins=' << plugins.inspect
  ret << ">"
end
join(channel, key=nil) click to toggle source
channel

channel to join

key

optional channel key if channel is +s

join a channel

# File lib/rbot/ircbot.rb, line 1313
def join(channel, key=nil)
  if(key)
    sendq "JOIN #{channel} :#{key}", channel, 2
  else
    sendq "JOIN #{channel}", channel, 2
  end
end
journal() click to toggle source

this should return the journal if the managing plugin has been loaded.

# File lib/rbot/core/journal.rb, line 13
def journal
  if @plugins['journal']
    @plugins['journal'].broker
  end
end
kick(channel, user, msg) click to toggle source

kicking a user

# File lib/rbot/ircbot.rb, line 1342
def kick(channel, user, msg)
  sendq "KICK #{channel} #{user} :#{msg}", channel, 2
end
mainloop() click to toggle source

begin event handling loop

# File lib/rbot/ircbot.rb, line 930
def mainloop
  @keep_looping = true
  while @keep_looping
    too_fast = 0
    quit_msg = nil
    valid_recv = false # did we receive anything (valid) from the server yet?
    begin
      reconnect(quit_msg, too_fast)
      quit if $interrupted > 0
      valid_recv = false
      while @socket.connected?
        quit if $interrupted > 0

        # Wait for messages and process them as they arrive. If nothing is
        # received, we call the ping_server() method that will PING the
        # server if appropriate, or raise a Irc::PingError if no PONG has been
        # received in the user-chosen timeout since the last PING sent.
        if @socket.select(1)
          break unless reply = @socket.gets
          @last_rec = Time.now
          @client.process reply
          valid_recv = true
          too_fast = 0
        else
          ping_server
        end
      end

    # I despair of this. Some of my users get "connection reset by peer"
    # exceptions that AREN'T SocketError's. How am I supposed to handle
    # that?
    rescue SystemExit
      @keep_looping = false
      break
    rescue Errno::ETIMEDOUT, Errno::ECONNABORTED, Irc::PingError, SocketError => e
      error "network exception: #{e.pretty_inspect}"
      quit_msg = e.to_s
      too_fast += 10 if valid_recv
    rescue ServerMessageParseError => e
      # if the bot tried reconnecting too often, we can get forcefully
      # disconnected by the server, while still receiving an empty message
      # wait at least 10 minutes in this case
      if e.message.empty?
        oldtf = too_fast
        too_fast = [too_fast, 300].max
        too_fast *= 2
        log "Empty message from server, extra delay multiplier #{oldtf} -> #{too_fast}"
      end
      quit_msg = "Unparsable Server Message: #{e.message.inspect}"
      retry
    rescue ServerError => e
      quit_msg = "server ERROR: #{e.message}"
      debug quit_msg
      idx = e.message.index('connect too fast')
      debug "'connect too fast' @ #{idx}"
      if idx
        oldtf = too_fast
        too_fast += (idx + 1) * 2
        log "Reconnecting too fast, extra delay multiplier #{oldtf} -> #{too_fast}"
      end
      idx = e.message.index(/a(uto)kill/i)
      debug "'autokill' @ #{idx}"
      if idx
        # we got auto-killed. since we don't have an easy way to tell
        # if it's permanent or temporary, we just set a rather high
        # reconnection timeout
        oldtf = too_fast
        too_fast += (idx + 1) * 5
        log "Killed by server, extra delay multiplier #{oldtf} -> #{too_fast}"
      end
      retry
    rescue Exception => e
      error "non-net exception: #{e.pretty_inspect}"
      quit_msg = e.to_s
    end
  end
end
mode(channel, mode, target=nil) click to toggle source

changing mode

# File lib/rbot/ircbot.rb, line 1332
def mode(channel, mode, target=nil)
  sendq "MODE #{channel} #{mode} #{target}", channel, 2
end
myself() click to toggle source

bot User in the client/server connection TODO multiserver

# File lib/rbot/ircbot.rb, line 103
def myself
  @client.user
end
nick() click to toggle source

bot nick in the client/server connection

# File lib/rbot/ircbot.rb, line 108
def nick
  myself.nick
end
nickchg(name) click to toggle source

attempt to change bot’s nick to name

# File lib/rbot/ircbot.rb, line 1327
def nickchg(name)
  sendq "NICK #{name}"
end
notice(where, message, options={}) click to toggle source

send a notice message to channel/nick where

# File lib/rbot/ircbot.rb, line 1152
def notice(where, message, options={})
  return if where.kind_of?(Channel) and quiet_on?(where)
  sendmsg "NOTICE", where, message, options
end
okay(where) click to toggle source

quick way to say “okay” (or equivalent) to where

# File lib/rbot/ircbot.rb, line 1179
def okay(where)
  say where, @lang.get("okay")
end
part(channel, message="") click to toggle source

part a channel

# File lib/rbot/ircbot.rb, line 1322
def part(channel, message="")
  sendq "PART #{channel} :#{message}", channel, 2
end
path(*components) click to toggle source

Return a path under the current botclass by joining the mentioned components. The components are automatically converted to String

# File lib/rbot/ircbot.rb, line 758
def path(*components)
  File.join(@botclass, *(components.map {|c| c.to_s}))
end
ping_server() click to toggle source

We want to respond to a hung server in a timely manner. If nothing was received in the user-selected timeout and we haven’t PINGed the server yet, we PING the server. If the PONG is not received within the user-defined timeout, we assume we’re in ping timeout and act accordingly.

# File lib/rbot/ircbot.rb, line 1380
def ping_server
  act_timeout = @config['server.ping_timeout']
  return if act_timeout <= 0
  now = Time.now
  if @last_rec && now > @last_rec + act_timeout
    if @last_ping.nil?
      # No previous PING pending, send a new one
      sendq "PING :rbot"
      @last_ping = Time.now
    else
      diff = now - @last_ping
      if diff > act_timeout
        debug "no PONG from server in #{diff} seconds, reconnecting"
        # the actual reconnect is handled in the main loop:
        raise Irc::PingError, "no PONG from server in #{diff} seconds"
      end
    end
  end
end
quiet_on?(channel) click to toggle source

checks if we should be quiet on a channel

# File lib/rbot/ircbot.rb, line 802
def quiet_on?(channel)
  ch = channel.downcase
  return (@quiet.include?('*') && !@not_quiet.include?(ch)) || @quiet.include?(ch)
end
quit(message=nil) click to toggle source
message

optional IRC quit message

quit IRC, shutdown the bot

# File lib/rbot/ircbot.rb, line 1253
def quit(message=nil)
  begin
    shutdown(message)
  ensure
    @keep_looping = false
  end
end
reconnect(message=nil, too_fast=0) click to toggle source

disconnect the bot from IRC, if connected, and then connect (again)

# File lib/rbot/ircbot.rb, line 892
def reconnect(message=nil, too_fast=0)
  # we will wait only if @last_rec was not nil, i.e. if we were connected or
  # got disconnected by a network error
  # if someone wants to manually call disconnect() _and_ reconnect(), they
  # will have to take care of the waiting themselves
  will_wait = !!@last_rec

  if @socket.connected?
    disconnect(message)
  end

  begin
    if will_wait
      log "\n\nDisconnected\n\n"

      quit if $interrupted > 0

      log "\n\nWaiting to reconnect\n\n"
      sleep @config['server.reconnect_wait']
      if too_fast > 0
        tf = too_fast*@config['server.reconnect_wait']
        tfu = Utils.secs_to_string(tf)
        log "Will sleep for an extra #{tf}s (#{tfu})"
        sleep tf
      end
    end

    connect
  rescue SystemExit
    exit 0
  rescue Exception => e
    error e
    will_wait = true
    retry
  end
end
register_filter(name, group=nil, &block) click to toggle source

This method is used to register a new filter

# File lib/rbot/core/utils/filters.rb, line 103
def register_filter(name, group=nil, &block)
  raise "No block provided" unless block_given?
  @filters ||= {}
  tlkey = global_filter_name(name, group)
  key = name.to_sym
  if has_filter?(tlkey)
    debug "Overwriting filter #{tlkey}"
  end
  @filters[tlkey] = DataFilter.new(&block)
  if group
    gkey = group.to_sym
    @filter_group ||= {}
    @filter_group[gkey] ||= {}
    if @filter_group[gkey].key?(key)
      debug "Overwriting filter #{key} in group #{gkey}"
    end
    @filter_group[gkey][key] = @filters[tlkey]
  end
end
repopulate_botclass_directory() click to toggle source
# File lib/rbot/ircbot.rb, line 731
def repopulate_botclass_directory
  template_dir = File.join Config::datadir, 'templates'
  if FileTest.directory? @botclass
    # compare the templates dir with the current botclass dir, filling up the
    # latter with any missing file. Sadly, FileUtils.cp_r doesn't have an
    # :update option, so we have to do it manually.
    # Note that we use the */** pattern because we don't want to match
    # keywords.rbot, which gets deleted on load and would therefore be missing
    # always
    missing = Dir.chdir(template_dir) { Dir.glob('*/**') } - Dir.chdir(@botclass) { Dir.glob('*/**') }
    missing.map do |f|
      dest = File.join(@botclass, f)
      FileUtils.mkdir_p(File.dirname(dest))
      FileUtils.cp File.join(template_dir, f), dest
    end
  else
    log "no #{@botclass} directory found, creating from templates..."
    if FileTest.exist? @botclass
      error "file #{@botclass} exists but isn't a directory"
      exit 2
    end
    FileUtils.cp_r template_dir, @botclass
  end
end
rescan(botmodule=nil) click to toggle source

call the rescan method for all or just the specified botmodule

:botmodule

instance of the botmodule to rescan

# File lib/rbot/ircbot.rb, line 1300
def rescan(botmodule=nil)
  debug "\tstopping timer..."
  @timer.stop
  @save_mutex.synchronize do
    # @lang.rescan
    @plugins.rescan(botmodule)
  end
  @timer.start
end
reset_quiet(channel = nil) click to toggle source
# File lib/rbot/ircbot.rb, line 819
def reset_quiet(channel = nil)
  if channel
    ch = channel.downcase.dup
    @quiet.delete(ch)
    @not_quiet << ch
  else
    @quiet.clear
    @not_quiet.clear
  end
end
restart(message=nil) click to toggle source

totally shutdown and respawn the bot

# File lib/rbot/ircbot.rb, line 1262
def restart(message=nil)
  message = _("restarting, back in %{wait}...") % {
    :wait => @config['server.reconnect_wait']
  } if (!message || message.empty?)
  shutdown(message)

  Irc::Bot::LoggerManager.instance.flush
  Irc::Bot::LoggerManager.instance.log_session_end

  sleep @config['server.reconnect_wait']
  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
save(botmodule=nil) click to toggle source

call the save method for all or the specified botmodule

:botmodule

optional botmodule to save

# File lib/rbot/ircbot.rb, line 1290
def save(botmodule=nil)
  @save_mutex.synchronize do
    @plugins.save(botmodule)
  end
end
say(where, message, options={}) click to toggle source

say something (PRIVMSG) to channel/nick where

# File lib/rbot/ircbot.rb, line 1158
def say(where, message, options={})
  return if where.kind_of?(Channel) and quiet_on?(where)
  sendmsg "PRIVMSG", where, message, options
end
sendmsg(original_type, original_where, original_message, options = {}) click to toggle source
type

message type

where

message target

message

message text

send message message of type type to target where 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.

# File lib/rbot/ircbot.rb, line 1015
def sendmsg(original_type, original_where, original_message, options = {})

  # filter message with sendmsg filters
  ds = DataStream.new original_message.to_s.dup,
    :type => original_type, :dest => original_where,
    :options => @default_send_options.merge(options)
  filters = filter_names(:sendmsg)
  filters.each do |fname|
    debug "filtering #{ds[:text]} with sendmsg filter #{fname}"
    ds.merge! filter(self.global_filter_name(fname, :sendmsg), ds)
  end

  opts = ds[:options]
  type = ds[:type]
  where = ds[:dest]
  filtered = ds[:text]

  if defined? WebServiceUser and where.instance_of? WebServiceUser
    debug 'sendmsg to web service!'
    where.response << filtered
    return
  end

  # For starters, set up appropriate queue channels and rings
  mchan = opts[:queue_channel]
  mring = opts[:queue_ring]
  if mchan
    chan = mchan
  else
    chan = where
  end
  if mring
    ring = mring
  else
    case where
    when User
      ring = 1
    else
      ring = 2
    end
  end

  multi_line = filtered.gsub(/[\r\n]+/, "\n")

  # if target is a channel with nocolor modes, strip colours
  if where.kind_of?(Channel) and where.mode.any?(*config['server.nocolor_modes'])
    multi_line.replace BasicUserMessage.strip_formatting(multi_line)
  end

  messages = Array.new
  case opts[:newlines]
  when :join
    messages << [multi_line.gsub("\n", opts[:join_with])]
  when :split
    multi_line.each_line do |line|
      line.chomp!
      next unless(line.size > 0)
      messages << line
    end
  else
    raise "Unknown :newlines option #{opts[:newlines]} while sending #{original_message.inspect}"
  end

  # The IRC protocol requires that each raw message must be not longer
  # than 512 characters. From this length with have to subtract the EOL
  # terminators (CR+LF) and the length of ":botnick!botuser@bothost "
  # that will be prepended by the server to all of our messages.

  # The maximum raw message length we can send is therefore 512 - 2 - 2
  # minus the length of our hostmask.

  max_len = 508 - myself.fullform.size

  # On servers that support IDENTIFY-MSG, we have to subtract 1, because messages
  # will have a + or - prepended
  if server.capabilities[:"identify-msg"]
    max_len -= 1
  end

  # When splitting the message, we'll be prefixing the following string:
  # (e.g. "PRIVMSG #rbot :")
  fixed = "#{type} #{where} :"

  # And this is what's left
  left = max_len - fixed.size

  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
    lines = all_lines
  end

  lines.each { |line|
    sendq "#{fixed}#{line}", chan, ring
    delegate_sent(type, where, line)
  }
end
sendq(message="", chan=nil, ring=0) click to toggle source

queue an arbitrary message for the server

# File lib/rbot/ircbot.rb, line 1146
def sendq(message="", chan=nil, ring=0)
  # temporary
  @socket.queue(message, chan, ring)
end
server() click to toggle source

server we are connected to TODO multiserver

# File lib/rbot/ircbot.rb, line 97
def server
  @client.server
end
set_default_send_options(opts={}) click to toggle source
# File lib/rbot/ircbot.rb, line 782
def set_default_send_options(opts={})
  # Default send options for NOTICE and PRIVMSG
  unless defined? @default_send_options
    @default_send_options = {
      :queue_channel => nil,      # use default queue channel
      :queue_ring => nil,         # use default queue ring
      :newlines => :split,        # or :join
      :join_with => ' ',          # by default, use a single space
      :max_lines => 0,          # maximum number of lines to send with a single command
      :overlong => :split,        # or :truncate
      # TODO an array of splitpoints would be preferable for this option:
      :split_at => /\s+/,         # by default, split overlong lines at whitespace
      :purge_split => true,       # should the split string be removed?
      :truncate_text => "#{Reverse}...#{Reverse}"  # text to be appended when truncating
    }
  end
  @default_send_options.update opts unless opts.empty?
end
set_quiet(channel = nil) click to toggle source
# File lib/rbot/ircbot.rb, line 807
def set_quiet(channel = nil)
  if channel
    ch = channel.downcase.dup
    @not_quiet.delete(ch)
    @quiet << ch
  else
    @quiet.clear
    @not_quiet.clear
    @quiet << '*'
  end
end
setup_plugins_path() click to toggle source
# File lib/rbot/ircbot.rb, line 762
def setup_plugins_path
  plugdir_default = File.join(Config::datadir, 'plugins')
  plugdir_local = File.join(@botclass, 'plugins')
  Dir.mkdir(plugdir_local) unless File.exist?(plugdir_local)

  @plugins.clear_botmodule_dirs
  @plugins.add_core_module_dir(File.join(Config::coredir, 'utils'))
  @plugins.add_core_module_dir(Config::coredir)
  if FileTest.directory? plugdir_local
    @plugins.add_plugin_dir(plugdir_local)
  else
    warning "local plugin location #{plugdir_local} is not a directory"
  end

  @config['plugins.path'].each do |_|
      path = _.sub(/^\(default\)/, plugdir_default)
      @plugins.add_plugin_dir(path)
  end
end
shutdown(message=nil) click to toggle source

disconnect from the server and cleanup all plugins and modules

# File lib/rbot/ircbot.rb, line 1217
def shutdown(message=nil)
  @quit_mutex.synchronize do
    debug "Shutting down: #{message}"
    ## No we don't restore them ... let everything run through
    # begin
    #   trap("SIGINT", "DEFAULT")
    #   trap("SIGTERM", "DEFAULT")
    #   trap("SIGHUP", "DEFAULT")
    # rescue => e
    #   debug "failed to restore signals: #{e.inspect}\nProbably running on windows?"
    # end
    debug "\tdisconnecting..."
    disconnect(message)
    debug "\tstopping timer..."
    @timer.stop
    debug "\tsaving ..."
    save
    debug "\tcleaning up ..."
    @save_mutex.synchronize do
      begin
        @plugins.cleanup
      rescue
        debug "\tignoring cleanup error: #{$!}"
      end
    end
    @httputil.cleanup
    # debug "\tstopping timers ..."
    # @timer.stop
    # debug "Closing registries"
    # @registry.close
    log "rbot quit (#{message})"
  end
end
status() click to toggle source

returns a string describing the current status of the bot (uptime etc)

# File lib/rbot/ircbot.rb, line 1365
def status
  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 %{up}, %{plug} plugins active, %{sent} lines sent, %{recv} received.") %
           {
             :up => uptime, :plug => @plugins.length,
             :sent => @socket.lines_sent, :recv => @socket.lines_received
           })
end
stop_server_pings() click to toggle source
# File lib/rbot/ircbot.rb, line 1400
def stop_server_pings
  # cancel previous PINGs and reset time of last RECV
  @last_ping = nil
  @last_rec = nil
end
topic(where, topic=nil) click to toggle source

set topic of channel where to topic can also be used to retrieve the topic of channel where by omitting the last argument

# File lib/rbot/ircbot.rb, line 1186
def topic(where, topic=nil)
  if topic.nil?
    sendq "TOPIC #{where}", where, 2
  else
    sendq "TOPIC #{where} :#{topic}", where, 2
  end
end
trap_signals() click to toggle source

trap signals

# File lib/rbot/ircbot.rb, line 852
def trap_signals
  begin
    %w(SIGINT SIGTERM SIGHUP SIGUSR1).each do |sig|
      trap(sig) { Thread.new { handle_signal sig } }
    end
  rescue ArgumentError => e
    debug "failed to trap signals (#{e.pretty_inspect}): running on Windows?"
  rescue Exception => e
    debug "failed to trap signals: #{e.pretty_inspect}"
  end
end
wanted_nick() click to toggle source

nick wanted by the bot. This defaults to the irc.nick config value, but may be overridden by a manual !nick command

# File lib/rbot/ircbot.rb, line 119
def wanted_nick
  @wanted_nick || config['irc.nick']
end
wanted_nick=(wn) click to toggle source

set the nick wanted by the bot

# File lib/rbot/ircbot.rb, line 124
def wanted_nick=(wn)
  if wn.nil? or wn.to_s.downcase == config['irc.nick'].downcase
    @wanted_nick = nil
  else
    @wanted_nick = wn.to_s.dup
  end
end
web_dispatcher() click to toggle source

Static web dispatcher instance used internally.

# File lib/rbot/core/webservice.rb, line 304
def web_dispatcher
  if defined? @web_dispatcher
    @web_dispatcher
  else
    @web_dispatcher = WebDispatcher.new(self)
  end
end
whois(nick, target=nil) click to toggle source

asking whois

# File lib/rbot/ircbot.rb, line 1337
def whois(nick, target=nil)
  sendq "WHOIS #{target} #{nick}", nil, 0
end

Private Instance Methods

delegate_sent(type, where, message) click to toggle source

delegate sent messages

# File lib/rbot/ircbot.rb, line 1409
def delegate_sent(type, where, message)
  args = [self, server, myself, server.user_or_channel(where.to_s), message]
  case type
    when "NOTICE"
      m = NoticeMessage.new(*args)
    when "PRIVMSG"
      m = PrivMessage.new(*args)
    when "QUIT"
      m = QuitMessage.new(*args)
      m.was_on = myself.channels
  end
  @plugins.delegate('sent', m)
end