bot_mob 0.1.10 → 0.1.12

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: 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
  - - ">="