bot_mob 0.1.10 → 0.1.12

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3b4ac52b9e58932d7a1f3b67621cc368ed2dbb25
4
- data.tar.gz: ced498a9261d93c8a3ced4baa47cd4152e8d4ebd
3
+ metadata.gz: b5068a2c5397972fdc3dcf0e07945c19ee94d6f7
4
+ data.tar.gz: cefecbe976056706a7a26cb54d2c81795042d4ec
5
5
  SHA512:
6
- metadata.gz: 321de0b69949d7aac245db16b992272313f6f4c699841b8760658839ab4ec0e8e30c53d8dc6ac0206b4a251f5dc1bd4d36ab860fe40cb78fff69ad4c18dd189f
7
- data.tar.gz: a1f8864999ec86a775f87a8fb46169da67b86895aa966f36be4c78f0dc280b87ed9224b5db83ace70ce62de7f02b864bc1880b6d4f642880764637e64b7944ee
6
+ metadata.gz: ff9962b41566397c1b8faf8379caaf1d978733755184fdc6dfdd88dee66a801fe1f4cea4b2533c87f62d9297dc75a1224a29f0f29616342b2580b768f1440c4f
7
+ data.tar.gz: b83796a1a6922871f8ac0a98b2d1f199f9724fa221e35822350fbdcccf5912385d5bc774785af063af879ce56b72735b40db88fc2887f59bc83d270027ad4fcc
data/.env.test ADDED
@@ -0,0 +1,2 @@
1
+ SLACK_CLIENT_ID=client_id
2
+ SLACK_CLIENT_SECRET=client_secret
data/.gitignore CHANGED
@@ -7,3 +7,4 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ .env
data/bin/console CHANGED
@@ -1,6 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'bundler/setup'
4
+ require 'dotenv'
5
+ Dotenv.load
6
+
4
7
  require 'bot_mob'
8
+ require File.join(BotMob.root, 'spec/script/active_record')
9
+ require File.join(BotMob.root, 'spec/support/mock_bot')
5
10
  require 'irb'
6
11
  IRB.start
data/bot_mob.gemspec CHANGED
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
20
20
  spec.bindir = 'exe'
21
21
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
22
  spec.require_paths = ['lib']
23
+ spec.required_ruby_version = '>= 2.2'
23
24
 
24
25
  spec.add_dependency 'bunny', '~> 2.5'
25
26
  spec.add_dependency 'crypt_keeper', '~> 0.21'
@@ -27,10 +28,14 @@ Gem::Specification.new do |spec|
27
28
  spec.add_dependency 'celluloid-io', '~> 0.17'
28
29
  spec.add_dependency 'activerecord', '~> 4.2'
29
30
 
31
+ spec.add_development_dependency 'dotenv'
30
32
  spec.add_development_dependency 'bundler', '~> 1.12'
31
33
  spec.add_development_dependency 'rake', '~> 10.0'
32
34
  spec.add_development_dependency 'rubocop', '~> 0.40'
35
+ spec.add_development_dependency 'rubocop-rspec'
33
36
  spec.add_development_dependency 'simplecov', '~> 0.12'
34
37
  spec.add_development_dependency 'rspec', '~> 3.0'
38
+ spec.add_development_dependency 'rack-test'
39
+ spec.add_development_dependency 'webmock'
35
40
  spec.add_development_dependency 'sqlite3', '~> 1.3'
36
41
  end
@@ -1,6 +1,4 @@
1
1
  module BotMob
2
- class InvalidNetworkError < StandardError; end
3
-
4
2
  class Ambassador
5
3
  NETWORKS = [:slack]
6
4
 
@@ -9,10 +7,14 @@ module BotMob
9
7
  send("setup_#{network}", code)
10
8
  end
11
9
 
10
+ def auth
11
+ { external_id: external_id, token: token }
12
+ end
13
+
12
14
  private
13
15
 
14
16
  def self.setup_slack(code)
15
- BotMob::Ambassadors::Slack.new(code)
17
+ BotMob::Slack::Ambassador.new(code)
16
18
  end
17
19
  end
18
20
  end
@@ -8,35 +8,45 @@ module BotMob
8
8
  def initialize(bot_class)
9
9
  @bot_class = bot_class
10
10
  @bots = {}
11
-
12
- Install.all.each { |install| register(install) }
13
11
  end
14
12
 
15
13
  def start!
16
14
  BotMob.logger.info("=> BotMob #{BotMob::VERSION} application starting")
17
- wire.connect(self)
18
- loop { sleep(10) }
19
- end
15
+ load_registry
16
+ wire.connect { |auth| connect(auth) }
20
17
 
21
- def add_bot(external_id)
22
- BotMob.logger.info "Adding bot: #{external_id}"
23
- install = Install.find_by_external_id(external_id)
24
- if install.nil?
25
- BotMob.logger.info "Could not find bot: #{external_id}"
26
- return
18
+ loop do
19
+ sleep(10)
20
+ # Avoid polling at all for the time being
21
+ # load_registry if registry_available? && !wire.available?
27
22
  end
23
+ end
28
24
 
29
- register install
25
+ def connect(auth)
26
+ external_id, token = auth['external_id'], auth['token']
27
+ BotMob.logger.info "Register bot: #{external_id}"
28
+ bot = bot_class.new(external_id, token)
29
+ @bots[external_id] ||= bot
30
+ @bots[external_id].refresh
30
31
  end
31
32
 
32
- def register(install)
33
- bot = bot_class.new(install.external_id, install.token)
34
- @bots[install.external_id] ||= bot
35
- @bots[install.external_id].refresh
33
+ def registry_available?
34
+ @registry_available.nil? || @registry_available
36
35
  end
37
36
 
38
37
  private
39
38
 
39
+ def load_registry
40
+ begin
41
+ BotMob::Install.all.each { |install| connect(install.auth) }
42
+ BotMob.logger.info '=> Registry loaded'
43
+ @registry_available = true
44
+ rescue ActiveRecord::ConnectionNotEstablished => e
45
+ @registry_available = false
46
+ BotMob.logger.info "=> Database unavailable, skipping registry"
47
+ end
48
+ end
49
+
40
50
  def wire
41
51
  @wire ||= BotMob::Wire.new
42
52
  end
@@ -13,9 +13,8 @@ module BotMob
13
13
  def process
14
14
  return unless ambassador.success?
15
15
 
16
- install = BotMob::Install.create_with_ambassador(ambassador)
17
- # check for install's success
18
- wire.publish(external_id: install.external_id)
16
+ install = BotMob::Install.create_with_auth(ambassador.auth)
17
+ wire.publish(external_id: install.external_id, token: install.token)
19
18
  end
20
19
 
21
20
  private
data/lib/bot_mob/bot.rb CHANGED
@@ -1,64 +1,31 @@
1
+ require 'forwardable'
2
+
1
3
  module BotMob
2
4
  # ## BotMob::Bot
3
5
  #
4
6
  # This is the base for all bots that will connect
5
7
  # to Slack.
6
8
  class Bot
7
- attr_reader :external_id, :state
9
+ attr_reader :external_id
10
+
11
+ extend Forwardable
12
+ def_delegators :connection, :connect, :reconnect, :refresh, :client
8
13
 
9
14
  def initialize(external_id, token)
10
15
  @external_id = external_id
11
-
12
- if token.nil?
13
- @state = :inactive
14
- else
15
- @token = token
16
- setup
17
- end
18
- end
19
-
20
- def establish_connection
21
- client.start_async
22
- rescue Slack::Web::Api::Error => e
23
- logger.info e.message
24
- set_state :inactive
25
- end
26
-
27
- def connect
28
- set_state :connecting
29
- establish_connection
30
- end
31
-
32
- def reconnect
33
- set_state :reconnecting
34
- @client = Slack::RealTime::Client.new(token: @token)
35
- setup
36
- establish_connection
16
+ @token = token
37
17
  end
38
18
 
39
19
  def api_post(args = {})
40
- logger.info(" Sending \"#{args.inspect}\"\n")
20
+ BotMob.logger.info(" Sending \"#{args.inspect}\"\n")
41
21
  client.web_client.chat_postMessage(args)
42
22
  end
43
23
 
44
24
  def realtime_post(args = {})
45
- logger.info(" Sending \"#{args[:text]}\"\n")
25
+ BotMob.logger.info(" Sending \"#{args[:text]}\"\n")
46
26
  client.message(args)
47
27
  end
48
28
 
49
- def refresh
50
- return unless active?
51
-
52
- case true
53
- when waiting?
54
- connect
55
- when disconnected?
56
- reconnect
57
- else
58
- logger.info "State[#{external_id}] - No Change"
59
- end
60
- end
61
-
62
29
  def handle(message)
63
30
  # noop
64
31
  end
@@ -67,61 +34,8 @@ module BotMob
67
34
  false
68
35
  end
69
36
 
70
- def active?
71
- @state != :inactive
72
- end
73
-
74
- def waiting?
75
- @state == :waiting
76
- end
77
-
78
- def disconnected?
79
- @state != :connected
80
- end
81
-
82
- def connected?
83
- @state == :connected
84
- end
85
-
86
- def closed?
87
- @state == :closed
88
- end
89
-
90
- private
91
-
92
- def setup
93
- @state = :waiting
94
-
95
- client.on(:hello) { set_state(:connected) }
96
- client.on(:close) { set_state(:disconnecting) }
97
- client.on(:closed) { set_state(:closed) }
98
-
99
- client.on :message do |data|
100
- message = BotMob::InboundMessage.new(:websocket, data)
101
-
102
- if respond?(message)
103
- logger.info("Received message at #{Time.now}")
104
- logger.info(" Parameters: #{message.data}")
105
- handle(message)
106
- end
107
- end
108
- end
109
-
110
- def set_state(state)
111
- if state == :connected
112
- logger.info "Successfully connected, '#{client.team.name}' team at https://#{client.team.domain}.slack.com."
113
- end
114
-
115
- logger.info "State[#{external_id}] - #{state}"
116
- @state = state
117
- end
118
-
119
- def client
120
- @client ||= Slack::RealTime::Client.new(token: @token)
121
- end
122
-
123
- def logger
124
- @logger ||= BotMob.logger
37
+ def connection
38
+ @connection ||= BotMob::Connection.new(:slack, self, @token)
125
39
  end
126
40
  end
127
41
  end
@@ -0,0 +1,109 @@
1
+ module BotMob
2
+ # ## BotMob::Connection
3
+ #
4
+ # The Connection is the point of contact
5
+ # between the Bot and the specified network
6
+ class Connection
7
+ attr_reader :bot
8
+
9
+ def initialize(_network, bot, token)
10
+ @bot = bot
11
+ @token = token
12
+ token.nil? ? set_state(:inactive) : setup
13
+ end
14
+
15
+ def connect
16
+ return unless active?
17
+ set_state(:connecting)
18
+ establish
19
+ end
20
+
21
+ def reconnect
22
+ return unless active?
23
+ set_state(:reconnecting)
24
+ renew_client
25
+ setup
26
+ establish
27
+ end
28
+
29
+ def refresh
30
+ return unless active?
31
+
32
+ case true
33
+ when waiting?
34
+ connect
35
+ when disconnected?
36
+ reconnect
37
+ else
38
+ logger.info "State[#{bot.external_id}] - No Change"
39
+ end
40
+ end
41
+
42
+ def client
43
+ @client ||= renew_client
44
+ end
45
+
46
+ private
47
+
48
+ def establish
49
+ client.start_async
50
+ rescue ::Slack::Web::Api::Error => e
51
+ logger.info e.message
52
+ set_state(:inactive)
53
+ end
54
+
55
+ def active?
56
+ @state != :inactive
57
+ end
58
+
59
+ def waiting?
60
+ @state == :waiting
61
+ end
62
+
63
+ def disconnected?
64
+ @state != :connected
65
+ end
66
+
67
+ def connected?
68
+ @state == :connected
69
+ end
70
+
71
+ def closed?
72
+ @state == :closed
73
+ end
74
+
75
+ def set_state(state)
76
+ if state == :connected
77
+ logger.info "Successfully connected, '#{client.team.name}' team at https://#{client.team.domain}.slack.com."
78
+ end
79
+
80
+ logger.info "State[#{bot.external_id}] - #{state}"
81
+ @state = state
82
+ end
83
+
84
+ def setup
85
+ set_state(:waiting)
86
+ client.on(:hello) { set_state(:connected) }
87
+ client.on(:close) { set_state(:disconnecting) }
88
+ client.on(:closed) { set_state(:closed) }
89
+
90
+ client.on :message do |data|
91
+ message = BotMob::InboundMessage.new(:websocket, data)
92
+
93
+ if bot.respond?(message)
94
+ logger.info("Received message at #{Time.now}")
95
+ logger.info(" Parameters: #{message.data}")
96
+ bot.handle(message)
97
+ end
98
+ end
99
+ end
100
+
101
+ def renew_client
102
+ @client = ::Slack::RealTime::Client.new(token: @token)
103
+ end
104
+
105
+ def logger
106
+ @logger ||= BotMob.logger
107
+ end
108
+ end
109
+ end
@@ -3,11 +3,15 @@ module BotMob
3
3
  crypt_keeper :token, encryptor: :postgres_pgp, key: ENV['BOTMOB_SECRET'], salt: ENV['BOTMOB_SALT']
4
4
  self.table_name = "botmob_installs"
5
5
 
6
- def self.create_with_ambassador(auth)
7
- where(external_id: auth.external_id).first_or_initialize.tap do |i|
8
- i.token = auth.token
6
+ def self.create_with_auth(auth)
7
+ where(external_id: auth[:external_id]).first_or_initialize.tap do |i|
8
+ i.token = auth[:token]
9
9
  i.save!
10
10
  end
11
11
  end
12
+
13
+ def auth
14
+ { external_id: external_id, token: token }
15
+ end
12
16
  end
13
17
  end
@@ -1,20 +1,20 @@
1
1
  module BotMob
2
- module Ambassadors
3
- class Slack
2
+ module Slack
3
+ class Ambassador
4
4
  def initialize(code)
5
5
  @code = code
6
6
  end
7
7
 
8
8
  def token
9
- oauth_response && oauth_response.bot_access_token
9
+ oauth_response.bot_access_token
10
10
  end
11
11
 
12
12
  def external_id
13
- oauth_response && oauth_response.bot_user_id
13
+ oauth_response.bot_user_id
14
14
  end
15
15
 
16
16
  def success?
17
- external_id && token
17
+ !!(external_id && token)
18
18
  end
19
19
 
20
20
  private
@@ -30,8 +30,16 @@ module BotMob
30
30
 
31
31
  @oauth_response = response.bot
32
32
  rescue ::Slack::Web::Api::Error => e
33
- puts e.message
34
- nil
33
+ case e.message
34
+ when 'invalid_client_id'
35
+ raise BotMob::InvalidClientIdError, 'Invalid SLACK_CLIENT_ID environment variable'
36
+ when 'bad_client_secret'
37
+ raise BotMob::InvalidClientSecretError, 'Invalid SLACK_CLIENT_SECRET environment variable'
38
+ when 'invalid_code'
39
+ raise BotMob::InvalidCodeError, 'Invalid access code'
40
+ else
41
+ raise e
42
+ end
35
43
  end
36
44
 
37
45
  def client
@@ -1,3 +1,3 @@
1
1
  module BotMob
2
- VERSION = '0.1.10'.freeze
2
+ VERSION = '0.1.12'.freeze
3
3
  end
data/lib/bot_mob/wire.rb CHANGED
@@ -10,33 +10,41 @@ module BotMob
10
10
  queue.publish(message.to_json, routing_key: queue.name)
11
11
  end
12
12
 
13
- def connect(app)
13
+ def connect
14
+ return if !block_given? || queue.nil?
15
+
14
16
  queue.subscribe do |_info, _props, body|
15
- app.add_bot(JSON.parse(body)['external_id'])
17
+ yield(JSON.parse(body))
16
18
  end
17
19
  rescue Bunny::PreconditionFailed => e
18
20
  BotMob.logger.info "#{e.channel_close.reply_code}, message: #{e.channel_close.reply_text}"
19
21
  end
20
22
 
23
+ def available?
24
+ @available
25
+ end
26
+
21
27
  private
22
28
 
23
29
  def channel
24
- @channel ||= connection.create_channel
30
+ @channel ||= connection && connection.create_channel
25
31
  end
26
32
 
27
33
  def queue
28
- @queue ||= channel.queue('wire.notifications')
34
+ @queue ||= channel && channel.queue('wire.notifications')
29
35
  end
30
36
 
31
37
  def connection
32
38
  @connection ||= begin
33
39
  Bunny.new.tap do |conn|
34
- Rails.logger.info('=> Connecting BotMob Wire')
35
40
  conn.start
41
+ @available = true
42
+ BotMob.logger.info('=> Wire connected')
36
43
  end
37
44
  rescue Bunny::TCPConnectionFailedForAllHosts => e
38
- puts "No RabbitMQ Server"
39
- raise
45
+ @available = false
46
+ BotMob.logger.info('=> RabbitMQ unavailable, skipping wire')
47
+ nil
40
48
  end
41
49
  end
42
50
  end
data/lib/bot_mob.rb CHANGED
@@ -13,21 +13,28 @@ module BotMob
13
13
  autoload :Application, 'bot_mob/application'
14
14
  autoload :Authority, 'bot_mob/authority'
15
15
  autoload :Bot, 'bot_mob/bot'
16
+ autoload :Connection, 'bot_mob/connection'
16
17
  autoload :InboundMessage, 'bot_mob/inbound_message'
17
18
  autoload :Install, 'bot_mob/install'
18
19
  autoload :Wire, 'bot_mob/wire'
19
20
 
20
- module Ambassadors
21
- autoload :Slack, 'bot_mob/ambassadors/slack'
21
+ module Slack
22
+ autoload :Ambassador, 'bot_mob/slack/ambassador'
22
23
  end
23
24
 
25
+ class InvalidNetworkError < StandardError; end
26
+ class InvalidClientIdError < StandardError; end
27
+ class InvalidClientSecretError < StandardError; end
28
+ class InvalidCodeError < StandardError; end
29
+ class ConnectionNotEstablished < StandardError; end
30
+
24
31
  def self.root
25
32
  File.expand_path('../..', __FILE__)
26
33
  end
27
34
 
28
35
  def self.logger
29
36
  @logger ||= begin
30
- buffer = Logger.new(STDOUT)
37
+ buffer = Logger.new(ENV['RACK_ENV'] == 'test' ? '/dev/null' : STDOUT)
31
38
  if buffer
32
39
  buffer.level = ($PROGRAM_NAME == 'irb' ? Logger::DEBUG : Logger::INFO)
33
40
  buffer.formatter = proc { |severity, datetime, progname, msg| "#{msg}\n" }
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.1.10
4
+ version: 0.1.12
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-08-11 00:00:00.000000000 Z
11
+ date: 2016-08-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bunny
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '4.2'
83
+ - !ruby/object:Gem::Dependency
84
+ name: dotenv
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: bundler
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -122,6 +136,20 @@ dependencies:
122
136
  - - "~>"
123
137
  - !ruby/object:Gem::Version
124
138
  version: '0.40'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rubocop-rspec
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
125
153
  - !ruby/object:Gem::Dependency
126
154
  name: simplecov
127
155
  requirement: !ruby/object:Gem::Requirement
@@ -150,6 +178,34 @@ dependencies:
150
178
  - - "~>"
151
179
  - !ruby/object:Gem::Version
152
180
  version: '3.0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: rack-test
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: webmock
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
153
209
  - !ruby/object:Gem::Dependency
154
210
  name: sqlite3
155
211
  requirement: !ruby/object:Gem::Requirement
@@ -171,6 +227,7 @@ executables: []
171
227
  extensions: []
172
228
  extra_rdoc_files: []
173
229
  files:
230
+ - ".env.test"
174
231
  - ".gitignore"
175
232
  - ".travis.yml"
176
233
  - CODE_OF_CONDUCT.md
@@ -182,12 +239,13 @@ files:
182
239
  - bot_mob.gemspec
183
240
  - lib/bot_mob.rb
184
241
  - lib/bot_mob/ambassador.rb
185
- - lib/bot_mob/ambassadors/slack.rb
186
242
  - lib/bot_mob/application.rb
187
243
  - lib/bot_mob/authority.rb
188
244
  - lib/bot_mob/bot.rb
245
+ - lib/bot_mob/connection.rb
189
246
  - lib/bot_mob/inbound_message.rb
190
247
  - lib/bot_mob/install.rb
248
+ - lib/bot_mob/slack/ambassador.rb
191
249
  - lib/bot_mob/version.rb
192
250
  - lib/bot_mob/wire.rb
193
251
  homepage: https://github.com/mwerner/bot_mob
@@ -201,7 +259,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
201
259
  requirements:
202
260
  - - ">="
203
261
  - !ruby/object:Gem::Version
204
- version: '0'
262
+ version: '2.2'
205
263
  required_rubygems_version: !ruby/object:Gem::Requirement
206
264
  requirements:
207
265
  - - ">="