bot_mob 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|