bot_mob 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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