class Kybus::Bot::Base
Base
class for bot implementation. It wraps the threads execution, the provider and the state storage inside an object.
Base
class for bot implementation. It wraps the threads execution, the provider and the state storage inside an object.
Attributes
Public Class Methods
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/kybus/bot/base.rb, line 41 def initialize(configs) @pool_size = configs['pool_size'] @provider = Kybus::Bot::Adapter.from_config(configs['provider']) @commands = Kybus::Bot::CommandDefinition.new # TODO: move this to config @repository = Kybus::Storage::Repository.from_config( nil, configs['state_repository'] .merge('primary_key' => 'channel_id', 'table' => 'bot_sessions'), {} ) @factory = Kybus::Storage::Factory.new(EmptyModel) @factory.register(:default, :json) @factory.register(:json, @repository) end
Public Instance Methods
Stores a parameter into the status
# File lib/kybus/bot/base.rb, line 183 def add_param(value) return if @state[:requested_param].nil? log_debug('Received new param', param: @state[:requested_param].to_sym, value: value) @state[:params][@state[:requested_param].to_sym] = value end
Sends a message to get the next parameter from the user
# File lib/kybus/bot/base.rb, line 175 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
# File lib/kybus/bot/base.rb, line 119 def clear_command @state[:cmd] = nil end
stores the command into state
# File lib/kybus/bot/base.rb, line 162 def command=(cmd) log_debug('Message set as command', command: cmd) @state[:cmd] = cmd.split(' ').first @state[:params] = {} end
Checks if the command is ready to be executed
# File lib/kybus/bot/base.rb, line 124 def command_ready? cmd = current_command_object cmd.ready?(current_params) end
returns the current_channel
from where the message was sent
# File lib/kybus/bot/base.rb, line 149 def current_channel @state[:channel_id] end
Loads command from state
# File lib/kybus/bot/base.rb, line 143 def current_command_object command = @state[:cmd] @commands[command] || @commands['default'] end
loads parameters from state
# File lib/kybus/bot/base.rb, line 130 def current_params @state[:params] || {} end
# File lib/kybus/bot/base.rb, line 153 def current_user @last_message.user end
# File lib/kybus/bot/base.rb, line 157 def is_private? @last_message.is_private? end
Private implementation for load message
# File lib/kybus/bot/base.rb, line 199 def load_state(channel) data = @factory.get(channel) data[:params] = JSON.parse(data[:params], symbolize_names: true) data rescue Kybus::Storage::Exceptions::ObjectNotFound @factory.create(channel_id: channel, params: {}.to_json) end
Loads the state from storage
# File lib/kybus/bot/base.rb, line 194 def load_state!(channel) @state = load_state(channel) end
# File lib/kybus/bot/base.rb, line 138 def mention(name) @provider.mention(name) end
validates which is the following parameter required
# File lib/kybus/bot/base.rb, line 170 def next_missing_param current_command_object.next_missing_param(current_params) end
# File lib/kybus/bot/base.rb, line 134 def params current_params end
Process a single message, this method can be overwriten to enable more complex implementations of commands. It receives a message object.
# File lib/kybus/bot/base.rb, line 78 def process_message(message) run_simple_command!(message) end
DSL method for adding simple commands
# File lib/kybus/bot/base.rb, line 109 def register_command(name, params = [], &block) @commands.register_command(name, params, block) end
# File lib/kybus/bot/base.rb, line 23 def rescue_from(klass, &block) @commands.register_command(klass, [], block) end
Starts the bot execution, this is a blocking call.
# File lib/kybus/bot/base.rb, line 60 def run @pool = Array.new(@pool_size) do # TODO: Create a subclass with the context execution Kybus::DRY::Daemon.new(@pool_size, true) do message = @provider.read_message @last_message = message process_message(message) end end # TODO: Implement an interface for killing the process @pool.each(&:run) # :nocov: # @pool.each(&:await) # :nocov: # end
Method for triggering command
# File lib/kybus/bot/base.rb, line 114 def run_command! instance_eval(¤t_command_object.block) clear_command end
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/kybus/bot/base.rb, line 86 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.raw_message else add_param(message.raw_message) end if command_ready? run_command! else ask_param(next_missing_param) end save_state! rescue StandardError => e catch = @commands[e.class] raise if catch.nil? instance_eval(&catch.block) clear_command end
# File lib/kybus/bot/testing.rb, line 20 def run_test run rescue Debug::NoMoreMessageException true end
Saves the state into storage
# File lib/kybus/bot/base.rb, line 208 def save_state! json = @state[:params] @state[:params] = json.to_json @state.store @state[:params] = json end
# File lib/kybus/bot/base.rb, line 31 def send_audio(content, channel = nil) @provider.send_audio(channel || current_channel, content) end
# File lib/kybus/bot/base.rb, line 27 def send_image(content, channel = nil) @provider.send_image(channel || current_channel, content) end
# File lib/kybus/bot/base.rb, line 19 def send_message(content, channel = nil) @provider.send_message(channel || current_channel, content) end
# File lib/kybus/bot/base.rb, line 215 def session @repository end
# File lib/kybus/bot/testing.rb, line 14 def stub_channels(messages) raise(NonDebugAdapterInTesting) unless @provider.is_a?(Debug) @provider = Debug.new('channels' => messages) end