bot_mob 0.3.0 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6463fee5649865aa2f59e0e19b7b3670ff5e20c8
4
- data.tar.gz: e96b710afa365c7b9152a2fd6b5af76dec618a0f
3
+ metadata.gz: 1b85965cd3ed7759eb4a7d79ca601dc94536cf04
4
+ data.tar.gz: 22edb2f9215b9e1414dab19908b5d33001897e65
5
5
  SHA512:
6
- metadata.gz: a7172822c373e804dfe65fb589ddf12bd78403f8416e2b91ce2bf9269268cce4f36039d452d1a9ee5c880ae30372eecb8d8caea8c953e40956fc6ac94ca8faa3
7
- data.tar.gz: 2c70c6c01e9bb1d4c6a6537ce889e5774bf486c2feac444bc75c64a61eeb1375b20c74e056c9efa046b0aae87f6fa34b3c55550e61380132cbc2741e31954163
6
+ metadata.gz: 82652bacc7af4349f248d6674c480fc96ee0ab6c682a70c7147099808a40c01a0c2b24d31c725cf8725b20a6b2e8e651aee65c124978597f773b9eb503331164
7
+ data.tar.gz: 7dbfbfd3c16abb88e1230e7227138b1741d47256753d3ebff5cc31fe7632c72739e3bfe2740fc992cb189d7b054c18b5612d6ba32c9dee31312b54e9e342ddd2
data/examples/server.rb CHANGED
@@ -1,7 +1,10 @@
1
+ require 'dotenv'
1
2
  require_relative 'fizz_buzz_bot'
2
3
  require_relative 'hello_bot'
3
4
 
5
+ Dotenv.load
6
+
4
7
  BotMob.start! do |app|
5
8
  app.register FizzBuzzBot
6
- app.register HelloBot.new(network: :slack, token: ENV['SLACK_TOKEN'])
9
+ app.register HelloBot.new(slack: { token: ENV['SLACK_TOKEN'] })
7
10
  end
data/lib/bot_mob.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require 'logger'
2
- require 'slack-ruby-client'
3
2
  require 'bot_mob/core_ext/hash'
4
3
  require 'bot_mob/core_ext/string'
5
4
  require 'bot_mob/version'
@@ -19,6 +18,7 @@ module BotMob
19
18
  autoload :Command, 'bot_mob/command'
20
19
  autoload :Connection, 'bot_mob/connection'
21
20
  autoload :InboundMessage, 'bot_mob/inbound_message'
21
+ autoload :Logger, 'bot_mob/logger'
22
22
  autoload :OutboundMessage, 'bot_mob/outbound_message'
23
23
  autoload :Roster, 'bot_mob/roster'
24
24
  autoload :Server, 'bot_mob/server'
@@ -61,7 +61,7 @@ module BotMob
61
61
  # Provide a logger for the BotMob gem, silences test env output by default
62
62
  def self.logger
63
63
  @logger ||= begin
64
- buffer = Logger.new(ENV['MOB_ENV'] == 'test' ? '/dev/null' : STDOUT)
64
+ buffer = BotMob::Logger.new(ENV['MOB_ENV'] == 'test' ? '/dev/null' : STDOUT)
65
65
  if buffer
66
66
  buffer.level = ($PROGRAM_NAME == 'irb' ? Logger::DEBUG : Logger::INFO)
67
67
  buffer.formatter = proc { |_s, _d, _p, msg| "#{msg}\n" }
data/lib/bot_mob/bot.rb CHANGED
@@ -56,13 +56,10 @@ module BotMob
56
56
  def receive(message, options = {})
57
57
  inbound_message = BotMob::InboundMessage.prepare(message, options)
58
58
 
59
- warrant_response = respond?(inbound_message)
60
- log_message(inbound_message) if warrant_response || log_responseless_activity?
61
- unless warrant_response
62
- if log_responseless_activity?
63
- BotMob.logger.info(' > Does not warrant response')
64
- end
65
-
59
+ BotMob.logger.log_request(self, :message, inbound_message.params)
60
+ BotMob.logger.info('-> Received message was from a bot') unless inbound_message.human?
61
+ unless respond?(inbound_message)
62
+ BotMob.logger.info('-> Does not warrant response')
66
63
  return
67
64
  end
68
65
 
@@ -77,11 +74,18 @@ module BotMob
77
74
  # bot.receive('foo')
78
75
  #
79
76
  # Specify *message* as the received data
80
- def receive_command(command)
81
- BotMob.logger.info("#{name} received command at #{Time.now}")
82
- BotMob.logger.info(" Parameters: {:text => \"#{command}\"}")
77
+ def receive_command(command, params = {})
78
+ BotMob.logger.log_request(self, :command, command: command)
79
+
80
+ return unless valid_command?(command)
83
81
 
84
- send(command) if valid_command?(command)
82
+ begin
83
+ send(command, params)
84
+ rescue ArgumentError => e
85
+ BotMob.logger.warn("#{name}##{command} is not accepting parameters")
86
+ BotMob.logger.warn("ArgumentError - #{e.message}")
87
+ send(command)
88
+ end
85
89
  end
86
90
 
87
91
  # ## `valid_command?`
@@ -106,9 +110,9 @@ module BotMob
106
110
  # contents of the inbound message. Commonly used for checking language
107
111
  # used or regex matching of the inbound messages' contents.
108
112
  #
109
- # Defaults to true
110
- def respond?(_inbound_message)
111
- true
113
+ # Defaults to true for human messages
114
+ def respond?(inbound_message)
115
+ inbound_message.human?
112
116
  end
113
117
 
114
118
  # ## `respond`
@@ -161,10 +165,5 @@ module BotMob
161
165
  def log_responseless_activity?
162
166
  BotMob.env.development?
163
167
  end
164
-
165
- def log_message(message)
166
- BotMob.logger.info("#{name} received message at #{Time.now}")
167
- BotMob.logger.info(" Parameters: #{message.params}")
168
- end
169
168
  end
170
169
  end
@@ -3,24 +3,102 @@ module BotMob
3
3
  #
4
4
  # Structured data provided to a bot
5
5
  class InboundMessage
6
- attr_reader :body, :network
6
+ attr_reader :user_name, :body, :network
7
7
 
8
8
  def initialize(**options)
9
- @network = options[:network]
10
- @body = options[:body]
9
+ @body ||= options[:body]
10
+ @network ||= options[:network]
11
+
12
+ provided_message_attributes(options).each do |attr|
13
+ instance_variable_set("@#{attr}".to_sym, options[attr.to_sym])
14
+ end
11
15
  end
12
16
 
17
+ # ## `prepare`
18
+ #
19
+ # Prepare accepts three different data types and attempts to coerce
20
+ # it into a contextual inbound message. In the case of an inbound
21
+ # message, it simply assumes the provided message is already correct.
22
+ # In the case of a hash, it passes the hash along as the `initialize`
23
+ # attrs. In the case of a string, it overrides the `body` key of the
24
+ # provided options, then passes the options provided to `prepare` as
25
+ # the `initialize` params
13
26
  def self.prepare(message, options = {})
14
27
  return message if message.is_a?(BotMob::InboundMessage)
28
+ options.symbolize_keys!
29
+
30
+ approach = "prepare_#{message.class.to_s.downcase}_attrs".to_sym
31
+ attrs = send(approach, message, options)
32
+ message_delegate(attrs[:network]).new(attrs)
33
+ end
15
34
 
16
- BotMob::InboundMessage.new(
17
- network: options[:network] || :roaming,
18
- body: message
19
- )
35
+ # ## `human?`
36
+ #
37
+ # This should be overrode in the inheriting bot class. Use this
38
+ # to determine if the inbound message was written by a human
39
+ def human?
40
+ ENV['MOB_NAME'] != user_name
20
41
  end
21
42
 
43
+ # ## `params`
44
+ #
45
+ # A representation of the relevant data attached to a bot
22
46
  def params
23
- @params || { body: body }
47
+ @params ||= {
48
+ body: body,
49
+ network: network,
50
+ user_name: user_name
51
+ }
52
+ end
53
+
54
+ class << self
55
+ def message_attrs(*attrs)
56
+ @message_attrs = attrs
57
+ attr_reader(*@message_attrs)
58
+ end
59
+
60
+ def message_attributes
61
+ @message_attrs || []
62
+ end
63
+
64
+ def default_user_name
65
+ ENV['MOB_NAME'] || 'nomad'
66
+ end
67
+
68
+ def default_network
69
+ :roaming
70
+ end
71
+
72
+ private
73
+
74
+ def prepare_hash_attrs(message, options)
75
+ attrs = message.symbolize_keys.merge(options)
76
+ attrs.merge(
77
+ network: attrs[:network] || default_network,
78
+ user_name: attrs[:user_name] || default_user_name
79
+ )
80
+ end
81
+
82
+ def prepare_string_attrs(message, options)
83
+ options.merge(
84
+ network: options[:network] || default_network,
85
+ user_name: options[:user_name] || default_user_name,
86
+ body: message
87
+ )
88
+ end
89
+
90
+ def message_delegate(network)
91
+ delegate = BotMob::Networks.const_get(network.to_s.camelize)
92
+ delegate.const_get('InboundMessage')
93
+ rescue NameError
94
+ raise BotMob::InvalidNetworkError
95
+ end
96
+ end
97
+
98
+ private
99
+
100
+ def provided_message_attributes(options)
101
+ self.class.message_attributes.select { |attr| options[attr.to_sym] }
24
102
  end
25
103
  end
26
104
  end
@@ -0,0 +1,22 @@
1
+ module BotMob
2
+ # # BotMob::Logger
3
+ #
4
+ # Wrapper for the logger implemented by BotMob
5
+ class Logger < ::Logger
6
+ OMITTED_PARAMS = %w(splat captures).freeze
7
+
8
+ def log_request(source, action = :request, params = {})
9
+ source_network = params[:network] || 'roaming'
10
+ ts = Time.now.strftime('%Y-%m-%d %H:%M:%S%Z')
11
+
12
+ info "\n#{source.name} received #{action} from :#{source_network} at #{ts}"
13
+ info "Parameters: #{logged_params(params).inspect}"
14
+ end
15
+
16
+ private
17
+
18
+ def logged_params(params)
19
+ params.reject { |k, _| OMITTED_PARAMS.include?(k) }
20
+ end
21
+ end
22
+ end
@@ -5,7 +5,8 @@ module BotMob
5
5
  # The default network for a bot that has not yet connected to
6
6
  # an external network
7
7
  module Roaming
8
- autoload :Connection, 'bot_mob/networks/roaming/connection'
8
+ autoload :Connection, 'bot_mob/networks/roaming/connection'
9
+ autoload :InboundMessage, 'bot_mob/networks/roaming/inbound_message'
9
10
  autoload :OutboundMessage, 'bot_mob/networks/roaming/outbound_message'
10
11
  end
11
12
  end
@@ -0,0 +1,15 @@
1
+ module BotMob
2
+ module Networks
3
+ module Roaming
4
+ # # BotMob::InboundMessage
5
+ #
6
+ # Structured data provided to a bot without a connected network
7
+ class InboundMessage < BotMob::InboundMessage
8
+ def initialize(**options)
9
+ @network = :roaming
10
+ super
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,10 +1,13 @@
1
+ require 'slack-ruby-client'
2
+
1
3
  module BotMob
2
4
  module Networks
3
5
  # ## BotMob::Networks::Slack
4
6
  #
5
7
  # Network connection adapters to work with Slack
6
8
  module Slack
7
- autoload :Connection, 'bot_mob/networks/slack/connection'
9
+ autoload :Connection, 'bot_mob/networks/slack/connection'
10
+ autoload :InboundMessage, 'bot_mob/networks/slack/inbound_message'
8
11
  autoload :OutboundMessage, 'bot_mob/networks/slack/outbound_message'
9
12
  end
10
13
  end
@@ -0,0 +1,36 @@
1
+ module BotMob
2
+ module Networks
3
+ module Slack
4
+ # # BotMob::InboundMessage
5
+ #
6
+ # Structured data provided to a bot by Slack via sinatra params
7
+ # {
8
+ # "token": "NiI3K5i7SORx0MAf8U9QRfCX",
9
+ # "team_id": "T09K1L491",
10
+ # "team_domain": "foobar",
11
+ # "service_id": "16319829032",
12
+ # "channel_id": "C29R5RZLN",
13
+ # "channel_name": "general",
14
+ # "timestamp": "1482729119.000014",
15
+ # "user_id": "U07R1RUJA",
16
+ # "user_name": "matthew",
17
+ # "text": "Hi Everyone!",
18
+ # "splat": [],
19
+ # "captures": [
20
+ # "slack"
21
+ # ],
22
+ # "network": "slack"
23
+ # }
24
+ class InboundMessage < BotMob::InboundMessage
25
+ message_attrs :domain, :channel_name, :user_name, :timestamp,
26
+ :token, :team_id, :service_id, :channel_id, :user_id
27
+
28
+ def initialize(**options)
29
+ @network = :slack
30
+ @body = options[:text]
31
+ super
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -10,8 +10,31 @@ module BotMob
10
10
  @bots = []
11
11
  end
12
12
 
13
- def register(bot, **_options)
14
- @bots << (bot.is_a?(Class) ? bot.new : bot)
13
+ def register(bot, **options)
14
+ @bots << (bot.is_a?(Class) ? bot.new(options) : bot)
15
+ end
16
+
17
+ # ## `disseminate`
18
+ #
19
+ # This method will distribute a message or command to all bots
20
+ # on the roster. It collects and returns a compacted array
21
+ # of the bots' responses
22
+ def disseminate(action, params = {})
23
+ bots.map do |bot|
24
+ approach = "handle_#{action}".to_sym
25
+ respond_to?(approach, true) ? send(approach, bot, params) : nil
26
+ end.compact
27
+ end
28
+
29
+ private
30
+
31
+ def handle_message(bot, params)
32
+ bot.receive(params)
33
+ end
34
+
35
+ def handle_command(bot, params)
36
+ method_name = params[:method].to_sym
37
+ bot.receive_command(method_name, params)
15
38
  end
16
39
  end
17
40
  end
@@ -11,6 +11,10 @@ module BotMob
11
11
  super
12
12
  end
13
13
 
14
+ def name
15
+ 'Server'
16
+ end
17
+
14
18
  get '/' do
15
19
  erb :index
16
20
  end
@@ -19,29 +23,27 @@ module BotMob
19
23
  erb :console
20
24
  end
21
25
 
22
- get '/authorize' do
23
- erb :authorize
24
- end
26
+ get '/:network/callback' do
27
+ BotMob.logger.log_request(self, :callback, params)
25
28
 
26
- get '/message' do
27
- results = []
28
- settings.roster.bots.map do |bot|
29
- result = bot.receive(params[:text], network: params[:network])
30
- results << result.body unless result.nil?
29
+ case params[:network].to_sym
30
+ when :facebook
31
+ params['hub.challenge']
32
+ else
33
+ erb :authorize
31
34
  end
35
+ end
36
+
37
+ post '/:network/message' do
38
+ BotMob.logger.log_request(self, :message, params)
32
39
 
33
- results.join(', ')
40
+ settings.roster.disseminate(:message, params).join(', ')
34
41
  end
35
42
 
36
- get '/command/:method' do
37
- results = []
38
- settings.roster.bots.map do |bot|
39
- method_name = params[:method].to_sym
40
- result = bot.receive_command(method_name)
41
- results << result unless result.nil?
42
- end
43
+ post '/:network/command/:method' do
44
+ BotMob.logger.log_request(self, :command, params)
43
45
 
44
- results.join(', ')
46
+ settings.roster.disseminate(:command, params).join(', ')
45
47
  end
46
48
  end
47
49
  end
@@ -1,3 +1,3 @@
1
1
  module BotMob
2
- VERSION = '0.3.0'.freeze
2
+ VERSION = '0.3.2'.freeze
3
3
  end
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.3.0
4
+ version: 0.3.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-12-26 00:00:00.000000000 Z
11
+ date: 2016-12-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sinatra
@@ -168,11 +168,14 @@ files:
168
168
  - lib/bot_mob/core_ext/string.rb
169
169
  - lib/bot_mob/environment.rb
170
170
  - lib/bot_mob/inbound_message.rb
171
+ - lib/bot_mob/logger.rb
171
172
  - lib/bot_mob/networks/roaming.rb
172
173
  - lib/bot_mob/networks/roaming/connection.rb
174
+ - lib/bot_mob/networks/roaming/inbound_message.rb
173
175
  - lib/bot_mob/networks/roaming/outbound_message.rb
174
176
  - lib/bot_mob/networks/slack.rb
175
177
  - lib/bot_mob/networks/slack/connection.rb
178
+ - lib/bot_mob/networks/slack/inbound_message.rb
176
179
  - lib/bot_mob/networks/slack/outbound_message.rb
177
180
  - lib/bot_mob/outbound_message.rb
178
181
  - lib/bot_mob/public/console.js