bot_mob 0.2.2 → 0.3.0
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/.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
|