bot_mob 0.2.1 → 0.2.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: 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