bot_mob 0.2.2 → 0.3.0

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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.env +1 -0
  3. data/.gitignore +0 -1
  4. data/.rubocop.yml +16 -0
  5. data/.travis.yml +1 -1
  6. data/Gemfile +0 -2
  7. data/LICENSE.txt +21 -0
  8. data/README.md +5 -4
  9. data/bin/console +8 -4
  10. data/bin/mob +4 -0
  11. data/bot_mob.gemspec +8 -14
  12. data/examples/fizz_buzz_bot.rb +30 -0
  13. data/examples/hello_bot.rb +14 -0
  14. data/examples/server.rb +7 -0
  15. data/lib/bot_mob.rb +46 -32
  16. data/lib/bot_mob/bot.rb +146 -29
  17. data/lib/bot_mob/command.rb +38 -0
  18. data/lib/bot_mob/connection.rb +18 -50
  19. data/lib/bot_mob/core_ext/hash.rb +148 -0
  20. data/lib/bot_mob/core_ext/string.rb +8 -0
  21. data/lib/bot_mob/environment.rb +25 -0
  22. data/lib/bot_mob/inbound_message.rb +13 -32
  23. data/lib/bot_mob/networks/roaming.rb +12 -0
  24. data/lib/bot_mob/networks/roaming/connection.rb +34 -0
  25. data/lib/bot_mob/networks/roaming/outbound_message.rb +15 -0
  26. data/lib/bot_mob/networks/slack.rb +11 -0
  27. data/lib/bot_mob/networks/slack/connection.rb +37 -0
  28. data/lib/bot_mob/networks/slack/outbound_message.rb +18 -0
  29. data/lib/bot_mob/outbound_message.rb +23 -10
  30. data/lib/bot_mob/public/console.js +10 -0
  31. data/lib/bot_mob/roster.rb +8 -80
  32. data/lib/bot_mob/server.rb +47 -0
  33. data/lib/bot_mob/version.rb +1 -1
  34. data/lib/bot_mob/views/authorize.erb +1 -0
  35. data/lib/bot_mob/views/console.erb +6 -0
  36. data/lib/bot_mob/views/index.erb +1 -0
  37. data/lib/bot_mob/views/layout.erb +11 -0
  38. metadata +50 -110
  39. data/lib/bot_mob/ambassador.rb +0 -34
  40. data/lib/bot_mob/application.rb +0 -43
  41. data/lib/bot_mob/authority.rb +0 -41
  42. data/lib/bot_mob/development/ambassador.rb +0 -21
  43. data/lib/bot_mob/development/connection.rb +0 -34
  44. data/lib/bot_mob/development/inbound_message.rb +0 -17
  45. data/lib/bot_mob/errors.rb +0 -12
  46. data/lib/bot_mob/install.rb +0 -16
  47. data/lib/bot_mob/nil_connection.rb +0 -29
  48. data/lib/bot_mob/slack.rb +0 -10
  49. data/lib/bot_mob/slack/ambassador.rb +0 -58
  50. data/lib/bot_mob/slack/connection.rb +0 -87
  51. data/lib/bot_mob/slack/inbound_message.rb +0 -52
  52. data/lib/bot_mob/wire.rb +0 -69
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a6c06b15e2ece464e52b403fd36bd610487da2b8
4
- data.tar.gz: 34dfe52f53892d31d918aae0611dcc347f571ab7
3
+ metadata.gz: 6463fee5649865aa2f59e0e19b7b3670ff5e20c8
4
+ data.tar.gz: e96b710afa365c7b9152a2fd6b5af76dec618a0f
5
5
  SHA512:
6
- metadata.gz: 34c4efc4364f76889ea9968669b3bcf7c3c5c8b85c6715dff07f138dbaa8bd3c1dd6a57416761251ccc7ba81244e9c054acb8cb6b084b69503ac6c9a8938fa58
7
- data.tar.gz: 2c4d05434f48c330d51f426404e7170fd63b410e7e09f67d532d05aa2ab78ef92171d617d75958c584a339f978b6e9988d84655530fb46ad42383e7c103550b6
6
+ metadata.gz: a7172822c373e804dfe65fb589ddf12bd78403f8416e2b91ce2bf9269268cce4f36039d452d1a9ee5c880ae30372eecb8d8caea8c953e40956fc6ac94ca8faa3
7
+ data.tar.gz: 2c70c6c01e9bb1d4c6a6537ce889e5774bf486c2feac444bc75c64a61eeb1375b20c74e056c9efa046b0aae87f6fa34b3c55550e61380132cbc2741e31954163
data/.env ADDED
@@ -0,0 +1 @@
1
+ SLACK_TOKEN=xoxb-119874907665-RPEF89Sv0MaEnGyKUHCl11qz
data/.gitignore CHANGED
@@ -7,4 +7,3 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
- .env
@@ -0,0 +1,16 @@
1
+ AllCops:
2
+ Exclude:
3
+ - 'bot_mob.gemspec'
4
+ - 'lib/bot_mob/core_ext/*.rb'
5
+
6
+ Style/RegexpLiteral:
7
+ Enabled: False
8
+
9
+ Style/PerlBackrefs:
10
+ Enabled: False
11
+
12
+ Style/BlockDelimiters:
13
+ Enabled: False
14
+
15
+ Metrics/LineLength:
16
+ Max: 110
@@ -1,5 +1,5 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - 2.3.1
4
+ - 2.1.8
5
5
  before_install: gem install bundler -v 1.12.5
data/Gemfile CHANGED
@@ -1,4 +1,2 @@
1
1
  source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in bot-mob.gemspec
4
2
  gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Matthew Werner
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md CHANGED
@@ -1,9 +1,5 @@
1
1
  # BotMob
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/bot_mob`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
6
-
7
3
  ## Installation
8
4
 
9
5
  Add this line to your application's Gemfile:
@@ -34,3 +30,8 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
34
30
 
35
31
  Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/bot_mob. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
36
32
 
33
+
34
+ ## License
35
+
36
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
37
+
@@ -1,12 +1,16 @@
1
1
  #!/usr/bin/env ruby
2
-
3
2
  require 'bundler/setup'
4
3
  require 'dotenv'
5
4
  Dotenv.load
6
5
 
7
6
  require 'bot_mob'
8
- require File.join(BotMob.root, 'spec/script/active_record')
9
- require File.join(BotMob.root, 'spec/support/mock_bot')
10
- require 'irb'
11
7
 
8
+ # You can add fixtures and/or initialization code here to make experimenting
9
+ # with your gem easier. You can also use a different console, if you like.
10
+
11
+ # (If you use this, don't forget to add pry to your Gemfile!)
12
+ # require "pry"
13
+ # Pry.start
14
+
15
+ require 'irb'
12
16
  IRB.start
data/bin/mob ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'bundler/setup'
3
+ require 'bot_mob'
4
+ BotMob::Command.new.handle(ARGV)
@@ -9,32 +9,26 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ['Matthew Werner']
10
10
  spec.email = ['m@mjw.io']
11
11
 
12
- spec.summary = 'Build your bots'
13
- spec.description = 'Easily create your own bot mob'
12
+ spec.summary = 'The bot mob framework'
13
+ spec.description = 'Easily create and manage bots'
14
14
  spec.homepage = 'https://github.com/mwerner/bot_mob'
15
+ spec.license = 'MIT'
15
16
 
16
17
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
18
  f.match(%r{^(test|spec|features)/})
18
19
  end
19
-
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'
24
23
 
25
- spec.add_dependency 'bunny', '~> 2.5'
24
+ spec.add_dependency 'sinatra', '~> 1.4'
26
25
  spec.add_dependency 'slack-ruby-client', '~> 0.7'
27
- spec.add_dependency 'celluloid-io', '~> 0.17'
28
- spec.add_dependency 'activerecord', '>= 4.1'
29
26
 
30
- spec.add_development_dependency 'dotenv'
31
27
  spec.add_development_dependency 'bundler', '~> 1.12'
32
28
  spec.add_development_dependency 'rake', '~> 10.0'
33
- spec.add_development_dependency 'rubocop', '~> 0.40'
34
- spec.add_development_dependency 'rubocop-rspec'
35
- spec.add_development_dependency 'simplecov', '~> 0.12'
36
29
  spec.add_development_dependency 'rspec', '~> 3.0'
37
- spec.add_development_dependency 'rack-test'
38
- spec.add_development_dependency 'webmock'
39
- spec.add_development_dependency 'sqlite3', '~> 1.3'
30
+ spec.add_development_dependency 'dotenv', '~> 2.1'
31
+ spec.add_development_dependency 'rubocop', '~> 0.45'
32
+ spec.add_development_dependency 'simplecov', '~> 0.12'
33
+ spec.add_development_dependency 'webmock', '~> 1.22'
40
34
  end
@@ -0,0 +1,30 @@
1
+ require 'bot_mob'
2
+
3
+ # FizzBuzz Bot Usage:
4
+ #
5
+ # FizzBuzzBot.new.receive('15') #=> "FizzBuzz"
6
+ class FizzBuzzBot < BotMob::Bot
7
+ def respond?(message)
8
+ message.body.to_i.to_s == message.body
9
+ end
10
+
11
+ def respond(message)
12
+ fizz_buzz(message.body.to_i)
13
+ end
14
+
15
+ private
16
+
17
+ def fizz_buzz(number)
18
+ if multiple?(number, 3) && multiple?(number, 5)
19
+ 'FizzBuzz'
20
+ elsif multiple?(number, 3)
21
+ 'Fizz'
22
+ elsif multiple?(number, 5)
23
+ 'Buzz'
24
+ end
25
+ end
26
+
27
+ def multiple?(number, n)
28
+ (number % n).zero?
29
+ end
30
+ end
@@ -0,0 +1,14 @@
1
+ require 'bot_mob'
2
+
3
+ # HellobBot
4
+ class HelloBot < BotMob::Bot
5
+ command :bye
6
+
7
+ def bye
8
+ 'NOOOO!'
9
+ end
10
+
11
+ def respond(message)
12
+ "Hello #{message.body}!"
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ require_relative 'fizz_buzz_bot'
2
+ require_relative 'hello_bot'
3
+
4
+ BotMob.start! do |app|
5
+ app.register FizzBuzzBot
6
+ app.register HelloBot.new(network: :slack, token: ENV['SLACK_TOKEN'])
7
+ end
@@ -1,56 +1,70 @@
1
- $LOAD_PATH.unshift File.dirname(__FILE__)
2
- require 'bot_mob/errors'
1
+ require 'logger'
2
+ require 'slack-ruby-client'
3
+ require 'bot_mob/core_ext/hash'
4
+ require 'bot_mob/core_ext/string'
3
5
  require 'bot_mob/version'
4
6
 
5
- # ## BotMob
7
+ # # BotMob
6
8
  #
7
- # BotMob provides all the tools you need to easily
8
- # connect multiple bots to chat networks
9
+ # BotMob base module namespace
9
10
  module BotMob
10
- autoload :Ambassador, 'bot_mob/ambassador'
11
- autoload :Application, 'bot_mob/application'
12
- autoload :Authority, 'bot_mob/authority'
11
+ class InvalidNetworkError < StandardError; end
12
+ class UnspecifiedTargetNetworkError < StandardError; end
13
+ class UnconnectedTargetNetworkError < StandardError; end
14
+ class UndefinedResponseError < StandardError; end
15
+ class NetworkConnectionError < StandardError; end
16
+
13
17
  autoload :Bot, 'bot_mob/bot'
18
+ autoload :Environment, 'bot_mob/environment'
19
+ autoload :Command, 'bot_mob/command'
14
20
  autoload :Connection, 'bot_mob/connection'
15
21
  autoload :InboundMessage, 'bot_mob/inbound_message'
16
- autoload :Install, 'bot_mob/install'
17
- autoload :NilConnection, 'bot_mob/nil_connection'
18
22
  autoload :OutboundMessage, 'bot_mob/outbound_message'
19
23
  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
24
+ autoload :Server, 'bot_mob/server'
27
25
 
28
- @@networks = {
29
- dev: BotMob::Development
30
- }
31
-
32
- def self.networks
33
- @@networks
34
- end
26
+ # Networks
27
+ require 'bot_mob/networks/roaming'
28
+ require 'bot_mob/networks/slack'
35
29
 
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]
30
+ # ## `start!`
31
+ #
32
+ # Start the bot mob server
33
+ #
34
+ # Usage:
35
+ #
36
+ # BotMob.start! do |app|
37
+ # app.register MyBot
38
+ # end
39
+ def self.start!
40
+ roster = BotMob::Roster.new
41
+ yield(roster)
42
+ BotMob::Server.run! roster: roster
42
43
  end
43
44
 
45
+ # ## `root`
46
+ #
47
+ # Provide the base root path of the gem directory
44
48
  def self.root
45
49
  File.expand_path('../..', __FILE__)
46
50
  end
47
51
 
52
+ # ## `env`
53
+ #
54
+ # The specified environment for your bot's runtime
55
+ def self.env
56
+ @environment ||= BotMob::Environment.new(ENV['MOB_ENV'])
57
+ end
58
+
59
+ # ## `logger`
60
+ #
61
+ # Provide a logger for the BotMob gem, silences test env output by default
48
62
  def self.logger
49
63
  @logger ||= begin
50
- buffer = Logger.new(ENV['RACK_ENV'] == 'test' ? '/dev/null' : STDOUT)
64
+ buffer = Logger.new(ENV['MOB_ENV'] == 'test' ? '/dev/null' : STDOUT)
51
65
  if buffer
52
66
  buffer.level = ($PROGRAM_NAME == 'irb' ? Logger::DEBUG : Logger::INFO)
53
- buffer.formatter = proc { |severity, datetime, progname, msg| "#{msg}\n" }
67
+ buffer.formatter = proc { |_s, _d, _p, msg| "#{msg}\n" }
54
68
  end
55
69
  buffer
56
70
  end
@@ -1,53 +1,170 @@
1
- require 'forwardable'
2
-
3
1
  module BotMob
4
2
  # ## BotMob::Bot
5
3
  #
6
4
  # This is the base for all bots that will connect
7
5
  # to respond to messages received by your application.
8
6
  class Bot
9
- attr_accessor :id, :network
10
- attr_reader :options
7
+ attr_reader :network, :options
11
8
 
12
- extend Forwardable
13
- def_delegators :connection, :connect, :reconnect, :refresh, :client, :deliver
9
+ class << self
10
+ def commands
11
+ @commands ||= []
12
+ end
14
13
 
15
- def initialize(*args)
16
- @network, @id, @options = *args
14
+ def command(method_name)
15
+ @commands ||= []
16
+ @commands << method_name.to_sym
17
+ end
17
18
  end
18
19
 
19
- def key
20
- "#{network}.#{id}"
20
+ # A bot does not require any params to be initialized
21
+ def initialize(**options)
22
+ @connections = {}
23
+ @options = options
24
+ options[:slack] ? connect!(:slack, options[:slack]) : connect!(:roaming)
21
25
  end
22
26
 
23
- def receive(message)
24
- inbound_message = message.is_a?(String) ? BotMob::InboundMessage.new(message) : message
25
- return unless respond?(inbound_message)
27
+ # ## `connect`
28
+ # `connect` allows you to specify a network to which
29
+ # you are intending to connect your bot
30
+ def connect!(network, options = {})
31
+ if @connections[network.to_sym]
32
+ BotMob.logger.warn "#{name} is already connected to #{network}"
33
+ return
34
+ end
26
35
 
27
- BotMob.logger.info("Received message at #{Time.now}")
28
- BotMob.logger.info(" Parameters: #{inbound_message.params}")
29
- respond(inbound_message)
30
- end
36
+ connection = BotMob::Connection.setup network, self, @options.merge(options)
37
+ return false if connection.nil?
31
38
 
32
- def respond(inbound_message)
33
- outbound = BotMob::OutboundMessage.new(text: "#{self.class} ACK! #{inbound_message.text}")
34
- deliver(outbound)
39
+ @connections[network.to_sym] = connection
35
40
  end
36
41
 
37
- def respond?(inbound_message)
38
- true
42
+ # ## `connected?`
43
+ # Simple assessment to determine if a given network has been connected
44
+ def connected?(network = nil)
45
+ network.nil? ? @connections.keys.empty? : !@connections[network.to_sym].nil?
39
46
  end
40
47
 
41
- private
48
+ # ## `receive`
49
+ # Receive a message from an implementation of this bot. This is the primary
50
+ # entry point for your bot to accept input.
51
+ #
52
+ # bot = BotMob.new
53
+ # bot.receive('foo')
54
+ #
55
+ # Specify *message* as the received data
56
+ def receive(message, options = {})
57
+ inbound_message = BotMob::InboundMessage.prepare(message, options)
42
58
 
43
- def connection
44
- @connection ||= begin
45
- if BotMob.valid_network?(network)
46
- BotMob::Connection.setup(network, self, options)
47
- else
48
- BotMob::NilConnection.new(self)
59
+ warrant_response = respond?(inbound_message)
60
+ log_message(inbound_message) if warrant_response || log_responseless_activity?
61
+ unless warrant_response
62
+ if log_responseless_activity?
63
+ BotMob.logger.info(' > Does not warrant response')
49
64
  end
65
+
66
+ return
50
67
  end
68
+
69
+ process_response(inbound_message)
70
+ end
71
+
72
+ # ## `receive_command`
73
+ # Receive a message from an implementation of this bot. This is the primary
74
+ # entry point for your bot to accept input.
75
+ #
76
+ # bot = BotMob.new
77
+ # bot.receive('foo')
78
+ #
79
+ # Specify *message* as the received data
80
+ def receive_command(command)
81
+ BotMob.logger.info("#{name} received command at #{Time.now}")
82
+ BotMob.logger.info(" Parameters: {:text => \"#{command}\"}")
83
+
84
+ send(command) if valid_command?(command)
85
+ end
86
+
87
+ # ## `valid_command?`
88
+ # A bot must respond to the given name explicitly
89
+ # declared in the class definition.
90
+ def valid_command?(name)
91
+ self.class.commands.include?(name.to_sym) && respond_to?(name.to_sym)
92
+ end
93
+
94
+ # ## `name`
95
+ # Convenience method to get the class name for the current bot class
96
+ def name
97
+ self.class.to_s
98
+ end
99
+
100
+ private
101
+
102
+ # ## `respond?`
103
+ #
104
+ # `respond?` is the determining method of the involvement of a bot.
105
+ # It should be a lightweight check of it's own logic against the
106
+ # contents of the inbound message. Commonly used for checking language
107
+ # used or regex matching of the inbound messages' contents.
108
+ #
109
+ # Defaults to true
110
+ def respond?(_inbound_message)
111
+ true
112
+ end
113
+
114
+ # ## `respond`
115
+ #
116
+ # `respond` accepts an inbound message, does whatever processing it
117
+ # needs, generates an outbound message and passes the message to the
118
+ # `deliver` method, which delegates to the currently assigned network.
119
+ #
120
+ # A respond method that is not overloaded will return the outbound message
121
+ def respond(inbound_message)
122
+ raise BotMob::UndefinedResponseError if BotMob.env.production?
123
+
124
+ BotMob::OutboundMessage.new(
125
+ body: "ACK - #{inbound_message.body}",
126
+ network: inbound_message.network
127
+ )
128
+ end
129
+
130
+ # ## `process_response`
131
+ #
132
+ # Begin the process of responding to a given message
133
+ def process_response(message)
134
+ response = respond(message)
135
+ outbound_message = BotMob::OutboundMessage.prepare(response, inbound: message)
136
+ deliver(outbound_message)
137
+ end
138
+
139
+ # ## `deliver`
140
+ #
141
+ # `deliver` takes the provided outbound message and executes the
142
+ # necessary actions to ensure the message arrives to the bot's network
143
+ def deliver(outbound_message)
144
+ outbound_network = outbound_message.network
145
+
146
+ # Assume they want the only connected network if only one exists
147
+ outbound_network ||= @connections.values.first if @connections.values.one?
148
+ raise BotMob::UnspecifiedTargetNetworkError if outbound_network.nil?
149
+
150
+ connection = @connections[outbound_network.to_sym]
151
+ raise BotMob::UnconnectedTargetNetworkError if connection.nil?
152
+
153
+ connection.deliver(outbound_message)
154
+ end
155
+
156
+ # ## `log_responseless_activity?`
157
+ #
158
+ # Log all received messages regardless of the result of `respond?`
159
+ #
160
+ # Defaults to true in development
161
+ def log_responseless_activity?
162
+ BotMob.env.development?
163
+ end
164
+
165
+ def log_message(message)
166
+ BotMob.logger.info("#{name} received message at #{Time.now}")
167
+ BotMob.logger.info(" Parameters: #{message.params}")
51
168
  end
52
169
  end
53
170
  end