bot_mob 0.2.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.env +1 -0
- data/.gitignore +0 -1
- data/.rubocop.yml +16 -0
- data/.travis.yml +1 -1
- data/Gemfile +0 -2
- data/LICENSE.txt +21 -0
- data/README.md +5 -4
- data/bin/console +8 -4
- data/bin/mob +4 -0
- data/bot_mob.gemspec +8 -14
- data/examples/fizz_buzz_bot.rb +30 -0
- data/examples/hello_bot.rb +14 -0
- data/examples/server.rb +7 -0
- data/lib/bot_mob.rb +46 -32
- data/lib/bot_mob/bot.rb +146 -29
- data/lib/bot_mob/command.rb +38 -0
- data/lib/bot_mob/connection.rb +18 -50
- data/lib/bot_mob/core_ext/hash.rb +148 -0
- data/lib/bot_mob/core_ext/string.rb +8 -0
- data/lib/bot_mob/environment.rb +25 -0
- data/lib/bot_mob/inbound_message.rb +13 -32
- data/lib/bot_mob/networks/roaming.rb +12 -0
- data/lib/bot_mob/networks/roaming/connection.rb +34 -0
- data/lib/bot_mob/networks/roaming/outbound_message.rb +15 -0
- data/lib/bot_mob/networks/slack.rb +11 -0
- data/lib/bot_mob/networks/slack/connection.rb +37 -0
- data/lib/bot_mob/networks/slack/outbound_message.rb +18 -0
- data/lib/bot_mob/outbound_message.rb +23 -10
- data/lib/bot_mob/public/console.js +10 -0
- data/lib/bot_mob/roster.rb +8 -80
- data/lib/bot_mob/server.rb +47 -0
- data/lib/bot_mob/version.rb +1 -1
- data/lib/bot_mob/views/authorize.erb +1 -0
- data/lib/bot_mob/views/console.erb +6 -0
- data/lib/bot_mob/views/index.erb +1 -0
- data/lib/bot_mob/views/layout.erb +11 -0
- metadata +50 -110
- data/lib/bot_mob/ambassador.rb +0 -34
- data/lib/bot_mob/application.rb +0 -43
- data/lib/bot_mob/authority.rb +0 -41
- data/lib/bot_mob/development/ambassador.rb +0 -21
- data/lib/bot_mob/development/connection.rb +0 -34
- data/lib/bot_mob/development/inbound_message.rb +0 -17
- data/lib/bot_mob/errors.rb +0 -12
- data/lib/bot_mob/install.rb +0 -16
- data/lib/bot_mob/nil_connection.rb +0 -29
- data/lib/bot_mob/slack.rb +0 -10
- data/lib/bot_mob/slack/ambassador.rb +0 -58
- data/lib/bot_mob/slack/connection.rb +0 -87
- data/lib/bot_mob/slack/inbound_message.rb +0 -52
- data/lib/bot_mob/wire.rb +0 -69
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module BotMob
|
4
|
+
# # BotMob::Command
|
5
|
+
#
|
6
|
+
# This class handles all the `mob` command line interactions
|
7
|
+
class Command
|
8
|
+
def initialize
|
9
|
+
# parse_options!
|
10
|
+
end
|
11
|
+
|
12
|
+
def handle(argv)
|
13
|
+
case argv[0].to_s.to_sym
|
14
|
+
when :server then BotMob::Server.run!(@options || {})
|
15
|
+
when :console then system("#{__dir__}/../../bin/console")
|
16
|
+
else
|
17
|
+
puts 'Usage: mob server [options]'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# private
|
22
|
+
|
23
|
+
# def parse_options!
|
24
|
+
# options = {}
|
25
|
+
# OptionParser.new do |opts|
|
26
|
+
# opts.on('-d', '--[no-]daemon', 'Run process as a daemon') do |opt|
|
27
|
+
# options[:daemon] = opt
|
28
|
+
# end
|
29
|
+
|
30
|
+
# opts.on('-v', '--[no-]verbose', 'Run verbosely') do |opt|
|
31
|
+
# options[:verbose] = opt
|
32
|
+
# end
|
33
|
+
# end.parse!
|
34
|
+
|
35
|
+
# @options = options
|
36
|
+
# end
|
37
|
+
end
|
38
|
+
end
|
data/lib/bot_mob/connection.rb
CHANGED
@@ -4,64 +4,32 @@ module BotMob
|
|
4
4
|
# The Connection is the point of contact
|
5
5
|
# between the Bot and the specified network
|
6
6
|
class Connection
|
7
|
-
attr_reader :bot
|
7
|
+
attr_reader :bot, :options
|
8
8
|
|
9
|
-
def
|
10
|
-
|
11
|
-
|
12
|
-
connection = network_delegate(network)
|
13
|
-
invalid_delegate_error(network) unless connection.respond_to?(:setup)
|
14
|
-
|
15
|
-
connection.setup(bot, options)
|
9
|
+
def initialize(bot, **options)
|
10
|
+
@bot = bot
|
11
|
+
@options = options
|
16
12
|
end
|
17
13
|
|
18
|
-
def
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
def self.network_delegate(network)
|
25
|
-
BotMob.networks[network].const_get('Connection')
|
26
|
-
end
|
27
|
-
|
28
|
-
def welcome_message
|
29
|
-
"Connected!"
|
30
|
-
end
|
31
|
-
|
32
|
-
def active?
|
33
|
-
@state != :inactive
|
34
|
-
end
|
35
|
-
|
36
|
-
def waiting?
|
37
|
-
@state == :waiting
|
38
|
-
end
|
39
|
-
|
40
|
-
def disconnected?
|
41
|
-
@state != :connected
|
42
|
-
end
|
43
|
-
|
44
|
-
def connected?
|
45
|
-
@state == :connected
|
14
|
+
def self.setup(network, bot, options = {})
|
15
|
+
connection_delegate(network).new(bot, options)
|
46
16
|
end
|
47
17
|
|
48
|
-
def
|
49
|
-
|
18
|
+
def deliver(outbound_message, _options = {})
|
19
|
+
# deliver(outbound_message, options)
|
20
|
+
# noop
|
21
|
+
outbound_message
|
50
22
|
end
|
51
23
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
BotMob.logger.info "State[#{bot.class.to_s}##{bot.key}] - #{state}"
|
56
|
-
@state = state
|
57
|
-
end
|
58
|
-
|
59
|
-
def self.invalid_network_error(network)
|
60
|
-
raise BotMob::InvalidNetworkError, "The #{network.inspect} network has not been mounted"
|
61
|
-
end
|
24
|
+
class << self
|
25
|
+
private
|
62
26
|
|
63
|
-
|
64
|
-
|
27
|
+
def connection_delegate(network)
|
28
|
+
delegate = BotMob::Networks.const_get(network.to_s.camelize)
|
29
|
+
delegate.const_get('Connection')
|
30
|
+
rescue NameError
|
31
|
+
raise BotMob::InvalidNetworkError
|
32
|
+
end
|
65
33
|
end
|
66
34
|
end
|
67
35
|
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# Hash - Core Extensions
|
2
|
+
class Hash
|
3
|
+
# Returns a new hash with all keys converted using the +block+ operation.
|
4
|
+
#
|
5
|
+
# hash = { name: 'Rob', age: '28' }
|
6
|
+
#
|
7
|
+
# hash.transform_keys { |key| key.to_s.upcase } # => {"NAME"=>"Rob", "AGE"=>"28"}
|
8
|
+
#
|
9
|
+
# If you do not provide a +block+, it will return an Enumerator
|
10
|
+
# for chaining with other methods:
|
11
|
+
#
|
12
|
+
# hash.transform_keys.with_index { |k, i| [k, i].join } # => {"name0"=>"Rob", "age1"=>"28"}
|
13
|
+
def transform_keys
|
14
|
+
return enum_for(:transform_keys) { size } unless block_given?
|
15
|
+
result = self.class.new
|
16
|
+
each_key do |key|
|
17
|
+
result[yield(key)] = self[key]
|
18
|
+
end
|
19
|
+
result
|
20
|
+
end
|
21
|
+
|
22
|
+
# Destructively converts all keys using the +block+ operations.
|
23
|
+
# Same as +transform_keys+ but modifies +self+.
|
24
|
+
def transform_keys!
|
25
|
+
return enum_for(:transform_keys!) { size } unless block_given?
|
26
|
+
keys.each do |key|
|
27
|
+
self[yield(key)] = delete(key)
|
28
|
+
end
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns a new hash with all keys converted to strings.
|
33
|
+
#
|
34
|
+
# hash = { name: 'Rob', age: '28' }
|
35
|
+
#
|
36
|
+
# hash.stringify_keys
|
37
|
+
# # => {"name"=>"Rob", "age"=>"28"}
|
38
|
+
def stringify_keys
|
39
|
+
transform_keys(&:to_s)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Destructively converts all keys to strings. Same as
|
43
|
+
# +stringify_keys+, but modifies +self+.
|
44
|
+
def stringify_keys!
|
45
|
+
transform_keys!(&:to_s)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns a new hash with all keys converted to symbols, as long as
|
49
|
+
# they respond to +to_sym+.
|
50
|
+
#
|
51
|
+
# hash = { 'name' => 'Rob', 'age' => '28' }
|
52
|
+
#
|
53
|
+
# hash.symbolize_keys
|
54
|
+
# # => {name: "Rob", age: "28"}
|
55
|
+
def symbolize_keys
|
56
|
+
transform_keys { |key| key.to_sym rescue key }
|
57
|
+
end
|
58
|
+
alias_method :to_options, :symbolize_keys
|
59
|
+
|
60
|
+
# Destructively converts all keys to symbols, as long as they respond
|
61
|
+
# to +to_sym+. Same as +symbolize_keys+, but modifies +self+.
|
62
|
+
def symbolize_keys!
|
63
|
+
transform_keys! { |key| key.to_sym rescue key }
|
64
|
+
end
|
65
|
+
alias_method :to_options!, :symbolize_keys!
|
66
|
+
|
67
|
+
# Returns a new hash with all keys converted by the block operation.
|
68
|
+
# This includes the keys from the root hash and from all
|
69
|
+
# nested hashes and arrays.
|
70
|
+
#
|
71
|
+
# hash = { person: { name: 'Rob', age: '28' } }
|
72
|
+
#
|
73
|
+
# hash.deep_transform_keys{ |key| key.to_s.upcase }
|
74
|
+
# # => {"PERSON"=>{"NAME"=>"Rob", "AGE"=>"28"}}
|
75
|
+
def deep_transform_keys(&block)
|
76
|
+
_deep_transform_keys_in_object(self, &block)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Destructively converts all keys by using the block operation.
|
80
|
+
# This includes the keys from the root hash and from all
|
81
|
+
# nested hashes and arrays.
|
82
|
+
def deep_transform_keys!(&block)
|
83
|
+
_deep_transform_keys_in_object!(self, &block)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns a new hash with all keys converted to strings.
|
87
|
+
# This includes the keys from the root hash and from all
|
88
|
+
# nested hashes and arrays.
|
89
|
+
#
|
90
|
+
# hash = { person: { name: 'Rob', age: '28' } }
|
91
|
+
#
|
92
|
+
# hash.deep_stringify_keys
|
93
|
+
# # => {"person"=>{"name"=>"Rob", "age"=>"28"}}
|
94
|
+
def deep_stringify_keys
|
95
|
+
deep_transform_keys(&:to_s)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Destructively converts all keys to strings.
|
99
|
+
# This includes the keys from the root hash and from all
|
100
|
+
# nested hashes and arrays.
|
101
|
+
def deep_stringify_keys!
|
102
|
+
deep_transform_keys!(&:to_s)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns a new hash with all keys converted to symbols, as long as
|
106
|
+
# they respond to +to_sym+. This includes the keys from the root hash
|
107
|
+
# and from all nested hashes and arrays.
|
108
|
+
#
|
109
|
+
# hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }
|
110
|
+
#
|
111
|
+
# hash.deep_symbolize_keys
|
112
|
+
# # => {person: {name: "Rob", age: "28"}}
|
113
|
+
def deep_symbolize_keys
|
114
|
+
deep_transform_keys { |key| key.to_sym rescue key }
|
115
|
+
end
|
116
|
+
|
117
|
+
# Destructively converts all keys to symbols, as long as they respond
|
118
|
+
# to +to_sym+. This includes the keys from the root hash and from all
|
119
|
+
# nested hashes and arrays.
|
120
|
+
def deep_symbolize_keys!
|
121
|
+
deep_transform_keys! { |key| key.to_sym rescue key }
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
# support methods for deep transforming nested hashes and arrays
|
127
|
+
def _deep_transform_keys_in_object(object, &block)
|
128
|
+
case object
|
129
|
+
when Hash
|
130
|
+
object.each_with_object({}) do |(key, value), result|
|
131
|
+
result[yield(key)] = _deep_transform_keys_in_object(value, &block)
|
132
|
+
end
|
133
|
+
else
|
134
|
+
object
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def _deep_transform_keys_in_object!(object, &block)
|
139
|
+
case object
|
140
|
+
when Hash
|
141
|
+
object.keys.each do |key|
|
142
|
+
value = object.delete(key)
|
143
|
+
object[yield(key)] = _deep_transform_keys_in_object!(value, &block)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
object
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module BotMob
|
2
|
+
# ## BotMob::Environment
|
3
|
+
#
|
4
|
+
# Provide convenience methods for tracking current
|
5
|
+
# BotMob runtime environment. Set using the environment
|
6
|
+
# variable: `ENV['MOB_ENV']`
|
7
|
+
class Environment
|
8
|
+
def initialize(env = nil)
|
9
|
+
@env = env || 'development'
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
@env
|
14
|
+
end
|
15
|
+
|
16
|
+
def respond_to_missing?(method_name, include_private = false)
|
17
|
+
method_name.to_s[/(.*)\?$/, 1] || super
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(method_name, *args)
|
21
|
+
environment = method_name.to_s[/(.*)\?$/, 1]
|
22
|
+
environment ? @env == environment : super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,45 +1,26 @@
|
|
1
1
|
module BotMob
|
2
|
-
#
|
2
|
+
# # BotMob::InboundMessage
|
3
3
|
#
|
4
|
-
#
|
5
|
-
# messages received by bots in your application
|
4
|
+
# Structured data provided to a bot
|
6
5
|
class InboundMessage
|
7
|
-
attr_reader :
|
6
|
+
attr_reader :body, :network
|
8
7
|
|
9
|
-
def initialize(
|
10
|
-
@
|
8
|
+
def initialize(**options)
|
9
|
+
@network = options[:network]
|
10
|
+
@body = options[:body]
|
11
11
|
end
|
12
12
|
|
13
|
-
def self.
|
14
|
-
|
15
|
-
invalid_network_error(network) unless BotMob.valid_network?(network)
|
13
|
+
def self.prepare(message, options = {})
|
14
|
+
return message if message.is_a?(BotMob::InboundMessage)
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
end
|
22
|
-
|
23
|
-
def arguments
|
24
|
-
@arguments ||= text.to_s[%r{\/[\w'_-]+\s*[\w'_-]+\s*(.*)}, 1]
|
16
|
+
BotMob::InboundMessage.new(
|
17
|
+
network: options[:network] || :roaming,
|
18
|
+
body: message
|
19
|
+
)
|
25
20
|
end
|
26
21
|
|
27
22
|
def params
|
28
|
-
@params || {
|
29
|
-
end
|
30
|
-
|
31
|
-
private
|
32
|
-
|
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"
|
39
|
-
end
|
40
|
-
|
41
|
-
def self.invalid_delegate_error(network)
|
42
|
-
raise BotMob::InvalidInboundMessageError, "The #{network.inspect} inbound message parser must implement the `parse` method"
|
23
|
+
@params || { body: body }
|
43
24
|
end
|
44
25
|
end
|
45
26
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module BotMob
|
2
|
+
module Networks
|
3
|
+
# ## BotMob::Networks::Roaming
|
4
|
+
#
|
5
|
+
# The default network for a bot that has not yet connected to
|
6
|
+
# an external network
|
7
|
+
module Roaming
|
8
|
+
autoload :Connection, 'bot_mob/networks/roaming/connection'
|
9
|
+
autoload :OutboundMessage, 'bot_mob/networks/roaming/outbound_message'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module BotMob
|
2
|
+
module Networks
|
3
|
+
module Roaming
|
4
|
+
# ## BotMob::Networks::Roaming::Connection
|
5
|
+
#
|
6
|
+
# The Connection is the point of contact
|
7
|
+
# between the Bot and the specified network
|
8
|
+
class Connection < BotMob::Connection
|
9
|
+
attr_reader :bot
|
10
|
+
|
11
|
+
def deliver(outbound_message, _options = {})
|
12
|
+
BotMob.logger.info " Roaming Connection Sent: #{outbound_message.inspect}"
|
13
|
+
outbound_message
|
14
|
+
end
|
15
|
+
|
16
|
+
def connect
|
17
|
+
BotMob.logger.info 'Connecting... '
|
18
|
+
end
|
19
|
+
|
20
|
+
def reconnect
|
21
|
+
BotMob.logger.info 'Reconnecting... '
|
22
|
+
end
|
23
|
+
|
24
|
+
def refresh
|
25
|
+
BotMob.logger.info 'Refreshing... '
|
26
|
+
end
|
27
|
+
|
28
|
+
def client
|
29
|
+
self
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module BotMob
|
2
|
+
module Networks
|
3
|
+
module Roaming
|
4
|
+
# # BotMob::Networks::Roaming::OutboundMessage
|
5
|
+
#
|
6
|
+
# Formatted message for default :roaming delivery
|
7
|
+
class OutboundMessage < BotMob::OutboundMessage
|
8
|
+
def initialize(**options)
|
9
|
+
@network = :roaming
|
10
|
+
super
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module BotMob
|
2
|
+
module Networks
|
3
|
+
# ## BotMob::Networks::Slack
|
4
|
+
#
|
5
|
+
# Network connection adapters to work with Slack
|
6
|
+
module Slack
|
7
|
+
autoload :Connection, 'bot_mob/networks/slack/connection'
|
8
|
+
autoload :OutboundMessage, 'bot_mob/networks/slack/outbound_message'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module BotMob
|
2
|
+
module Networks
|
3
|
+
module Slack
|
4
|
+
# # BotMob::Networks::Slack::Connection
|
5
|
+
#
|
6
|
+
# Handles all the connection details between a bot and Slack
|
7
|
+
class Connection < BotMob::Connection
|
8
|
+
attr_reader :bot, :token
|
9
|
+
|
10
|
+
def initialize(bot, options = {})
|
11
|
+
@bot = bot
|
12
|
+
@token = options[:token]
|
13
|
+
raise BotMob::NetworkConnectionError, 'Slack connection requires a :token parameter' if @token.nil?
|
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.body.inspect}\"\n")
|
19
|
+
|
20
|
+
client.chat_postMessage(
|
21
|
+
channel: outbound_message.channel,
|
22
|
+
text: outbound_message.body,
|
23
|
+
as_user: true
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def client
|
28
|
+
@client ||= begin
|
29
|
+
client = ::Slack::Web::Client.new(token: token)
|
30
|
+
client.auth_test
|
31
|
+
client
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|