class Ant::Bot::Base

Base class for bot implementation. It wraps the threads execution, the provider and the state storage inside an object.

Public Class Methods

new(configs) click to toggle source

Configurations needed:

  • pool_size: number of threads created in execution

  • provider: a configuration for a thread provider. See supported adapters

  • name: The bot name

  • repository: Configurations about the state storage

# File lib/ant/bot/base.rb, line 23
def initialize(configs)
  @pool_size = configs['pool_size']
  @provider = Ant::Bot::Adapter.from_config(configs['provider'])
  @commands = Ant::Bot::CommandDefinition.new

  # TODO: move this to config
  @repository = Ant::Storage::Repository.from_config(
    nil,
    configs['state_repository']
      .merge('primary_key' => 'channel_id',
             'table' => 'bot_sessions'),
    {}
  )
  @factory = Ant::Storage::Factory.new(EmptyModel)
  @factory.register(:default, :json)
  @factory.register(:json, @repository)
end

Public Instance Methods

add_param(value) click to toggle source

Stores a parameter into the status

# File lib/ant/bot/base.rb, line 141
def add_param(value)
  log_debug('Received new param',
            param: @state[:requested_param].to_sym,
            value: value)

  @state[:params][@state[:requested_param].to_sym] = value.raw_message
end
ask_param(param) click to toggle source

Sends a message to get the next parameter from the user

# File lib/ant/bot/base.rb, line 133
def ask_param(param)
  log_debug('I\'m going to ask the next param', param: param)
  @provider.send_message(current_channel,
                         "I need you to tell me #{param}")
  @state[:requested_param] = param.to_s
end
command=(cmd) click to toggle source

stores the command into state

# File lib/ant/bot/base.rb, line 120
def command=(cmd)
  log_debug('Message set as command', command: cmd)

  @state[:cmd] = cmd.raw_message
  @state[:params] = {}
end
command_ready?() click to toggle source

Checks if the command is ready to be executed

# File lib/ant/bot/base.rb, line 98
def command_ready?
  cmd = current_command_object
  cmd.ready?(current_params)
end
current_channel() click to toggle source

returns the current_channel from where the message was sent

# File lib/ant/bot/base.rb, line 115
def current_channel
  @state[:channel_id]
end
current_command_object() click to toggle source

Loads command from state

# File lib/ant/bot/base.rb, line 109
def current_command_object
  command = @state[:cmd]
  @commands[command]
end
current_params() click to toggle source

loads parameters from state

# File lib/ant/bot/base.rb, line 104
def current_params
  @state[:params] || {}
end
load_state(channel) click to toggle source

Private implementation for load message

# File lib/ant/bot/base.rb, line 155
def load_state(channel)
  data = @factory.get(channel)
  data[:params] = JSON.parse(data[:params], symbolize_names: true)
  data
rescue Ant::Storage::Exceptions::ObjectNotFound
  @factory.create(channel_id: channel, params: {}.to_json)
end
load_state!(channel) click to toggle source

Loads the state from storage

# File lib/ant/bot/base.rb, line 150
def load_state!(channel)
  @state = load_state(channel)
end
next_missing_param() click to toggle source

validates which is the following parameter required

# File lib/ant/bot/base.rb, line 128
def next_missing_param
  current_command_object.next_missing_param(current_params)
end
process_message(message) click to toggle source

Process a single message, this method can be overwriten to enable more complex implementations of commands. It receives a message object.

# File lib/ant/bot/base.rb, line 63
def process_message(message)
  run_simple_command!(message)
end
register_command(name, params, &block) click to toggle source

DSL method for adding simple commands

# File lib/ant/bot/base.rb, line 88
def register_command(name, params, &block)
  @commands.register_command(name, params, block)
end
run() click to toggle source

Starts the bot execution, this is a blocking call.

# File lib/ant/bot/base.rb, line 46
def run
  @pool = Array.new(@pool_size) do
    # TODO: Create a subclass with the context execution
    Ant::DRY::Daemon.new(@pool_size, true) do
      message = @provider.read_message
      process_message(message)
    end
  end
  # TODO: Implement an interface for killing the process
  @pool.each(&:run)
  # :nocov: #
  @pool.each(&:await)
  # :nocov: #
end
run_command!() click to toggle source

Method for triggering command

# File lib/ant/bot/base.rb, line 93
def run_command!
  current_command_object.execute(current_params)
end
run_simple_command!(message) click to toggle source

Executes a command with the easiest definition. It runs a state machine:

  • If the message is a command, set the status to asking params

  • If the message is a param, stores it

  • If the command is ready to be executed, trigger it.

# File lib/ant/bot/base.rb, line 71
def run_simple_command!(message)
  load_state!(message.channel_id)
  log_debug('loaded state', message: message.to_h, state: @state.to_h)
  if message.command?
    self.command = message
  else
    add_param(message)
  end
  if command_ready?
    run_command!
  else
    ask_param(next_missing_param)
  end
  save_state!
end
save_state!() click to toggle source

Saves the state into storage

# File lib/ant/bot/base.rb, line 164
def save_state!
  json = @state[:params]
  @state[:params] = json.to_json
  @state.store
  @state[:params] = json
end
session() click to toggle source
# File lib/ant/bot/base.rb, line 41
def session
  @repository.connection
end