bot_mob 0.2.1 → 0.2.2

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: 3b057501d92431ac59d7c69c9460b60ee5480f17
4
- data.tar.gz: 2e44458a59521d2b1da4b36f2e737f5448125ff2
3
+ metadata.gz: a6c06b15e2ece464e52b403fd36bd610487da2b8
4
+ data.tar.gz: 34dfe52f53892d31d918aae0611dcc347f571ab7
5
5
  SHA512:
6
- metadata.gz: 61a2e9ff80ace5da65189c32943dbf1e7a5b634cb886c05c571159020b5809ae0d777270df0fb0bc6688226024608812dd12d08aadaa359a27a13156fbfe6d22
7
- data.tar.gz: b821beaea5c9da7d69bbe316cb65a34975a5f6ea40a1f3f8301c2de5b975f4b3f1935eba32fd06ae87f0ae4d5f6bb0dc436254ee508b5dc7c087274882e28b81
6
+ metadata.gz: 34c4efc4364f76889ea9968669b3bcf7c3c5c8b85c6715dff07f138dbaa8bd3c1dd6a57416761251ccc7ba81244e9c054acb8cb6b084b69503ac6c9a8938fa58
7
+ data.tar.gz: 2c4d05434f48c330d51f426404e7170fd63b410e7e09f67d532d05aa2ab78ef92171d617d75958c584a339f978b6e9988d84655530fb46ad42383e7c103550b6
@@ -8,4 +8,5 @@ require 'bot_mob'
8
8
  require File.join(BotMob.root, 'spec/script/active_record')
9
9
  require File.join(BotMob.root, 'spec/support/mock_bot')
10
10
  require 'irb'
11
+
11
12
  IRB.start
@@ -1,32 +1,45 @@
1
1
  $LOAD_PATH.unshift File.dirname(__FILE__)
2
- require 'bunny'
3
- require 'slack-ruby-client'
2
+ require 'bot_mob/errors'
4
3
  require 'bot_mob/version'
5
4
 
6
5
  # ## BotMob
7
6
  #
8
7
  # BotMob provides all the tools you need to easily
9
- # connect to chat networks
8
+ # connect multiple bots to chat networks
10
9
  module BotMob
11
- autoload :Ambassador, 'bot_mob/ambassador'
12
- autoload :Application, 'bot_mob/application'
13
- autoload :Authority, 'bot_mob/authority'
14
- autoload :Bot, 'bot_mob/bot'
15
- autoload :Connection, 'bot_mob/connection'
16
- autoload :InboundMessage, 'bot_mob/inbound_message'
17
- autoload :Install, 'bot_mob/install'
18
- autoload :Roster, 'bot_mob/roster'
19
- autoload :Wire, 'bot_mob/wire'
20
-
21
- module Slack
22
- autoload :Ambassador, 'bot_mob/slack/ambassador'
10
+ autoload :Ambassador, 'bot_mob/ambassador'
11
+ autoload :Application, 'bot_mob/application'
12
+ autoload :Authority, 'bot_mob/authority'
13
+ autoload :Bot, 'bot_mob/bot'
14
+ autoload :Connection, 'bot_mob/connection'
15
+ autoload :InboundMessage, 'bot_mob/inbound_message'
16
+ autoload :Install, 'bot_mob/install'
17
+ autoload :NilConnection, 'bot_mob/nil_connection'
18
+ autoload :OutboundMessage, 'bot_mob/outbound_message'
19
+ autoload :Roster, 'bot_mob/roster'
20
+ autoload :Wire, 'bot_mob/wire'
21
+
22
+ module Development
23
+ autoload :Ambassador, 'bot_mob/development/ambassador'
24
+ autoload :Connection, 'bot_mob/development/connection'
25
+ autoload :InboundMessage, 'bot_mob/development/inbound_message'
26
+ end
27
+
28
+ @@networks = {
29
+ dev: BotMob::Development
30
+ }
31
+
32
+ def self.networks
33
+ @@networks
23
34
  end
24
35
 
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
36
+ def self.mount(network_map)
37
+ @@networks.merge!(network_map)
38
+ end
39
+
40
+ def self.valid_network?(network)
41
+ networks.include?(network.to_s.to_sym) && BotMob.networks[network]
42
+ end
30
43
 
31
44
  def self.root
32
45
  File.expand_path('../..', __FILE__)
@@ -1,20 +1,34 @@
1
1
  module BotMob
2
2
  class Ambassador
3
- NETWORKS = [:slack]
3
+ # ## BotMob::Ambassador.setup
4
+ #
5
+ # Ambassador setup delegates the ambassadorship
6
+ # to the correct network.
7
+ def self.setup(network, args = {})
8
+ invalid_network_error(network) unless BotMob.valid_network?(network)
4
9
 
5
- def self.setup(network, code)
6
- raise BotMob::InvalidNetworkError unless NETWORKS.include?(network.to_s.to_sym)
7
- send("setup_#{network}", code)
10
+ ambassador = network_delegate(network)
11
+ invalid_delegate_error(network) unless ambassador.respond_to?(:setup)
12
+
13
+ ambassador.setup(args)
8
14
  end
9
15
 
10
- def auth
11
- { external_id: external_id, token: token }
16
+ def data
17
+ {}
12
18
  end
13
19
 
14
20
  private
15
21
 
16
- def self.setup_slack(code)
17
- BotMob::Slack::Ambassador.new(code)
22
+ def self.network_delegate(network)
23
+ BotMob.networks[network].const_get('Ambassador')
24
+ end
25
+
26
+ def self.invalid_network_error(network)
27
+ raise BotMob::InvalidNetworkError, "The #{network.inspect} network has not been mounted"
28
+ end
29
+
30
+ def self.invalid_delegate_error(network)
31
+ raise BotMob::InvalidAmbassadorError, "The #{network.inspect} ambassador must implement the `setup` method"
18
32
  end
19
33
  end
20
34
  end
@@ -3,7 +3,7 @@ require 'forwardable'
3
3
  module BotMob
4
4
  # ## BotMob::Application
5
5
  #
6
- # This is the base class for BotMob.
6
+ # This is the base application class for BotMob.
7
7
  class Application
8
8
  extend Forwardable
9
9
 
@@ -13,9 +13,7 @@ module BotMob
13
13
  BotMob.logger.info("=> BotMob #{BotMob::VERSION} application starting")
14
14
 
15
15
  roster.load
16
- wire.listen do |bot|
17
- roster.connect(bot)
18
- end
16
+ wire.listen { |auth| roster.connect(auth) }
19
17
 
20
18
  loop do
21
19
  sleep(10)
@@ -26,10 +24,18 @@ module BotMob
26
24
 
27
25
  private
28
26
 
27
+ # ## Roster
28
+ #
29
+ # The `roster` is the object that keeps track of the registration
30
+ # and participation of any various bots active on your mob
29
31
  def roster
30
32
  @roster ||= BotMob::Roster.new
31
33
  end
32
34
 
35
+ # ## Wire
36
+ #
37
+ # The `wire` is the object that allows for other processes to
38
+ # tell your bot mob application to take some action.
33
39
  def wire
34
40
  @wire ||= BotMob::Wire.new
35
41
  end
@@ -1,23 +1,39 @@
1
1
  module BotMob
2
2
  # ## BotMob::Authority
3
3
  #
4
- # The Authority class will process an access code
5
- # and retrieve an access token from Slack
4
+ # The Authority class will process an access args
5
+ # and retrieve an access token from the specified
6
+ # network
7
+ #
8
+ # Example Invocation:
9
+ #
10
+ # BotMob::Authority.new(:slack, code: params[:code])
6
11
  class Authority
7
- attr_reader :network, :ambassador
12
+ attr_reader :network
8
13
 
9
- def initialize(network, code)
10
- @ambassador = BotMob::Ambassador.setup(network, code)
14
+ def initialize(network, args = {})
15
+ @network = network
16
+ @args = args
11
17
  end
12
18
 
13
19
  def process(bot_class)
14
20
  return unless ambassador.success?
15
21
 
16
- wire.publish(ambassador.auth.merge(bot_class: bot_class.to_s))
22
+ # wire.publish(network: :slack, external_id: 'foo', bot_class: 'MockBot', data: { token: ENV['IOBOT_TOKEN'] } )
23
+ wire.publish({
24
+ network: network,
25
+ external_id: ambassador.external_id,
26
+ bot_class: bot_class.to_s,
27
+ data: ambassador.data || {}
28
+ })
17
29
  end
18
30
 
19
31
  private
20
32
 
33
+ def ambassador
34
+ @ambassador ||= BotMob::Ambassador.setup(network, @args)
35
+ end
36
+
21
37
  def wire
22
38
  @wire ||= BotMob::Wire.new
23
39
  end
@@ -4,38 +4,50 @@ module BotMob
4
4
  # ## BotMob::Bot
5
5
  #
6
6
  # This is the base for all bots that will connect
7
- # to Slack.
7
+ # to respond to messages received by your application.
8
8
  class Bot
9
- attr_reader :external_id
9
+ attr_accessor :id, :network
10
+ attr_reader :options
10
11
 
11
12
  extend Forwardable
12
- def_delegators :connection, :connect, :reconnect, :refresh, :client
13
+ def_delegators :connection, :connect, :reconnect, :refresh, :client, :deliver
13
14
 
14
- def initialize(external_id, token)
15
- @external_id = external_id
16
- @token = token
15
+ def initialize(*args)
16
+ @network, @id, @options = *args
17
17
  end
18
18
 
19
- def api_post(args = {})
20
- BotMob.logger.info(" Sending \"#{args.inspect}\"\n")
21
- client.web_client.chat_postMessage(args)
19
+ def key
20
+ "#{network}.#{id}"
22
21
  end
23
22
 
24
- def realtime_post(args = {})
25
- BotMob.logger.info(" Sending \"#{args[:text]}\"\n")
26
- client.message(args)
23
+ def receive(message)
24
+ inbound_message = message.is_a?(String) ? BotMob::InboundMessage.new(message) : message
25
+ return unless respond?(inbound_message)
26
+
27
+ BotMob.logger.info("Received message at #{Time.now}")
28
+ BotMob.logger.info(" Parameters: #{inbound_message.params}")
29
+ respond(inbound_message)
27
30
  end
28
31
 
29
- def respond(message)
30
- # noop
32
+ def respond(inbound_message)
33
+ outbound = BotMob::OutboundMessage.new(text: "#{self.class} ACK! #{inbound_message.text}")
34
+ deliver(outbound)
31
35
  end
32
36
 
33
- def respond?(message)
34
- false
37
+ def respond?(inbound_message)
38
+ true
35
39
  end
36
40
 
41
+ private
42
+
37
43
  def connection
38
- @connection ||= BotMob::Connection.new(:slack, self, @token)
44
+ @connection ||= begin
45
+ if BotMob.valid_network?(network)
46
+ BotMob::Connection.setup(network, self, options)
47
+ else
48
+ BotMob::NilConnection.new(self)
49
+ end
50
+ end
39
51
  end
40
52
  end
41
53
  end
@@ -6,50 +6,27 @@ module BotMob
6
6
  class Connection
7
7
  attr_reader :bot
8
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
9
+ def self.setup(network, bot, options = {})
10
+ invalid_network_error(network) unless BotMob.valid_network?(network)
20
11
 
21
- def reconnect
22
- return unless active?
23
- set_state(:reconnecting)
24
- renew_client
25
- setup
26
- establish
27
- end
12
+ connection = network_delegate(network)
13
+ invalid_delegate_error(network) unless connection.respond_to?(:setup)
28
14
 
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
15
+ connection.setup(bot, options)
40
16
  end
41
17
 
42
- def client
43
- @client ||= renew_client
18
+ def deliver(outbound_message, options = {})
19
+ # connection.deliver(outbound_message, options)
44
20
  end
45
21
 
46
22
  private
47
23
 
48
- def establish
49
- client.start_async
50
- rescue ::Slack::Web::Api::Error => e
51
- logger.info e.message
52
- set_state(:inactive)
24
+ def self.network_delegate(network)
25
+ BotMob.networks[network].const_get('Connection')
26
+ end
27
+
28
+ def welcome_message
29
+ "Connected!"
53
30
  end
54
31
 
55
32
  def active?
@@ -73,37 +50,18 @@ module BotMob
73
50
  end
74
51
 
75
52
  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
53
+ BotMob.logger.info welcome_message if state == :connected
79
54
 
80
- logger.info "State[#{bot.class.to_s}##{bot.external_id}] - #{state}"
55
+ BotMob.logger.info "State[#{bot.class.to_s}##{bot.key}] - #{state}"
81
56
  @state = state
82
57
  end
83
58
 
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.respond(message)
97
- end
98
- end
99
- end
100
-
101
- def renew_client
102
- @client = ::Slack::RealTime::Client.new(token: @token)
59
+ def self.invalid_network_error(network)
60
+ raise BotMob::InvalidNetworkError, "The #{network.inspect} network has not been mounted"
103
61
  end
104
62
 
105
- def logger
106
- @logger ||= BotMob.logger
63
+ def self.invalid_delegate_error(network)
64
+ raise BotMob::InvalidConnectionError, "The #{network.inspect} connection must implement the `setup` method"
107
65
  end
108
66
  end
109
67
  end
@@ -0,0 +1,21 @@
1
+ module BotMob
2
+ module Development
3
+ class Ambassador < BotMob::Ambassador
4
+ def self.setup(args)
5
+ new
6
+ end
7
+
8
+ def success?
9
+ true
10
+ end
11
+
12
+ def external_id
13
+ 'dev'
14
+ end
15
+
16
+ def token
17
+ 'na'
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,34 @@
1
+ module BotMob
2
+ module Development
3
+ class Connection < BotMob::Connection
4
+
5
+ def initialize(bot, options = {})
6
+ @bot = bot
7
+ end
8
+
9
+ def self.setup(bot, options = {})
10
+ new(bot, options)
11
+ end
12
+
13
+ def deliver(outbound_message, options = {})
14
+ BotMob.logger.info " Dev Connection Sent: #{outbound_message.text.inspect}"
15
+ end
16
+
17
+ def connect
18
+ BotMob.logger.info 'Connecting... '
19
+ end
20
+
21
+ def reconnect
22
+ BotMob.logger.info 'Reconnecting... '
23
+ end
24
+
25
+ def refresh
26
+ BotMob.logger.info 'Refreshing... '
27
+ end
28
+
29
+ def client
30
+ self
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,17 @@
1
+ module BotMob
2
+ module Development
3
+ class InboundMessage < BotMob::InboundMessage
4
+ def initialize(params)
5
+ @text = params[:text]
6
+ @network = params[:network]
7
+ end
8
+
9
+ def self.parse(**params)
10
+ new({
11
+ network: params[:network],
12
+ text: params[:text]
13
+ })
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ module BotMob
2
+ class InvalidAmbassadorError < StandardError; end
3
+ class InvalidConnectionError < StandardError; end
4
+ class InvalidInboundMessageError < StandardError; end
5
+
6
+ class InvalidNetworkError < StandardError; end
7
+ class InvalidClientIdError < StandardError; end
8
+ class InvalidClientSecretError < StandardError; end
9
+ class InvalidCodeError < StandardError; end
10
+ class ConnectionNotEstablished < StandardError; end
11
+ class UnregisteredBotError < StandardError; end
12
+ end
@@ -1,52 +1,45 @@
1
1
  module BotMob
2
- # ## BotMob::Inbound Message
2
+ # ## BotMob::InboundMessage
3
3
  #
4
4
  # The InboundMessage class provides easy access to
5
- # messages received from Slack's Real Time API
5
+ # messages received by bots in your application
6
6
  class InboundMessage
7
- attr_reader :source, :data, :token, :team_id, :team_domain,
8
- :channel_id, :channel_name, :external_id, :user_name,
9
- :command, :text, :bot_id, :response_url, :ts
7
+ attr_reader :network, :text, :params
10
8
 
11
- def initialize(source, data)
12
- @data = data
13
- send("parse_#{source}".to_sym, data)
9
+ def initialize(text)
10
+ @text = text
14
11
  end
15
12
 
16
- def arguments
17
- @arguments ||= text[%r{\/[\w'_-]+\s*[\w'_-]+\s*(.*)}, 1]
13
+ def self.parse(params)
14
+ network = params[:network]
15
+ invalid_network_error(network) unless BotMob.valid_network?(network)
16
+
17
+ parser = network_delegate(network)
18
+ invalid_delegate_error(network) unless parser.respond_to?(:parse)
19
+
20
+ parser.parse(params)
18
21
  end
19
22
 
20
- def command?
21
- !command.nil?
23
+ def arguments
24
+ @arguments ||= text.to_s[%r{\/[\w'_-]+\s*[\w'_-]+\s*(.*)}, 1]
22
25
  end
23
26
 
24
- def human?
25
- bot_id.nil?
27
+ def params
28
+ @params || { network: network, text: text }
26
29
  end
27
30
 
28
31
  private
29
32
 
30
- def parse_websocket(data)
31
- @text = data[:text]
32
- @channel_id = data[:channel_id] || data[:channel]
33
- @bot_id = data[:bot_id]
34
- @team = data[:team]
35
- @ts = data[:ts]
36
- @external_id = data[:user_id]
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"
37
39
  end
38
40
 
39
- def parse_webhook(data)
40
- @token = data['token']
41
- @team_id = data['team_id']
42
- @team_domain = data['team_domain']
43
- @channel_id = data['channel_id']
44
- @channel_name = data['channel_name']
45
- @external_id = data['user_id']
46
- @user_name = data['user_name']
47
- @command = data['command']
48
- @text = data['text']
49
- @response_url = data['response_url']
41
+ def self.invalid_delegate_error(network)
42
+ raise BotMob::InvalidInboundMessageError, "The #{network.inspect} inbound message parser must implement the `parse` method"
50
43
  end
51
44
  end
52
45
  end
@@ -1,6 +1,6 @@
1
1
  module BotMob
2
2
  class Install < ActiveRecord::Base
3
- self.table_name = "botmob_installs"
3
+ self.table_name = "bot_mob_installs"
4
4
 
5
5
  def self.registered_bot(auth)
6
6
  where(bot_class: auth['bot_class'], external_id: auth['external_id'])
@@ -8,13 +8,9 @@ module BotMob
8
8
 
9
9
  def self.create_with_auth(auth)
10
10
  registered_bot(auth).first_or_initialize.tap do |i|
11
- i.token = auth['token']
11
+ i.data = auth['data']
12
12
  i.save!
13
13
  end
14
14
  end
15
-
16
- def auth
17
- { external_id: external_id, token: token }
18
- end
19
15
  end
20
16
  end
@@ -0,0 +1,29 @@
1
+ module BotMob
2
+ class NilConnection
3
+
4
+ def initialize(bot)
5
+ @bot = bot
6
+ end
7
+
8
+ def deliver(outbound_message, options = {})
9
+ BotMob.logger.info '! Missing connection attempting to deliver... '
10
+ BotMob.logger.info " Message sent: #{outbound_message.text.inspect}"
11
+ end
12
+
13
+ def connect
14
+ BotMob.logger.info '! Missing connection attempting to connect... '
15
+ end
16
+
17
+ def reconnect
18
+ BotMob.logger.info '! Missing connection attempting to reconnect... '
19
+ end
20
+
21
+ def refresh
22
+ BotMob.logger.info '! Missing connection attempting to refresh... '
23
+ end
24
+
25
+ def client
26
+ self
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,19 @@
1
+ module BotMob
2
+ # ## BotMob::Outbound Message
3
+ #
4
+ # The OutboundMessage class provides a convenient
5
+ # way to consistently allow your bot to respond
6
+ # to a given network
7
+ class OutboundMessage
8
+ attr_reader :text, :channel_id
9
+
10
+ def initialize(data)
11
+ @text = data[:text]
12
+ @channel_id = data[:channel_id]
13
+ end
14
+
15
+ def to_h
16
+ { channel: channel_id, text: text }
17
+ end
18
+ end
19
+ end
@@ -7,23 +7,42 @@ module BotMob
7
7
  @bots = {}
8
8
  end
9
9
 
10
+ # `connect`
11
+ #
12
+ # `connect` receives an auth hash that contains a bot_class
13
+ # along with any accompanying metadata that the bot will need
14
+ # to connect to the specified network. Provided by BotMob::Authority
15
+ # delivered over the wire.
16
+ #
17
+ # Example `auth` param:
18
+ #
19
+ # {
20
+ # bot_class: 'FooBot',
21
+ # network: 'slack',
22
+ # external_id: 'USER1',
23
+ # data: {
24
+ # token: 'abc123'
25
+ # }
26
+ # }
27
+ #
10
28
  def connect(auth)
29
+ # { network: :dev, external_id: 'foo', bot_class: 'FooBot', data: {} }
11
30
  class_name = auth['bot_class'].to_s
12
31
  bot_class = registry[class_name]
13
- external_id, token = auth['external_id'], auth['token']
14
- BotMob.logger.info "Initializing #{bot_class}: #{external_id}"
32
+ raise BotMob::UnregisteredBotError, 'You must explicitly register your bot class with the roster before connecting that bot type' if bot_class.nil?
33
+ BotMob.logger.info "Selecting #{class_name.inspect} from registry: #{bot_class.inspect}"
15
34
 
16
- puts "Registry: #{registry.inspect}"
17
- puts "Using registered bot: #{bot_class.inspect}"
18
35
  unless auth.is_a?(BotMob::Install)
19
- BotMob.logger.info "Updating #{bot_class} bot record..."
20
- BotMob::Install.create_with_auth(auth)
36
+ begin
37
+ BotMob.logger.info "Updating #{class_name} database record for #{auth['external_id']}..."
38
+ BotMob::Install.create_with_auth(auth)
39
+ rescue ActiveRecord::StatementInvalid => e
40
+ BotMob.logger.info "Unable to save record: #{e.message}"
41
+ end
21
42
  end
22
43
 
23
- puts "Setting up a new bot with:\n #{auth.inspect}\n #{external_id}\n #{token}"
24
- bot = bot_class.new(external_id, token)
25
- @bots["#{bot_class}##{external_id}"] ||= bot
26
- @bots["#{bot_class}##{external_id}"].refresh
44
+ network = auth['network']
45
+ allocate(bot_class, network, auth)
27
46
  end
28
47
 
29
48
  def load
@@ -48,8 +67,23 @@ module BotMob
48
67
  @registry[bot_class.to_s] = bot_class
49
68
  end
50
69
 
70
+ private
71
+
51
72
  def available?
52
73
  @available.nil? || @available
53
74
  end
75
+
76
+ def allocate(bot_class, network, auth)
77
+ key = "#{bot_class}##{auth['network']}.#{auth['external_id']}"
78
+
79
+ if @bots[key]
80
+ BotMob.logger.info "Using existing #{key} bot"
81
+ else
82
+ BotMob.logger.info "Setting up a new #{bot_class} bot: #{key}"
83
+ @bots[key] = bot_class.new(auth['network'].to_sym, auth['external_id'], auth['data'] || {})
84
+ end
85
+
86
+ @bots[key].refresh
87
+ end
54
88
  end
55
89
  end
@@ -0,0 +1,10 @@
1
+ require 'bot_mob'
2
+ require 'slack-ruby-client'
3
+
4
+ module BotMob
5
+ module Slack
6
+ autoload :Ambassador, 'bot_mob/slack/ambassador'
7
+ autoload :Connection, 'bot_mob/slack/connection'
8
+ autoload :InboundMessage, 'bot_mob/slack/inbound_message'
9
+ end
10
+ end
@@ -5,6 +5,10 @@ module BotMob
5
5
  @code = code
6
6
  end
7
7
 
8
+ def self.setup(args)
9
+ new(args[:code])
10
+ end
11
+
8
12
  def token
9
13
  oauth_response.bot_access_token
10
14
  end
@@ -13,6 +17,10 @@ module BotMob
13
17
  oauth_response.bot_user_id
14
18
  end
15
19
 
20
+ def data
21
+ { token: token }
22
+ end
23
+
16
24
  def success?
17
25
  !!(external_id && token)
18
26
  end
@@ -0,0 +1,87 @@
1
+ module BotMob
2
+ module Slack
3
+ class Connection < BotMob::Connection
4
+ attr_reader :bot, :token
5
+
6
+ def initialize(bot, options = {})
7
+ @bot = bot
8
+ @token = options['token']
9
+ token.nil? ? set_state(:inactive) : setup
10
+ end
11
+
12
+ def self.setup(bot, options = {})
13
+ new(bot, options)
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.text.inspect}\"\n")
19
+ client.web_client.chat_postMessage(outbound_message.to_h)
20
+ end
21
+
22
+ def connect
23
+ return unless active?
24
+ set_state(:connecting)
25
+ establish
26
+ end
27
+
28
+ def reconnect
29
+ return unless active?
30
+ set_state(:reconnecting)
31
+ renew_client
32
+ setup
33
+ establish
34
+ end
35
+
36
+ def refresh
37
+ return unless active?
38
+
39
+ case true
40
+ when waiting?
41
+ connect
42
+ when disconnected?
43
+ reconnect
44
+ else
45
+ BotMob.logger.info "State[#{bot.class.to_s}##{bot.key}] - No Change"
46
+ end
47
+ end
48
+
49
+ def welcome_message
50
+ "Successfully connected, '#{client.team.name}' team at https://#{client.team.domain}.slack.com."
51
+ end
52
+
53
+ def client
54
+ @client ||= renew_client
55
+ end
56
+
57
+ private
58
+
59
+ def establish
60
+ client.start_async
61
+ rescue ::Slack::Web::Api::Error => e
62
+ BotMob.logger.info e.message
63
+ set_state(:waiting)
64
+ end
65
+
66
+ def setup
67
+ set_state(:waiting)
68
+ client.on(:hello) { set_state(:connected) }
69
+ client.on(:close) { set_state(:disconnecting) }
70
+ client.on(:closed) { set_state(:closed) }
71
+
72
+ client.on :message do |data|
73
+ message = BotMob::InboundMessage.parse({
74
+ network: :slack,
75
+ format: :websocket
76
+ }.merge(data))
77
+
78
+ bot.receive message
79
+ end
80
+ end
81
+
82
+ def renew_client
83
+ @client = ::Slack::RealTime::Client.new(token: @token)
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,52 @@
1
+ module BotMob
2
+ module Slack
3
+ class InboundMessage < BotMob::InboundMessage
4
+ attr_reader :token, :team_id, :team_domain, :params, :format,
5
+ :channel_id, :channel_name, :external_id, :user_name,
6
+ :command, :bot_id, :response_url, :ts
7
+
8
+ def initialize(params)
9
+ @format = params[:format] || :websocket
10
+ @network = params[:network]
11
+ @params = params
12
+ send("parse_#{format}".to_sym, @params)
13
+ end
14
+
15
+ def self.parse(params)
16
+ new(params)
17
+ end
18
+
19
+ def human?
20
+ bot_id.nil?
21
+ end
22
+
23
+ def command?
24
+ !command.nil?
25
+ end
26
+
27
+ private
28
+
29
+ def parse_websocket(data)
30
+ @text = data['text']
31
+ @channel_id = data['channel_id'] || data['channel']
32
+ @bot_id = data['bot_id']
33
+ @team = data['team']
34
+ @ts = data['ts']
35
+ @external_id = data['user_id']
36
+ end
37
+
38
+ def parse_webhook(data)
39
+ @token = data['token']
40
+ @team_id = data['team_id']
41
+ @team_domain = data['team_domain']
42
+ @channel_id = data['channel_id']
43
+ @channel_name = data['channel_name']
44
+ @external_id = data['user_id']
45
+ @user_name = data['user_name']
46
+ @command = data['command']
47
+ @text = data['text']
48
+ @response_url = data['response_url']
49
+ end
50
+ end
51
+ end
52
+ end
@@ -1,3 +1,3 @@
1
1
  module BotMob
2
- VERSION = '0.2.1'.freeze
2
+ VERSION = '0.2.2'.freeze
3
3
  end
@@ -1,3 +1,5 @@
1
+ require 'bunny'
2
+
1
3
  module BotMob
2
4
  # ## BotMob::Wire
3
5
  #
@@ -5,11 +7,26 @@ module BotMob
5
7
  # receives notifications from authenticated installs
6
8
  # over RabbitMQ
7
9
  class Wire
10
+ def initialize
11
+ @available = nil
12
+ end
13
+
14
+ # ## wire.publish
15
+ #
16
+ # The `publish` method takes a given message, converts it to JSON
17
+ # and pushes the json over the wire on the `wire.notifications` channel.
18
+ # This is primarily used for passing successful authentication to the
19
+ # running BotMob Application.
8
20
  def publish(message = {})
9
21
  BotMob.logger.info "Publishing #{message} to wire.notifications"
10
22
  queue.publish(message.to_json, routing_key: queue.name)
11
23
  end
12
24
 
25
+ # ## wire.listen
26
+ #
27
+ # The `listen` method takes a block to perform when a
28
+ # message is received over the wire. The message published
29
+ # to the wire is parsed JSON provided to the calling block.
13
30
  def listen
14
31
  return if !block_given? || queue.nil?
15
32
 
@@ -20,8 +37,9 @@ module BotMob
20
37
  BotMob.logger.info "#{e.channel_close.reply_code}, message: #{e.channel_close.reply_text}"
21
38
  end
22
39
 
40
+
23
41
  def available?
24
- @available
42
+ !!@available
25
43
  end
26
44
 
27
45
  private
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.2.1
4
+ version: 0.2.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-08-19 00:00:00.000000000 Z
11
+ date: 2016-08-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bunny
@@ -229,10 +229,19 @@ files:
229
229
  - lib/bot_mob/authority.rb
230
230
  - lib/bot_mob/bot.rb
231
231
  - lib/bot_mob/connection.rb
232
+ - lib/bot_mob/development/ambassador.rb
233
+ - lib/bot_mob/development/connection.rb
234
+ - lib/bot_mob/development/inbound_message.rb
235
+ - lib/bot_mob/errors.rb
232
236
  - lib/bot_mob/inbound_message.rb
233
237
  - lib/bot_mob/install.rb
238
+ - lib/bot_mob/nil_connection.rb
239
+ - lib/bot_mob/outbound_message.rb
234
240
  - lib/bot_mob/roster.rb
241
+ - lib/bot_mob/slack.rb
235
242
  - lib/bot_mob/slack/ambassador.rb
243
+ - lib/bot_mob/slack/connection.rb
244
+ - lib/bot_mob/slack/inbound_message.rb
236
245
  - lib/bot_mob/version.rb
237
246
  - lib/bot_mob/wire.rb
238
247
  homepage: https://github.com/mwerner/bot_mob