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.
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