bot_mob 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/console +1 -0
- data/lib/bot_mob.rb +33 -20
- data/lib/bot_mob/ambassador.rb +22 -8
- data/lib/bot_mob/application.rb +10 -4
- data/lib/bot_mob/authority.rb +22 -6
- data/lib/bot_mob/bot.rb +29 -17
- data/lib/bot_mob/connection.rb +19 -61
- data/lib/bot_mob/development/ambassador.rb +21 -0
- data/lib/bot_mob/development/connection.rb +34 -0
- data/lib/bot_mob/development/inbound_message.rb +17 -0
- data/lib/bot_mob/errors.rb +12 -0
- data/lib/bot_mob/inbound_message.rb +25 -32
- data/lib/bot_mob/install.rb +2 -6
- data/lib/bot_mob/nil_connection.rb +29 -0
- data/lib/bot_mob/outbound_message.rb +19 -0
- data/lib/bot_mob/roster.rb +44 -10
- data/lib/bot_mob/slack.rb +10 -0
- data/lib/bot_mob/slack/ambassador.rb +8 -0
- data/lib/bot_mob/slack/connection.rb +87 -0
- data/lib/bot_mob/slack/inbound_message.rb +52 -0
- data/lib/bot_mob/version.rb +1 -1
- data/lib/bot_mob/wire.rb +19 -1
- metadata +11 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a6c06b15e2ece464e52b403fd36bd610487da2b8
|
4
|
+
data.tar.gz: 34dfe52f53892d31d918aae0611dcc347f571ab7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 34c4efc4364f76889ea9968669b3bcf7c3c5c8b85c6715dff07f138dbaa8bd3c1dd6a57416761251ccc7ba81244e9c054acb8cb6b084b69503ac6c9a8938fa58
|
7
|
+
data.tar.gz: 2c4d05434f48c330d51f426404e7170fd63b410e7e09f67d532d05aa2ab78ef92171d617d75958c584a339f978b6e9988d84655530fb46ad42383e7c103550b6
|
data/bin/console
CHANGED
data/lib/bot_mob.rb
CHANGED
@@ -1,32 +1,45 @@
|
|
1
1
|
$LOAD_PATH.unshift File.dirname(__FILE__)
|
2
|
-
require '
|
3
|
-
require 'slack-ruby-client'
|
2
|
+
require 'bot_mob/errors'
|
4
3
|
require 'bot_mob/version'
|
5
4
|
|
6
5
|
# ## BotMob
|
7
6
|
#
|
8
7
|
# BotMob provides all the tools you need to easily
|
9
|
-
# connect to chat networks
|
8
|
+
# connect multiple bots to chat networks
|
10
9
|
module BotMob
|
11
|
-
autoload :Ambassador,
|
12
|
-
autoload :Application,
|
13
|
-
autoload :Authority,
|
14
|
-
autoload :Bot,
|
15
|
-
autoload :Connection,
|
16
|
-
autoload :InboundMessage,
|
17
|
-
autoload :Install,
|
18
|
-
autoload :
|
19
|
-
autoload :
|
20
|
-
|
21
|
-
|
22
|
-
|
10
|
+
autoload :Ambassador, 'bot_mob/ambassador'
|
11
|
+
autoload :Application, 'bot_mob/application'
|
12
|
+
autoload :Authority, 'bot_mob/authority'
|
13
|
+
autoload :Bot, 'bot_mob/bot'
|
14
|
+
autoload :Connection, 'bot_mob/connection'
|
15
|
+
autoload :InboundMessage, 'bot_mob/inbound_message'
|
16
|
+
autoload :Install, 'bot_mob/install'
|
17
|
+
autoload :NilConnection, 'bot_mob/nil_connection'
|
18
|
+
autoload :OutboundMessage, 'bot_mob/outbound_message'
|
19
|
+
autoload :Roster, 'bot_mob/roster'
|
20
|
+
autoload :Wire, 'bot_mob/wire'
|
21
|
+
|
22
|
+
module Development
|
23
|
+
autoload :Ambassador, 'bot_mob/development/ambassador'
|
24
|
+
autoload :Connection, 'bot_mob/development/connection'
|
25
|
+
autoload :InboundMessage, 'bot_mob/development/inbound_message'
|
26
|
+
end
|
27
|
+
|
28
|
+
@@networks = {
|
29
|
+
dev: BotMob::Development
|
30
|
+
}
|
31
|
+
|
32
|
+
def self.networks
|
33
|
+
@@networks
|
23
34
|
end
|
24
35
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
36
|
+
def self.mount(network_map)
|
37
|
+
@@networks.merge!(network_map)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.valid_network?(network)
|
41
|
+
networks.include?(network.to_s.to_sym) && BotMob.networks[network]
|
42
|
+
end
|
30
43
|
|
31
44
|
def self.root
|
32
45
|
File.expand_path('../..', __FILE__)
|
data/lib/bot_mob/ambassador.rb
CHANGED
@@ -1,20 +1,34 @@
|
|
1
1
|
module BotMob
|
2
2
|
class Ambassador
|
3
|
-
|
3
|
+
# ## BotMob::Ambassador.setup
|
4
|
+
#
|
5
|
+
# Ambassador setup delegates the ambassadorship
|
6
|
+
# to the correct network.
|
7
|
+
def self.setup(network, args = {})
|
8
|
+
invalid_network_error(network) unless BotMob.valid_network?(network)
|
4
9
|
|
5
|
-
|
6
|
-
|
7
|
-
|
10
|
+
ambassador = network_delegate(network)
|
11
|
+
invalid_delegate_error(network) unless ambassador.respond_to?(:setup)
|
12
|
+
|
13
|
+
ambassador.setup(args)
|
8
14
|
end
|
9
15
|
|
10
|
-
def
|
11
|
-
{
|
16
|
+
def data
|
17
|
+
{}
|
12
18
|
end
|
13
19
|
|
14
20
|
private
|
15
21
|
|
16
|
-
def self.
|
17
|
-
BotMob
|
22
|
+
def self.network_delegate(network)
|
23
|
+
BotMob.networks[network].const_get('Ambassador')
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.invalid_network_error(network)
|
27
|
+
raise BotMob::InvalidNetworkError, "The #{network.inspect} network has not been mounted"
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.invalid_delegate_error(network)
|
31
|
+
raise BotMob::InvalidAmbassadorError, "The #{network.inspect} ambassador must implement the `setup` method"
|
18
32
|
end
|
19
33
|
end
|
20
34
|
end
|
data/lib/bot_mob/application.rb
CHANGED
@@ -3,7 +3,7 @@ require 'forwardable'
|
|
3
3
|
module BotMob
|
4
4
|
# ## BotMob::Application
|
5
5
|
#
|
6
|
-
# This is the base class for BotMob.
|
6
|
+
# This is the base application class for BotMob.
|
7
7
|
class Application
|
8
8
|
extend Forwardable
|
9
9
|
|
@@ -13,9 +13,7 @@ module BotMob
|
|
13
13
|
BotMob.logger.info("=> BotMob #{BotMob::VERSION} application starting")
|
14
14
|
|
15
15
|
roster.load
|
16
|
-
wire.listen
|
17
|
-
roster.connect(bot)
|
18
|
-
end
|
16
|
+
wire.listen { |auth| roster.connect(auth) }
|
19
17
|
|
20
18
|
loop do
|
21
19
|
sleep(10)
|
@@ -26,10 +24,18 @@ module BotMob
|
|
26
24
|
|
27
25
|
private
|
28
26
|
|
27
|
+
# ## Roster
|
28
|
+
#
|
29
|
+
# The `roster` is the object that keeps track of the registration
|
30
|
+
# and participation of any various bots active on your mob
|
29
31
|
def roster
|
30
32
|
@roster ||= BotMob::Roster.new
|
31
33
|
end
|
32
34
|
|
35
|
+
# ## Wire
|
36
|
+
#
|
37
|
+
# The `wire` is the object that allows for other processes to
|
38
|
+
# tell your bot mob application to take some action.
|
33
39
|
def wire
|
34
40
|
@wire ||= BotMob::Wire.new
|
35
41
|
end
|
data/lib/bot_mob/authority.rb
CHANGED
@@ -1,23 +1,39 @@
|
|
1
1
|
module BotMob
|
2
2
|
# ## BotMob::Authority
|
3
3
|
#
|
4
|
-
# The Authority class will process an access
|
5
|
-
# and retrieve an access token from
|
4
|
+
# The Authority class will process an access args
|
5
|
+
# and retrieve an access token from the specified
|
6
|
+
# network
|
7
|
+
#
|
8
|
+
# Example Invocation:
|
9
|
+
#
|
10
|
+
# BotMob::Authority.new(:slack, code: params[:code])
|
6
11
|
class Authority
|
7
|
-
attr_reader :network
|
12
|
+
attr_reader :network
|
8
13
|
|
9
|
-
def initialize(network,
|
10
|
-
@
|
14
|
+
def initialize(network, args = {})
|
15
|
+
@network = network
|
16
|
+
@args = args
|
11
17
|
end
|
12
18
|
|
13
19
|
def process(bot_class)
|
14
20
|
return unless ambassador.success?
|
15
21
|
|
16
|
-
wire.publish(
|
22
|
+
# wire.publish(network: :slack, external_id: 'foo', bot_class: 'MockBot', data: { token: ENV['IOBOT_TOKEN'] } )
|
23
|
+
wire.publish({
|
24
|
+
network: network,
|
25
|
+
external_id: ambassador.external_id,
|
26
|
+
bot_class: bot_class.to_s,
|
27
|
+
data: ambassador.data || {}
|
28
|
+
})
|
17
29
|
end
|
18
30
|
|
19
31
|
private
|
20
32
|
|
33
|
+
def ambassador
|
34
|
+
@ambassador ||= BotMob::Ambassador.setup(network, @args)
|
35
|
+
end
|
36
|
+
|
21
37
|
def wire
|
22
38
|
@wire ||= BotMob::Wire.new
|
23
39
|
end
|
data/lib/bot_mob/bot.rb
CHANGED
@@ -4,38 +4,50 @@ module BotMob
|
|
4
4
|
# ## BotMob::Bot
|
5
5
|
#
|
6
6
|
# This is the base for all bots that will connect
|
7
|
-
# to
|
7
|
+
# to respond to messages received by your application.
|
8
8
|
class Bot
|
9
|
-
|
9
|
+
attr_accessor :id, :network
|
10
|
+
attr_reader :options
|
10
11
|
|
11
12
|
extend Forwardable
|
12
|
-
def_delegators :connection, :connect, :reconnect, :refresh, :client
|
13
|
+
def_delegators :connection, :connect, :reconnect, :refresh, :client, :deliver
|
13
14
|
|
14
|
-
def initialize(
|
15
|
-
@
|
16
|
-
@token = token
|
15
|
+
def initialize(*args)
|
16
|
+
@network, @id, @options = *args
|
17
17
|
end
|
18
18
|
|
19
|
-
def
|
20
|
-
|
21
|
-
client.web_client.chat_postMessage(args)
|
19
|
+
def key
|
20
|
+
"#{network}.#{id}"
|
22
21
|
end
|
23
22
|
|
24
|
-
def
|
25
|
-
BotMob.
|
26
|
-
|
23
|
+
def receive(message)
|
24
|
+
inbound_message = message.is_a?(String) ? BotMob::InboundMessage.new(message) : message
|
25
|
+
return unless respond?(inbound_message)
|
26
|
+
|
27
|
+
BotMob.logger.info("Received message at #{Time.now}")
|
28
|
+
BotMob.logger.info(" Parameters: #{inbound_message.params}")
|
29
|
+
respond(inbound_message)
|
27
30
|
end
|
28
31
|
|
29
|
-
def respond(
|
30
|
-
#
|
32
|
+
def respond(inbound_message)
|
33
|
+
outbound = BotMob::OutboundMessage.new(text: "#{self.class} ACK! #{inbound_message.text}")
|
34
|
+
deliver(outbound)
|
31
35
|
end
|
32
36
|
|
33
|
-
def respond?(
|
34
|
-
|
37
|
+
def respond?(inbound_message)
|
38
|
+
true
|
35
39
|
end
|
36
40
|
|
41
|
+
private
|
42
|
+
|
37
43
|
def connection
|
38
|
-
@connection ||=
|
44
|
+
@connection ||= begin
|
45
|
+
if BotMob.valid_network?(network)
|
46
|
+
BotMob::Connection.setup(network, self, options)
|
47
|
+
else
|
48
|
+
BotMob::NilConnection.new(self)
|
49
|
+
end
|
50
|
+
end
|
39
51
|
end
|
40
52
|
end
|
41
53
|
end
|
data/lib/bot_mob/connection.rb
CHANGED
@@ -6,50 +6,27 @@ module BotMob
|
|
6
6
|
class Connection
|
7
7
|
attr_reader :bot
|
8
8
|
|
9
|
-
def
|
10
|
-
|
11
|
-
@token = token
|
12
|
-
token.nil? ? set_state(:inactive) : setup
|
13
|
-
end
|
14
|
-
|
15
|
-
def connect
|
16
|
-
return unless active?
|
17
|
-
set_state(:connecting)
|
18
|
-
establish
|
19
|
-
end
|
9
|
+
def self.setup(network, bot, options = {})
|
10
|
+
invalid_network_error(network) unless BotMob.valid_network?(network)
|
20
11
|
|
21
|
-
|
22
|
-
|
23
|
-
set_state(:reconnecting)
|
24
|
-
renew_client
|
25
|
-
setup
|
26
|
-
establish
|
27
|
-
end
|
12
|
+
connection = network_delegate(network)
|
13
|
+
invalid_delegate_error(network) unless connection.respond_to?(:setup)
|
28
14
|
|
29
|
-
|
30
|
-
return unless active?
|
31
|
-
|
32
|
-
case true
|
33
|
-
when waiting?
|
34
|
-
connect
|
35
|
-
when disconnected?
|
36
|
-
reconnect
|
37
|
-
else
|
38
|
-
logger.info "State[#{bot.external_id}] - No Change"
|
39
|
-
end
|
15
|
+
connection.setup(bot, options)
|
40
16
|
end
|
41
17
|
|
42
|
-
def
|
43
|
-
|
18
|
+
def deliver(outbound_message, options = {})
|
19
|
+
# connection.deliver(outbound_message, options)
|
44
20
|
end
|
45
21
|
|
46
22
|
private
|
47
23
|
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
24
|
+
def self.network_delegate(network)
|
25
|
+
BotMob.networks[network].const_get('Connection')
|
26
|
+
end
|
27
|
+
|
28
|
+
def welcome_message
|
29
|
+
"Connected!"
|
53
30
|
end
|
54
31
|
|
55
32
|
def active?
|
@@ -73,37 +50,18 @@ module BotMob
|
|
73
50
|
end
|
74
51
|
|
75
52
|
def set_state(state)
|
76
|
-
if state == :connected
|
77
|
-
logger.info "Successfully connected, '#{client.team.name}' team at https://#{client.team.domain}.slack.com."
|
78
|
-
end
|
53
|
+
BotMob.logger.info welcome_message if state == :connected
|
79
54
|
|
80
|
-
logger.info "State[#{bot.class.to_s}##{bot.
|
55
|
+
BotMob.logger.info "State[#{bot.class.to_s}##{bot.key}] - #{state}"
|
81
56
|
@state = state
|
82
57
|
end
|
83
58
|
|
84
|
-
def
|
85
|
-
|
86
|
-
client.on(:hello) { set_state(:connected) }
|
87
|
-
client.on(:close) { set_state(:disconnecting) }
|
88
|
-
client.on(:closed) { set_state(:closed) }
|
89
|
-
|
90
|
-
client.on :message do |data|
|
91
|
-
message = BotMob::InboundMessage.new(:websocket, data)
|
92
|
-
|
93
|
-
if bot.respond?(message)
|
94
|
-
logger.info("Received message at #{Time.now}")
|
95
|
-
logger.info(" Parameters: #{message.data}")
|
96
|
-
bot.respond(message)
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
def renew_client
|
102
|
-
@client = ::Slack::RealTime::Client.new(token: @token)
|
59
|
+
def self.invalid_network_error(network)
|
60
|
+
raise BotMob::InvalidNetworkError, "The #{network.inspect} network has not been mounted"
|
103
61
|
end
|
104
62
|
|
105
|
-
def
|
106
|
-
|
63
|
+
def self.invalid_delegate_error(network)
|
64
|
+
raise BotMob::InvalidConnectionError, "The #{network.inspect} connection must implement the `setup` method"
|
107
65
|
end
|
108
66
|
end
|
109
67
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module BotMob
|
2
|
+
module Development
|
3
|
+
class Connection < BotMob::Connection
|
4
|
+
|
5
|
+
def initialize(bot, options = {})
|
6
|
+
@bot = bot
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.setup(bot, options = {})
|
10
|
+
new(bot, options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def deliver(outbound_message, options = {})
|
14
|
+
BotMob.logger.info " Dev Connection Sent: #{outbound_message.text.inspect}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def connect
|
18
|
+
BotMob.logger.info 'Connecting... '
|
19
|
+
end
|
20
|
+
|
21
|
+
def reconnect
|
22
|
+
BotMob.logger.info 'Reconnecting... '
|
23
|
+
end
|
24
|
+
|
25
|
+
def refresh
|
26
|
+
BotMob.logger.info 'Refreshing... '
|
27
|
+
end
|
28
|
+
|
29
|
+
def client
|
30
|
+
self
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module BotMob
|
2
|
+
module Development
|
3
|
+
class InboundMessage < BotMob::InboundMessage
|
4
|
+
def initialize(params)
|
5
|
+
@text = params[:text]
|
6
|
+
@network = params[:network]
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.parse(**params)
|
10
|
+
new({
|
11
|
+
network: params[:network],
|
12
|
+
text: params[:text]
|
13
|
+
})
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module BotMob
|
2
|
+
class InvalidAmbassadorError < StandardError; end
|
3
|
+
class InvalidConnectionError < StandardError; end
|
4
|
+
class InvalidInboundMessageError < StandardError; end
|
5
|
+
|
6
|
+
class InvalidNetworkError < StandardError; end
|
7
|
+
class InvalidClientIdError < StandardError; end
|
8
|
+
class InvalidClientSecretError < StandardError; end
|
9
|
+
class InvalidCodeError < StandardError; end
|
10
|
+
class ConnectionNotEstablished < StandardError; end
|
11
|
+
class UnregisteredBotError < StandardError; end
|
12
|
+
end
|
@@ -1,52 +1,45 @@
|
|
1
1
|
module BotMob
|
2
|
-
# ## BotMob::
|
2
|
+
# ## BotMob::InboundMessage
|
3
3
|
#
|
4
4
|
# The InboundMessage class provides easy access to
|
5
|
-
# messages received
|
5
|
+
# messages received by bots in your application
|
6
6
|
class InboundMessage
|
7
|
-
attr_reader :
|
8
|
-
:channel_id, :channel_name, :external_id, :user_name,
|
9
|
-
:command, :text, :bot_id, :response_url, :ts
|
7
|
+
attr_reader :network, :text, :params
|
10
8
|
|
11
|
-
def initialize(
|
12
|
-
@
|
13
|
-
send("parse_#{source}".to_sym, data)
|
9
|
+
def initialize(text)
|
10
|
+
@text = text
|
14
11
|
end
|
15
12
|
|
16
|
-
def
|
17
|
-
|
13
|
+
def self.parse(params)
|
14
|
+
network = params[:network]
|
15
|
+
invalid_network_error(network) unless BotMob.valid_network?(network)
|
16
|
+
|
17
|
+
parser = network_delegate(network)
|
18
|
+
invalid_delegate_error(network) unless parser.respond_to?(:parse)
|
19
|
+
|
20
|
+
parser.parse(params)
|
18
21
|
end
|
19
22
|
|
20
|
-
def
|
21
|
-
|
23
|
+
def arguments
|
24
|
+
@arguments ||= text.to_s[%r{\/[\w'_-]+\s*[\w'_-]+\s*(.*)}, 1]
|
22
25
|
end
|
23
26
|
|
24
|
-
def
|
25
|
-
|
27
|
+
def params
|
28
|
+
@params || { network: network, text: text }
|
26
29
|
end
|
27
30
|
|
28
31
|
private
|
29
32
|
|
30
|
-
def
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
@external_id = data[:user_id]
|
33
|
+
def self.network_delegate(network)
|
34
|
+
BotMob.networks[network].const_get('InboundMessage')
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.invalid_network_error(network)
|
38
|
+
raise BotMob::InvalidNetworkError, "The #{network.inspect} network has not been mounted"
|
37
39
|
end
|
38
40
|
|
39
|
-
def
|
40
|
-
|
41
|
-
@team_id = data['team_id']
|
42
|
-
@team_domain = data['team_domain']
|
43
|
-
@channel_id = data['channel_id']
|
44
|
-
@channel_name = data['channel_name']
|
45
|
-
@external_id = data['user_id']
|
46
|
-
@user_name = data['user_name']
|
47
|
-
@command = data['command']
|
48
|
-
@text = data['text']
|
49
|
-
@response_url = data['response_url']
|
41
|
+
def self.invalid_delegate_error(network)
|
42
|
+
raise BotMob::InvalidInboundMessageError, "The #{network.inspect} inbound message parser must implement the `parse` method"
|
50
43
|
end
|
51
44
|
end
|
52
45
|
end
|
data/lib/bot_mob/install.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module BotMob
|
2
2
|
class Install < ActiveRecord::Base
|
3
|
-
self.table_name = "
|
3
|
+
self.table_name = "bot_mob_installs"
|
4
4
|
|
5
5
|
def self.registered_bot(auth)
|
6
6
|
where(bot_class: auth['bot_class'], external_id: auth['external_id'])
|
@@ -8,13 +8,9 @@ module BotMob
|
|
8
8
|
|
9
9
|
def self.create_with_auth(auth)
|
10
10
|
registered_bot(auth).first_or_initialize.tap do |i|
|
11
|
-
i.
|
11
|
+
i.data = auth['data']
|
12
12
|
i.save!
|
13
13
|
end
|
14
14
|
end
|
15
|
-
|
16
|
-
def auth
|
17
|
-
{ external_id: external_id, token: token }
|
18
|
-
end
|
19
15
|
end
|
20
16
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module BotMob
|
2
|
+
class NilConnection
|
3
|
+
|
4
|
+
def initialize(bot)
|
5
|
+
@bot = bot
|
6
|
+
end
|
7
|
+
|
8
|
+
def deliver(outbound_message, options = {})
|
9
|
+
BotMob.logger.info '! Missing connection attempting to deliver... '
|
10
|
+
BotMob.logger.info " Message sent: #{outbound_message.text.inspect}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def connect
|
14
|
+
BotMob.logger.info '! Missing connection attempting to connect... '
|
15
|
+
end
|
16
|
+
|
17
|
+
def reconnect
|
18
|
+
BotMob.logger.info '! Missing connection attempting to reconnect... '
|
19
|
+
end
|
20
|
+
|
21
|
+
def refresh
|
22
|
+
BotMob.logger.info '! Missing connection attempting to refresh... '
|
23
|
+
end
|
24
|
+
|
25
|
+
def client
|
26
|
+
self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module BotMob
|
2
|
+
# ## BotMob::Outbound Message
|
3
|
+
#
|
4
|
+
# The OutboundMessage class provides a convenient
|
5
|
+
# way to consistently allow your bot to respond
|
6
|
+
# to a given network
|
7
|
+
class OutboundMessage
|
8
|
+
attr_reader :text, :channel_id
|
9
|
+
|
10
|
+
def initialize(data)
|
11
|
+
@text = data[:text]
|
12
|
+
@channel_id = data[:channel_id]
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_h
|
16
|
+
{ channel: channel_id, text: text }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/bot_mob/roster.rb
CHANGED
@@ -7,23 +7,42 @@ module BotMob
|
|
7
7
|
@bots = {}
|
8
8
|
end
|
9
9
|
|
10
|
+
# `connect`
|
11
|
+
#
|
12
|
+
# `connect` receives an auth hash that contains a bot_class
|
13
|
+
# along with any accompanying metadata that the bot will need
|
14
|
+
# to connect to the specified network. Provided by BotMob::Authority
|
15
|
+
# delivered over the wire.
|
16
|
+
#
|
17
|
+
# Example `auth` param:
|
18
|
+
#
|
19
|
+
# {
|
20
|
+
# bot_class: 'FooBot',
|
21
|
+
# network: 'slack',
|
22
|
+
# external_id: 'USER1',
|
23
|
+
# data: {
|
24
|
+
# token: 'abc123'
|
25
|
+
# }
|
26
|
+
# }
|
27
|
+
#
|
10
28
|
def connect(auth)
|
29
|
+
# { network: :dev, external_id: 'foo', bot_class: 'FooBot', data: {} }
|
11
30
|
class_name = auth['bot_class'].to_s
|
12
31
|
bot_class = registry[class_name]
|
13
|
-
|
14
|
-
BotMob.logger.info "
|
32
|
+
raise BotMob::UnregisteredBotError, 'You must explicitly register your bot class with the roster before connecting that bot type' if bot_class.nil?
|
33
|
+
BotMob.logger.info "Selecting #{class_name.inspect} from registry: #{bot_class.inspect}"
|
15
34
|
|
16
|
-
puts "Registry: #{registry.inspect}"
|
17
|
-
puts "Using registered bot: #{bot_class.inspect}"
|
18
35
|
unless auth.is_a?(BotMob::Install)
|
19
|
-
|
20
|
-
|
36
|
+
begin
|
37
|
+
BotMob.logger.info "Updating #{class_name} database record for #{auth['external_id']}..."
|
38
|
+
BotMob::Install.create_with_auth(auth)
|
39
|
+
rescue ActiveRecord::StatementInvalid => e
|
40
|
+
BotMob.logger.info "Unable to save record: #{e.message}"
|
41
|
+
end
|
21
42
|
end
|
22
43
|
|
23
|
-
|
24
|
-
|
25
|
-
@bots["#{bot_class}##{external_id}"] ||= bot
|
26
|
-
@bots["#{bot_class}##{external_id}"].refresh
|
44
|
+
network = auth['network']
|
45
|
+
allocate(bot_class, network, auth)
|
27
46
|
end
|
28
47
|
|
29
48
|
def load
|
@@ -48,8 +67,23 @@ module BotMob
|
|
48
67
|
@registry[bot_class.to_s] = bot_class
|
49
68
|
end
|
50
69
|
|
70
|
+
private
|
71
|
+
|
51
72
|
def available?
|
52
73
|
@available.nil? || @available
|
53
74
|
end
|
75
|
+
|
76
|
+
def allocate(bot_class, network, auth)
|
77
|
+
key = "#{bot_class}##{auth['network']}.#{auth['external_id']}"
|
78
|
+
|
79
|
+
if @bots[key]
|
80
|
+
BotMob.logger.info "Using existing #{key} bot"
|
81
|
+
else
|
82
|
+
BotMob.logger.info "Setting up a new #{bot_class} bot: #{key}"
|
83
|
+
@bots[key] = bot_class.new(auth['network'].to_sym, auth['external_id'], auth['data'] || {})
|
84
|
+
end
|
85
|
+
|
86
|
+
@bots[key].refresh
|
87
|
+
end
|
54
88
|
end
|
55
89
|
end
|
@@ -5,6 +5,10 @@ module BotMob
|
|
5
5
|
@code = code
|
6
6
|
end
|
7
7
|
|
8
|
+
def self.setup(args)
|
9
|
+
new(args[:code])
|
10
|
+
end
|
11
|
+
|
8
12
|
def token
|
9
13
|
oauth_response.bot_access_token
|
10
14
|
end
|
@@ -13,6 +17,10 @@ module BotMob
|
|
13
17
|
oauth_response.bot_user_id
|
14
18
|
end
|
15
19
|
|
20
|
+
def data
|
21
|
+
{ token: token }
|
22
|
+
end
|
23
|
+
|
16
24
|
def success?
|
17
25
|
!!(external_id && token)
|
18
26
|
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module BotMob
|
2
|
+
module Slack
|
3
|
+
class Connection < BotMob::Connection
|
4
|
+
attr_reader :bot, :token
|
5
|
+
|
6
|
+
def initialize(bot, options = {})
|
7
|
+
@bot = bot
|
8
|
+
@token = options['token']
|
9
|
+
token.nil? ? set_state(:inactive) : setup
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.setup(bot, options = {})
|
13
|
+
new(bot, options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def deliver(outbound_message, options = {})
|
17
|
+
BotMob.logger.info(" Sending to Slack: \"#{options.inspect}\"\n")
|
18
|
+
BotMob.logger.info(" Text: \"#{outbound_message.text.inspect}\"\n")
|
19
|
+
client.web_client.chat_postMessage(outbound_message.to_h)
|
20
|
+
end
|
21
|
+
|
22
|
+
def connect
|
23
|
+
return unless active?
|
24
|
+
set_state(:connecting)
|
25
|
+
establish
|
26
|
+
end
|
27
|
+
|
28
|
+
def reconnect
|
29
|
+
return unless active?
|
30
|
+
set_state(:reconnecting)
|
31
|
+
renew_client
|
32
|
+
setup
|
33
|
+
establish
|
34
|
+
end
|
35
|
+
|
36
|
+
def refresh
|
37
|
+
return unless active?
|
38
|
+
|
39
|
+
case true
|
40
|
+
when waiting?
|
41
|
+
connect
|
42
|
+
when disconnected?
|
43
|
+
reconnect
|
44
|
+
else
|
45
|
+
BotMob.logger.info "State[#{bot.class.to_s}##{bot.key}] - No Change"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def welcome_message
|
50
|
+
"Successfully connected, '#{client.team.name}' team at https://#{client.team.domain}.slack.com."
|
51
|
+
end
|
52
|
+
|
53
|
+
def client
|
54
|
+
@client ||= renew_client
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def establish
|
60
|
+
client.start_async
|
61
|
+
rescue ::Slack::Web::Api::Error => e
|
62
|
+
BotMob.logger.info e.message
|
63
|
+
set_state(:waiting)
|
64
|
+
end
|
65
|
+
|
66
|
+
def setup
|
67
|
+
set_state(:waiting)
|
68
|
+
client.on(:hello) { set_state(:connected) }
|
69
|
+
client.on(:close) { set_state(:disconnecting) }
|
70
|
+
client.on(:closed) { set_state(:closed) }
|
71
|
+
|
72
|
+
client.on :message do |data|
|
73
|
+
message = BotMob::InboundMessage.parse({
|
74
|
+
network: :slack,
|
75
|
+
format: :websocket
|
76
|
+
}.merge(data))
|
77
|
+
|
78
|
+
bot.receive message
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def renew_client
|
83
|
+
@client = ::Slack::RealTime::Client.new(token: @token)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module BotMob
|
2
|
+
module Slack
|
3
|
+
class InboundMessage < BotMob::InboundMessage
|
4
|
+
attr_reader :token, :team_id, :team_domain, :params, :format,
|
5
|
+
:channel_id, :channel_name, :external_id, :user_name,
|
6
|
+
:command, :bot_id, :response_url, :ts
|
7
|
+
|
8
|
+
def initialize(params)
|
9
|
+
@format = params[:format] || :websocket
|
10
|
+
@network = params[:network]
|
11
|
+
@params = params
|
12
|
+
send("parse_#{format}".to_sym, @params)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.parse(params)
|
16
|
+
new(params)
|
17
|
+
end
|
18
|
+
|
19
|
+
def human?
|
20
|
+
bot_id.nil?
|
21
|
+
end
|
22
|
+
|
23
|
+
def command?
|
24
|
+
!command.nil?
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def parse_websocket(data)
|
30
|
+
@text = data['text']
|
31
|
+
@channel_id = data['channel_id'] || data['channel']
|
32
|
+
@bot_id = data['bot_id']
|
33
|
+
@team = data['team']
|
34
|
+
@ts = data['ts']
|
35
|
+
@external_id = data['user_id']
|
36
|
+
end
|
37
|
+
|
38
|
+
def parse_webhook(data)
|
39
|
+
@token = data['token']
|
40
|
+
@team_id = data['team_id']
|
41
|
+
@team_domain = data['team_domain']
|
42
|
+
@channel_id = data['channel_id']
|
43
|
+
@channel_name = data['channel_name']
|
44
|
+
@external_id = data['user_id']
|
45
|
+
@user_name = data['user_name']
|
46
|
+
@command = data['command']
|
47
|
+
@text = data['text']
|
48
|
+
@response_url = data['response_url']
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/bot_mob/version.rb
CHANGED
data/lib/bot_mob/wire.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'bunny'
|
2
|
+
|
1
3
|
module BotMob
|
2
4
|
# ## BotMob::Wire
|
3
5
|
#
|
@@ -5,11 +7,26 @@ module BotMob
|
|
5
7
|
# receives notifications from authenticated installs
|
6
8
|
# over RabbitMQ
|
7
9
|
class Wire
|
10
|
+
def initialize
|
11
|
+
@available = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
# ## wire.publish
|
15
|
+
#
|
16
|
+
# The `publish` method takes a given message, converts it to JSON
|
17
|
+
# and pushes the json over the wire on the `wire.notifications` channel.
|
18
|
+
# This is primarily used for passing successful authentication to the
|
19
|
+
# running BotMob Application.
|
8
20
|
def publish(message = {})
|
9
21
|
BotMob.logger.info "Publishing #{message} to wire.notifications"
|
10
22
|
queue.publish(message.to_json, routing_key: queue.name)
|
11
23
|
end
|
12
24
|
|
25
|
+
# ## wire.listen
|
26
|
+
#
|
27
|
+
# The `listen` method takes a block to perform when a
|
28
|
+
# message is received over the wire. The message published
|
29
|
+
# to the wire is parsed JSON provided to the calling block.
|
13
30
|
def listen
|
14
31
|
return if !block_given? || queue.nil?
|
15
32
|
|
@@ -20,8 +37,9 @@ module BotMob
|
|
20
37
|
BotMob.logger.info "#{e.channel_close.reply_code}, message: #{e.channel_close.reply_text}"
|
21
38
|
end
|
22
39
|
|
40
|
+
|
23
41
|
def available?
|
24
|
-
|
42
|
+
!!@available
|
25
43
|
end
|
26
44
|
|
27
45
|
private
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bot_mob
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew Werner
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-08-
|
11
|
+
date: 2016-08-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bunny
|
@@ -229,10 +229,19 @@ files:
|
|
229
229
|
- lib/bot_mob/authority.rb
|
230
230
|
- lib/bot_mob/bot.rb
|
231
231
|
- lib/bot_mob/connection.rb
|
232
|
+
- lib/bot_mob/development/ambassador.rb
|
233
|
+
- lib/bot_mob/development/connection.rb
|
234
|
+
- lib/bot_mob/development/inbound_message.rb
|
235
|
+
- lib/bot_mob/errors.rb
|
232
236
|
- lib/bot_mob/inbound_message.rb
|
233
237
|
- lib/bot_mob/install.rb
|
238
|
+
- lib/bot_mob/nil_connection.rb
|
239
|
+
- lib/bot_mob/outbound_message.rb
|
234
240
|
- lib/bot_mob/roster.rb
|
241
|
+
- lib/bot_mob/slack.rb
|
235
242
|
- lib/bot_mob/slack/ambassador.rb
|
243
|
+
- lib/bot_mob/slack/connection.rb
|
244
|
+
- lib/bot_mob/slack/inbound_message.rb
|
236
245
|
- lib/bot_mob/version.rb
|
237
246
|
- lib/bot_mob/wire.rb
|
238
247
|
homepage: https://github.com/mwerner/bot_mob
|