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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.env +1 -0
  3. data/.gitignore +0 -1
  4. data/.rubocop.yml +16 -0
  5. data/.travis.yml +1 -1
  6. data/Gemfile +0 -2
  7. data/LICENSE.txt +21 -0
  8. data/README.md +5 -4
  9. data/bin/console +8 -4
  10. data/bin/mob +4 -0
  11. data/bot_mob.gemspec +8 -14
  12. data/examples/fizz_buzz_bot.rb +30 -0
  13. data/examples/hello_bot.rb +14 -0
  14. data/examples/server.rb +7 -0
  15. data/lib/bot_mob.rb +46 -32
  16. data/lib/bot_mob/bot.rb +146 -29
  17. data/lib/bot_mob/command.rb +38 -0
  18. data/lib/bot_mob/connection.rb +18 -50
  19. data/lib/bot_mob/core_ext/hash.rb +148 -0
  20. data/lib/bot_mob/core_ext/string.rb +8 -0
  21. data/lib/bot_mob/environment.rb +25 -0
  22. data/lib/bot_mob/inbound_message.rb +13 -32
  23. data/lib/bot_mob/networks/roaming.rb +12 -0
  24. data/lib/bot_mob/networks/roaming/connection.rb +34 -0
  25. data/lib/bot_mob/networks/roaming/outbound_message.rb +15 -0
  26. data/lib/bot_mob/networks/slack.rb +11 -0
  27. data/lib/bot_mob/networks/slack/connection.rb +37 -0
  28. data/lib/bot_mob/networks/slack/outbound_message.rb +18 -0
  29. data/lib/bot_mob/outbound_message.rb +23 -10
  30. data/lib/bot_mob/public/console.js +10 -0
  31. data/lib/bot_mob/roster.rb +8 -80
  32. data/lib/bot_mob/server.rb +47 -0
  33. data/lib/bot_mob/version.rb +1 -1
  34. data/lib/bot_mob/views/authorize.erb +1 -0
  35. data/lib/bot_mob/views/console.erb +6 -0
  36. data/lib/bot_mob/views/index.erb +1 -0
  37. data/lib/bot_mob/views/layout.erb +11 -0
  38. metadata +50 -110
  39. data/lib/bot_mob/ambassador.rb +0 -34
  40. data/lib/bot_mob/application.rb +0 -43
  41. data/lib/bot_mob/authority.rb +0 -41
  42. data/lib/bot_mob/development/ambassador.rb +0 -21
  43. data/lib/bot_mob/development/connection.rb +0 -34
  44. data/lib/bot_mob/development/inbound_message.rb +0 -17
  45. data/lib/bot_mob/errors.rb +0 -12
  46. data/lib/bot_mob/install.rb +0 -16
  47. data/lib/bot_mob/nil_connection.rb +0 -29
  48. data/lib/bot_mob/slack.rb +0 -10
  49. data/lib/bot_mob/slack/ambassador.rb +0 -58
  50. data/lib/bot_mob/slack/connection.rb +0 -87
  51. data/lib/bot_mob/slack/inbound_message.rb +0 -52
  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
@@ -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 self.setup(network, bot, options = {})
10
- invalid_network_error(network) unless BotMob.valid_network?(network)
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 deliver(outbound_message, options = {})
19
- # connection.deliver(outbound_message, options)
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 closed?
49
- @state == :closed
18
+ def deliver(outbound_message, _options = {})
19
+ # deliver(outbound_message, options)
20
+ # noop
21
+ outbound_message
50
22
  end
51
23
 
52
- def set_state(state)
53
- BotMob.logger.info welcome_message if state == :connected
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
- def self.invalid_delegate_error(network)
64
- raise BotMob::InvalidConnectionError, "The #{network.inspect} connection must implement the `setup` method"
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,8 @@
1
+ # String - Core Extensions
2
+ class String
3
+ def camelize
4
+ string = self
5
+ string = string.sub(/^[a-z\d]*/) { $&.capitalize }
6
+ string.gsub(/(?:_|(\/))([a-z\d]*)/) { "#{$1}#{$2.capitalize}" }.gsub('/', '::')
7
+ end
8
+ 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
- # ## BotMob::InboundMessage
2
+ # # BotMob::InboundMessage
3
3
  #
4
- # The InboundMessage class provides easy access to
5
- # messages received by bots in your application
4
+ # Structured data provided to a bot
6
5
  class InboundMessage
7
- attr_reader :network, :text, :params
6
+ attr_reader :body, :network
8
7
 
9
- def initialize(text)
10
- @text = text
8
+ def initialize(**options)
9
+ @network = options[:network]
10
+ @body = options[:body]
11
11
  end
12
12
 
13
- def self.parse(params)
14
- network = params[:network]
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
- parser = network_delegate(network)
18
- invalid_delegate_error(network) unless parser.respond_to?(:parse)
19
-
20
- parser.parse(params)
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 || { network: network, text: text }
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