module Rumpy::Bot

include this module into your bot’s class

Attributes

pid_file[R]

Public Instance Methods

log_file=(logfile) click to toggle source

if @log_file isn’t set, initialize it

# File lib/rumpy/bot.rb, line 14
def log_file=(logfile)
  @log_file ||= logfile
end
start() click to toggle source

one and only public function, defined in this module simply initializes bot’s variables, connection, etc. and starts bot

# File lib/rumpy/bot.rb, line 21
def start
  logger_init

  init

  connect

  set_iq_callback
  set_subscription_callback
  set_message_callback

  start_backend_thread
  start_output_queue_thread

  prepare_users

  @logger.info 'Bot is going ONLINE'
  @output_queue.enq Jabber::Presence.new(nil, @status, @priority)

  add_signal_trap

  Thread.stop
rescue => ex
  general_error ex
  exit
end

Private Instance Methods

add_signal_trap() click to toggle source
# File lib/rumpy/bot.rb, line 224
def add_signal_trap
  Signal.trap :TERM do |signo| # soft stop
    @logger.info 'Bot is unavailable'
    @output_queue.enq Jabber::Presence.new.set_type :unavailable

    @queues.each do |user, queue|
      queue.enq :halt
    end
    sleep 1 until @queues.empty?

    @output_queue.enq :halt
    sleep 1 until @output_queue.empty?

    @client.close

    @logger.info 'terminating'
    @logger.close
    exit
  end
end
connect() click to toggle source
# File lib/rumpy/bot.rb, line 103
def connect
  @logger.debug 'establishing xmpp connection'

  @client.connect
  @client.auth @password
  @roster = Jabber::Roster::Helper.new @client
  @roster.wait_for_roster

  @logger.info 'xmpp connection established'
end
connection_timeout_error() click to toggle source
# File lib/rumpy/bot.rb, line 307
def connection_timeout_error
  @logger.warn 'ActiveRecord::ConnectionTimeoutError'
  @logger.info 'sleep and retry again'
  sleep 3
end
general_error(exception) click to toggle source
# File lib/rumpy/bot.rb, line 313
def general_error(exception)
  @logger.error exception.inspect
  @logger.error exception.backtrace
end
init() click to toggle source
# File lib/rumpy/bot.rb, line 62
def init
  @config_path ||= 'config'
  @main_model  ||= :user

  @logger.debug 'initializing some variables'

  xmppconfig  = YAML::load_file @config_path + '/xmpp.yml'
  @logger.info 'loaded xmpp.yml'
  @logger.debug "xmpp.yml: #{xmppconfig.inspect}"
  @lang       = YAML::load_file @config_path + '/lang.yml'
  @logger.info 'loaded lang.yml'
  @logger.debug "lang.yml: #{@lang.inspect}"
  @jid        = Jabber::JID.new xmppconfig['jid']
  @priority   = xmppconfig['priority']
  @status     = xmppconfig['status']
  @password   = xmppconfig['password']
  @client     = Jabber::Client.new @jid
  Jabber::Version::SimpleResponder.new(@client, @bot_name || self.class.to_s, @bot_version || '1.0.0', RUBY_PLATFORM)

  if @models_files
    dbconfig  = YAML::load_file @config_path + '/database.yml'
    @logger.info 'loaded database.yml'
    @logger.debug "database.yml: #{dbconfig.inspect}"
    ActiveRecord::Base.establish_connection dbconfig
    @logger.info 'database connection established'
    @models_files.each do |file|
      self.class.require file
      @logger.info "added models file '#{file}'"
    end
  end

  @main_model = Object.const_get @main_model.to_s.capitalize
  @logger.info "main model set to #{@main_model}"

  @queues = Hash.new do |h, k|
    h[k]  = Queue.new
  end

  @output_queue = Queue.new
end
logger_init() click to toggle source
# File lib/rumpy/bot.rb, line 50
def logger_init
  unless @logger
    @log_file             ||= STDERR
    @log_level            ||= Logger::INFO
    @logger                 = Logger.new @log_file, @log_shift_age, @log_shift_size
    @logger.level           = @log_level
    @logger.datetime_format = "%Y-%m-%d %H:%M:%S"
  end

  @logger.info 'starting bot'
end
prepare_users() click to toggle source
# File lib/rumpy/bot.rb, line 245
def prepare_users
  @logger.debug 'clear wrong users'

  @roster.items.each do |jid, item|
    user = @main_model.find_by_jid jid.strip.to_s
    if user.nil? || item.subscription != :both
      @logger.info "deleting from roster user with jid #{jid}"
      item.remove
    end
  end
  @main_model.find_each do |user|
    items = @roster.find user.jid
    if items.empty?
      @logger.info "deleting from database user with jid #{user.jid}"
      user.destroy
    else
      start_user_thread user
    end
  end

  @main_model.connection_pool.release_connection
end
set_iq_callback() click to toggle source
# File lib/rumpy/bot.rb, line 114
def set_iq_callback
  @client.add_iq_callback do |iq|
    @logger.debug "got iq #{iq}"
    if iq.type == :get # hack for pidgin (STOP USING IT)
      response = iq.answer true
      if iq.elements['time'] == "<time xmlns='urn:xmpp:time'/>"
        @logger.debug 'this is time request, okay'
        response.set_type :result
        tm = Time.now
        response.elements['time'].add REXML::Element.new('tzo')
        response.elements['time/tzo'].text = tm.xmlschema[-6..-1]
        response.elements['time'].add REXML::Element.new('utc')
        response.elements['time/utc'].text = tm.utc.xmlschema
      else
        response.set_type :error
      end # if iq.elements['time']
      @output_queue.enq response
    end
  end
end
set_message_callback() click to toggle source
# File lib/rumpy/bot.rb, line 172
def set_message_callback
  @client.add_message_callback do |msg|
    if msg.type != :error && msg.body && msg.from
      if @roster[msg.from] && @roster[msg.from].subscription == :both
        @logger.debug "got normal message #{msg}"

        @queues[msg.from.strip.to_s].enq msg
      else
        @logger.debug "user not in roster: #{msg.from}"

        @output_queue.enq msg.answer.set_body @lang['stranger']
      end
    end
  end
end
set_subscription_callback() click to toggle source
# File lib/rumpy/bot.rb, line 135
def set_subscription_callback
  @roster.add_subscription_request_callback do |item, presence|
    jid = presence.from
    @roster.accept_subscription jid
    @output_queue.enq presence.answer.set_type :subscribe
    @output_queue.enq Jabber::Message.new(jid, @lang['hello']).set_type :chat

    @logger.info "#{jid} just subscribed"
  end
  @roster.add_subscription_callback do |item, presence|
    begin
      case presence.type
      when :unsubscribed, :unsubscribe
        @logger.info "#{item.jid} wanna unsubscribe"
        @queues[item.jid.strip.to_s].enq :unsubscribe
        item.remove
      when :subscribed
        user = @main_model.new
        user.jid = item.jid.strip.to_s
        user.save
        start_user_thread user

        @logger.info "added new user: #{user.jid}"
        @output_queue.enq Jabber::Message.new(item.jid, @lang['authorized']).set_type :chat
      end
    rescue ActiveRecord::StatementInvalid
      statement_invalid_error
      retry
    rescue ActiveRecord::ConnectionTimeoutError
      connection_timeout_error
      retry
    rescue => ex
      general_error ex
    end
  end
end
start_backend_thread() click to toggle source
# File lib/rumpy/bot.rb, line 188
def start_backend_thread
  Thread.new do
    begin
      loop do
        backend_func().each do |result|
          message = Jabber::Message.new(*result).set_type :chat
          @output_queue.enq message if message.body && message.to
        end
      end
    rescue ActiveRecord::StatementInvalid
      statement_invalid_error
      retry
    rescue ActiveRecord::ConnectionTimeoutError
      connection_timeout_error
      retry
    rescue => ex
      general_error ex
    end # begin
  end if self.respond_to? :backend_func
end
start_output_queue_thread() click to toggle source
# File lib/rumpy/bot.rb, line 209
def start_output_queue_thread
  Thread.new do
    @logger.info "Output queue initialized"
    until (msg = @output_queue.deq) == :halt do
      if msg.nil?
        @logger.debug "got nil message. wtf?"
      else
        @logger.debug "sending message #{msg}"
        @client.send msg
      end
    end
    @logger.info "Output queue destroyed"
  end
end
start_user_thread(user) click to toggle source
# File lib/rumpy/bot.rb, line 268
def start_user_thread(user)
  Thread.new(user) do |user|
    @logger.debug "thread for user #{user.jid} started"

    until (msg = @queues[user.jid].deq).kind_of? Symbol do
      begin
        pars_results = parser_func msg.body
        @logger.debug "parsed message: #{pars_results.inspect}"
        answer = do_func user, pars_results
        @output_queue.enq msg.answer.set_body answer unless answer.nil? or answer.empty?
      rescue ActiveRecord::StatementInvalid
        statement_invalid_error
        retry
      rescue ActiveRecord::ConnectionTimeoutError
        connection_timeout_error
        retry
      rescue => ex
        general_error ex
      end # begin

      @main_model.connection_pool.release_connection
    end # until (msg = @queues[user.jid].deq).kind_of? Symbol do

    if msg == :unsubscribe
      @logger.info "removing user #{user.jid}"
      user.destroy
    end

    @queues.delete user.jid

  end # Thread.new do
end
statement_invalid_error() click to toggle source
# File lib/rumpy/bot.rb, line 301
def statement_invalid_error
  @logger.warn 'Statement Invalid catched'
  @logger.info 'Reconnecting to database'
  @main_model.connection.reconnect!
end