Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions lib/rebot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,25 @@

require 'slack_bot_server'
require 'slack_bot_server/redis_queue'
require 'slack_bot_server/remote_control'

require "rebot/version"

require "rebot/configuration"
require "rebot/server"
require "rebot/message"
require "rebot/bot"
require "rebot/conversation"

require 'rebot/backends/relax'

module Rebot
class RemoteControl < SlackBotServer::RemoteControl
def start_conversation(key, options = nil)
@queue.push([:start_conversation, key, options])
end
end

def self.logger
@logger ||= Logger.new(STDOUT)
end
Expand All @@ -27,14 +39,22 @@ def self.find_conversation(name)
end
end

def self.configure
yield(configuration)
end

def self.configuration
@configuration ||= Configuration.new
end

def self.server
@server ||= SlackBotServer::Server.new(queue: SlackBotServer::RedisQueue.new)
@server ||= Server.setup(configuration.compile)
end


def self.remote_control
@remote_control ||= begin
require 'slack_bot_server/remote_control'
SlackBotServer::RemoteControl.new(queue: SlackBotServer::RedisQueue.new)
RemoteControl.new(queue: SlackBotServer::RedisQueue.new)
end
end
end
171 changes: 171 additions & 0 deletions lib/rebot/backends/relax.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
module Rebot
module Backends
class Relax
def initialize(config)
@queue = config.queue
@bots = {}
@add_proc = -> (token) { SlackBotServer::SimpleBot.new(token: token) }
@running = false

@bots_key = config.adapter_options[:bots_key]
@incoming_queue = config.adapter_options[:incoming_queue]
@outgoing_queue = config.adapter_options[:outgoing_queue]
end

def start
EM.run do
@running = true
listen_for_relax_events
listen_for_instructions if @queue
start_ticking

end
end

def on_add(&block)
@add_proc = block
end

def add_bot(token)
if @bots[token.to_sym]
Rebot.logger.warn "Attempt to add already added bot with token: #{token}"
else
bot = @add_proc.call(token)
Rebot.logger.info "Adding bot with token: #{bot.key}"
@bots[bot.key.to_sym] = bot
unless redis.hexists(@bots_key, bot.token)
redis.multi do
redis.hset(@bots_key, bot.token, { token: bot.token }.to_json)
redis.publish(@outgoing_queue, { type: 'bot_added', token: bot.token }.to_json)
end
end
end
rescue => e
# TODO:
raise e
end

def message(token, message)
redis.publish(@outgoing_queue, { type: 'message', token: token, message: message }.to_json)
end

private

def start_ticking
EM.add_periodic_timer(1) do
begin
@bots.values.each { |b| b.convos.each { |convo| convo.tick } }
rescue => e
# TODO
log_error(e)
end
end
end

def listen_for_relax_events
EM.add_periodic_timer(0.1) do
begin
event_json = redis.lpop(@incoming_queue)
if event_json
event = JSON.parse(event_json)
if bot = @bots[event['token'].to_sym]
Rebot.logger.debug "Received message for bot: #{bot}: #{event}"
event = normalize_event_format(event)
bot.send(:run_callbacks, event['type'], event)
end
end
rescue => e
# TODO
log_error(e)
end
end
end

def listen_for_instructions
EM.add_periodic_timer(1) do
begin
next_message = @queue.pop
process_instruction(next_message) if next_message
rescue => e
log_error(e)
# TODO
end
end
end

def process_instruction(instruction)
type, *args = instruction
Rebot.logger.info("Received remote instruction: #{type} with arguments: #{args}")
bot_key = args.shift
if type.to_sym == :add_bot
add_bot(bot_key, *args)
else
with_bot(bot_key) do |bot|
case type.to_sym
when :remove_bot
remove_bot(bot_key)
when :broadcast
log "[#{bot_key}] broadcast: #{args}"
bot.broadcast(*args)
when :say
Rebot.logger.info "[#{bot_key}] say: #{args}"
bot.say(*args)
when :start_conversation
bot.start_conversation(*args)
when :say_to
user_id, message_data = args
log "[#{bot_key}] say_to: (#{user_id}) #{message_data}"
bot.say_to(user_id, message_data)
when :call
method, method_args = args
bot.call(method, method_args)
else
log unknown_command: instruction
Rebot.logger.warn("Unknown command: #{instruction}")
end
end
end
end

def with_bot(key)
if bot = bot(key)
yield bot
else
Rebot.logger.warn("Unknown bot: #{key}")
end
end

def bot(key)
@bots[key.to_sym]
end

def normalize_event_format(event)
if event['event'] == 'mention' || event['event'] == 'direct_mention'
event['event'] = 'mention'
end

if event['event'] == 'direct_message'
event['event'] = 'dm'
end
event
end

def log_error(e)
Rebot.logger.warn("Error in server: #{e} - #{e.message}")
Rebot.logger.warn(e.backtrace.join("\n"))
end

def redis
if uri = ENV['REDISTOGO_URL']
redis_uri = URI.parse(uri)
elsif uri = ENV['REDIS_URL']
redis_uri = URI.parse(uri)
else
redis_uri = URI.parse("redis://localhost:6379")
end

@redis ||= Redis.new(url: redis_uri, db: 0)
end
end
end
end
12 changes: 9 additions & 3 deletions lib/rebot/bot.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
module Rebot
class Bot < SlackBotServer::Bot

def initialize(token:, key: nil)
super
@convos = []
attr_reader :team_id, :convos

def initialize(token:, team_id: nil, options: {})
super(token: token, key: nil)
@team_id = team_id
@convos = []
end

def default_message_options
Expand Down Expand Up @@ -32,7 +35,10 @@ def typing(options = {})

def start
super
schedule_conversations
end

def schedule_conversations
EM.add_periodic_timer(1) do
@convos.each { |convo| convo.tick }
end
Expand Down
46 changes: 46 additions & 0 deletions lib/rebot/configuration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
module Rebot
class Configuration
# Adapter to use, currently supported: slack_bot_server and relax
attr_reader :adapter_options

# Bot class
attr_reader :bot_class

attr_reader :queue

# TODO: validate adapters
def initialize
@queue = SlackBotServer::RedisQueue.new
@adapter = :slack_bot_server
@adapter_options = {}
@bot_class = Rebot::Bot
end

def bot(bot_class = nil)
if bot_class
@bot_class = bot_class
else
@bot_class
end
end

def adapter(adapter = nil, options = {})
if adapter
@adapter = adapter
@adapter_options = options
else
@adapter
end
end

def compile
if self.adapter == :relax
self.adapter_options[:bots_key] ||= "relax_bots_key"
self.adapter_options[:incoming_queue] ||= "rebot_incoming_queue"
self.adapter_options[:outgoing_queue] ||= "rebot_outgoing_queue"
end

self
end
end
end
5 changes: 3 additions & 2 deletions lib/rebot/conversation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module Rebot
class Conversation
DEFAULT_TIMEOUT = 180 # set default timeout to 180 seconds (3 minutes)
DEFAULT_TIMEOUT_MESSAGE = "We can pick this up later."
DEFAULT_STOP_PATTERNS = ["^exit", "^stop", "^quit"]
DEFAULT_STOP_PATTERNS = ["^exit", "^stop", "^quit", "^cancel"]
DEFAULT_STOP_MESSAGE = "Ok. Done"

attr_reader :data, :source_message, :bot
Expand Down Expand Up @@ -110,8 +110,9 @@ def tick
end

def handle(message)
message.conversation = true
@last_active_at = Time.now
Rebot.logger.debug "Handling message in conversation: #{message.text}"
Rebot.logger.info "Handling message in conversation: #{message.text}"

if stop_patterns.any? { |sp| message.text.match(Regexp.new(sp, true)) }
say(text: stop_message, action: "stop")
Expand Down
11 changes: 9 additions & 2 deletions lib/rebot/message.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
module Rebot
class Message
attr_reader :text, :user, :channel, :event
attr_reader :text, :user, :channel, :event
attr_accessor :conversation

def initialize(data, bot)
@data = data
@bot = bot

# Flag to indicate whether or not
# the message is part of conversation
@conversation = false

@mention_regex = /\A(<@#{bot.bot_user_id}>)[\s\:](.*)/

@user = data['user']
Expand All @@ -26,7 +31,9 @@ def [](key)

def resolve_event(type)
# set up a couple of special cases based on subtype
if @data['subtype'] == 'channel_join'
if @data['event']
@data['event']
elsif @data['subtype'] == 'channel_join'
if @data['user'] == @bot.bot_user_id
"bot_channel_join"
else
Expand Down
23 changes: 23 additions & 0 deletions lib/rebot/server.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# FIXME
module Rebot
class Server
def self.setup(config)
backend = init_backend(config)
backend.on_add do |token|
config.bot_class.new(token: token)
end
backend
end

def self.init_backend(config)
if config.adapter == :slack_bot_server
SlackBotServer.logger = Rebot.logger
SlackBotServer::Server.new(queue: config.queue)
elsif config.adapter == :relax
Backends::Relax.new(config)
else
raise "Unknown server adapter #{configuration.adapter}"
end
end
end
end