stealth 0.10.6 → 1.0.0.pre1

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
  SHA256:
3
- metadata.gz: e3c11a8fa36934b10676a7849577d116f8f855205bc9293be4feecd8b70bbfec
4
- data.tar.gz: d40c666b0092cd2318d3754d7c8f3824ff3720f228b54a5104bef715c16bf67e
3
+ metadata.gz: a447e930b543fe9125bb09baaaf12f160f5fa715e346fd6b304285f5a31f56f2
4
+ data.tar.gz: f5ff084dcb475b87ff972458751b16cc05779bed6eebf67f3feb15ba97909d68
5
5
  SHA512:
6
- metadata.gz: 24584d7ffb9f5cfcb3f46476128185182b0ef93ddbeeeea8e12e049c187264847d371044cd72381d5d866bc60aaa9c003039e6a763335ce8b73ecfa165336d2b
7
- data.tar.gz: 9023451c457607c89d1116f2a8de0d0ae7cec8175b9693ac598664f5a1b530bbf3fc1925f21c1f6fe2f493a963d3ced1d825dbf8f6ce1b815076dda6a38a7b12
6
+ metadata.gz: ad24b962addcbe10da90dc44a8b0220c7ced906c42a525f45d37f4ee9baad43600b968cae7fdabc2f5a5cad4279718203ec49680a396c6fa8b24429a6f8dc891
7
+ data.tar.gz: 207b7e854c327ef050a013017da9e284d820f3cac5819af8558ea9b7a86c8f252bef1fb7fe9bd8741fb5119d56be8523ae19930f0403d33f3e7b99e11ba132ab
data/Gemfile.lock CHANGED
@@ -1,55 +1,55 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- stealth (0.10.3)
5
- activesupport (~> 5.2.0.rc1)
4
+ stealth (0.10.6)
5
+ activesupport (~> 5.2.0)
6
6
  multi_json (~> 1.12)
7
7
  puma (~> 3.10)
8
8
  sidekiq (~> 5.0)
9
- sinatra (~> 2.0)
9
+ sinatra (~> 2.0.1)
10
10
  thor (~> 0.20)
11
11
 
12
12
  GEM
13
13
  remote: https://rubygems.org/
14
14
  specs:
15
- activesupport (5.2.0.rc1)
15
+ activesupport (5.2.0)
16
16
  concurrent-ruby (~> 1.0, >= 1.0.2)
17
- i18n (~> 0.7)
17
+ i18n (>= 0.7, < 2)
18
18
  minitest (~> 5.1)
19
19
  tzinfo (~> 1.1)
20
20
  concurrent-ruby (1.0.5)
21
21
  connection_pool (2.2.1)
22
22
  diff-lcs (1.3)
23
- i18n (0.9.5)
23
+ i18n (1.0.0)
24
24
  concurrent-ruby (~> 1.0)
25
25
  minitest (5.11.3)
26
26
  mock_redis (0.17.3)
27
27
  multi_json (1.13.1)
28
28
  mustermann (1.0.2)
29
- oj (3.3.6)
30
- puma (3.11.2)
31
- rack (2.0.3)
29
+ oj (3.5.0)
30
+ puma (3.11.3)
31
+ rack (2.0.4)
32
32
  rack-protection (2.0.1)
33
33
  rack
34
- rack-test (0.7.0)
34
+ rack-test (0.8.3)
35
35
  rack (>= 1.0, < 3)
36
36
  redis (4.0.1)
37
- rspec (3.6.0)
38
- rspec-core (~> 3.6.0)
39
- rspec-expectations (~> 3.6.0)
40
- rspec-mocks (~> 3.6.0)
41
- rspec-core (3.6.0)
42
- rspec-support (~> 3.6.0)
43
- rspec-expectations (3.6.0)
37
+ rspec (3.7.0)
38
+ rspec-core (~> 3.7.0)
39
+ rspec-expectations (~> 3.7.0)
40
+ rspec-mocks (~> 3.7.0)
41
+ rspec-core (3.7.1)
42
+ rspec-support (~> 3.7.0)
43
+ rspec-expectations (3.7.0)
44
44
  diff-lcs (>= 1.2.0, < 2.0)
45
- rspec-support (~> 3.6.0)
46
- rspec-mocks (3.6.0)
45
+ rspec-support (~> 3.7.0)
46
+ rspec-mocks (3.7.0)
47
47
  diff-lcs (>= 1.2.0, < 2.0)
48
- rspec-support (~> 3.6.0)
49
- rspec-support (3.6.0)
48
+ rspec-support (~> 3.7.0)
49
+ rspec-support (3.7.1)
50
50
  rspec_junit_formatter (0.3.0)
51
51
  rspec-core (>= 2, < 4, != 2.12.0)
52
- sidekiq (5.1.1)
52
+ sidekiq (5.1.3)
53
53
  concurrent-ruby (~> 1.0)
54
54
  connection_pool (~> 2.2, >= 2.2.0)
55
55
  rack-protection (>= 1.5.0)
data/README.md CHANGED
@@ -1 +1,46 @@
1
- # Stealth
1
+ # <a href='https://hellostealth.org'><img src='logo.svg' height='120' alt='Stealth Logo' aria-label='hellostealth.org' /></a>
2
+
3
+ Stealth is a Ruby based framework for creating conversational (voice & chat) bots. It's design is inspired by Ruby on Rails's philosophy of convention over configuration. It has an MVC architecture with the slight caveat that `views` are aptly named `replies`.
4
+
5
+ ## Getting Started
6
+
7
+ Getting started with Stealth is simple:
8
+
9
+ ```
10
+ > gem install stealth
11
+ > stealth new <bot>
12
+ ```
13
+
14
+ ## Service Integrations
15
+
16
+ Stealth is extensible. All service integrations are split out into separate Ruby Gems. Things like analytics and natural language processing ([NLP](https://en.wikipedia.org/wiki/Natural-language_processing)) can be added in as gems as well.
17
+
18
+ Currently, there are gems for:
19
+
20
+ ### Messaging
21
+ * [Facebook Messenger](https://github.com/hellostealth/stealth-facebook)
22
+ * [Twilio SMS](https://github.com/hellostealth/stealth-twilio)
23
+
24
+ ### Analytics
25
+ * [Mixpanel](https://github.com/hellostealth/stealth-mixpanel)
26
+
27
+ ## Docs
28
+
29
+ You can find our full docs [here](https://docs.hellostealth.org).
30
+
31
+ ## Thanks
32
+
33
+ Stealth wouldn't exist without the great work of many other open source projects and people including:
34
+
35
+ * [Ruby](https://www.ruby-lang.org/) for creating our favorite programming language;
36
+ * [Ruby on Rails](http://rubyonrails.org) for projects like `ActiveRecord` and serving as an inspiration;
37
+ * [Thor](http://whatisthor.com) for providing us with CLI tools and generators;
38
+ * [Sinatra](http://sinatrarb.com) for providing a fantastic, modular way for handling HTTP requests;
39
+ * [Sidekiq](https://sidekiq.org) for the super quick background jobs;
40
+ * [Dr. Robert Ford](http://westworld.wikia.com/wiki/Robert_Ford) a.k.a. Anthony Hopkins.
41
+
42
+ ## License
43
+
44
+ "Stealth" and the Stealth logo are copyright (c) 2018 The Black Ops Bureau Inc.
45
+
46
+ Stealth source code is released under the MIT License.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.10.6
1
+ 1.0.0.pre1
data/lib/stealth/base.rb CHANGED
@@ -11,20 +11,6 @@ require 'stealth/version'
11
11
  require 'stealth/errors'
12
12
  require 'stealth/logger'
13
13
  require 'stealth/configuration'
14
- require 'stealth/jobs'
15
- require 'stealth/dispatcher'
16
- require 'stealth/server'
17
- require 'stealth/reply'
18
- require 'stealth/scheduled_reply'
19
- require 'stealth/service_reply'
20
- require 'stealth/service_message'
21
- require 'stealth/session'
22
- require 'stealth/controller/callbacks'
23
- require 'stealth/controller/catch_all'
24
- require 'stealth/controller/helpers'
25
- require 'stealth/controller/controller'
26
- require 'stealth/flow/base'
27
- require 'stealth/services/base_client'
28
14
 
29
15
  module Stealth
30
16
 
@@ -36,7 +22,25 @@ module Stealth
36
22
  @root ||= File.expand_path(Pathname.new(Dir.pwd))
37
23
  end
38
24
 
25
+ def self.reloader
26
+ @reloader ||= begin
27
+ if Stealth.env == 'development'
28
+ ActiveSupport::Dependencies.mechanism = :load
29
+ ActiveSupport::Dependencies.autoload_paths = Dir["bot/**/*.rb"]
30
+ # @reloader = ActiveSupport::FileUpdateChecker.new(Dir["bot/**/*.rb"]) do
31
+ # puts "YAAASSS"
32
+ # end
33
+ # ActiveSupport::Dependencies.autoload_paths = %w[bot/controllers bot/helpers bot/models]
34
+ # ActiveSupport::Dependencies.autoload_paths = [File.join(Stealth.root, "bot")]
35
+ end
36
+ end
37
+ end
38
+
39
39
  def self.boot
40
+ if Stealth.env == 'development'
41
+ ActiveSupport::Dependencies.mechanism = :load
42
+ ActiveSupport::Dependencies.autoload_paths = Dir["bot/**/*.rb"]
43
+ end
40
44
  load_environment
41
45
  end
42
46
 
@@ -72,14 +76,14 @@ module Stealth
72
76
  require File.join(Stealth.root, 'config', 'boot')
73
77
  require_directory("config/initializers")
74
78
  # Require explicitly to ensure it loads first
75
- require File.join(Stealth.root, 'bot', 'controllers', 'bot_controller')
79
+ require_dependency File.join(Stealth.root, 'bot', 'controllers', 'bot_controller')
76
80
  require_directory("bot")
77
81
  end
78
82
 
79
83
  private
80
84
 
81
85
  def self.require_directory(directory)
82
- for_each_file_in(directory) { |file| require_relative(file) }
86
+ for_each_file_in(directory) { |file| require_dependency(file) }
83
87
  end
84
88
 
85
89
  def self.for_each_file_in(directory, &blk)
@@ -91,3 +95,19 @@ module Stealth
91
95
  end
92
96
 
93
97
  end
98
+
99
+ require 'stealth/jobs'
100
+ require 'stealth/dispatcher'
101
+ require 'stealth/server'
102
+ require 'stealth/reply'
103
+ require 'stealth/scheduled_reply'
104
+ require 'stealth/service_reply'
105
+ require 'stealth/service_message'
106
+ require 'stealth/session'
107
+ require 'stealth/controller/callbacks'
108
+ require 'stealth/controller/replies'
109
+ require 'stealth/controller/catch_all'
110
+ require 'stealth/controller/helpers'
111
+ require 'stealth/controller/controller'
112
+ require 'stealth/flow/base'
113
+ require 'stealth/services/base_client'
@@ -5,6 +5,7 @@ module Stealth
5
5
  class Controller
6
6
 
7
7
  include Stealth::Controller::Callbacks
8
+ include Stealth::Controller::Replies
8
9
  include Stealth::Controller::CatchAll
9
10
  include Stealth::Controller::Helpers
10
11
 
@@ -35,37 +36,6 @@ module Stealth
35
36
  raise(Stealth::Errors::ControllerRoutingNotImplemented, "Please implement `route` method in BotController")
36
37
  end
37
38
 
38
- def send_replies
39
- service_reply = Stealth::ServiceReply.new(
40
- recipient_id: current_user_id,
41
- yaml_reply: action_replies,
42
- context: binding
43
- )
44
-
45
- for reply in service_reply.replies do
46
- handler = reply_handler.new(
47
- recipient_id: current_user_id,
48
- reply: reply
49
- )
50
-
51
- translated_reply = handler.send(reply.reply_type)
52
- client = service_client.new(reply: translated_reply)
53
- client.transmit
54
-
55
- # If this was a 'delay' type of reply, let's respect the delay
56
- if reply.reply_type == 'delay'
57
- begin
58
- sleep_duration = Float(reply["duration"])
59
- sleep(sleep_duration)
60
- rescue ArgumentError, TypeError
61
- raise(ArgumentError, 'Invalid duration specified. Duration must be a float')
62
- end
63
- end
64
- end
65
-
66
- @progressed = :sent_replies
67
- end
68
-
69
39
  def flow_controller
70
40
  @flow_controller ||= begin
71
41
  flow_controller = [current_session.flow_string.pluralize, 'controller'].join('_').classify.constantize
@@ -132,36 +102,6 @@ module Stealth
132
102
 
133
103
  private
134
104
 
135
- def reply_handler
136
- begin
137
- Kernel.const_get("Stealth::Services::#{current_service.classify}::ReplyHandler")
138
- rescue NameError
139
- raise(Stealth::Errors::ServiceNotRecognized, "The service '#{current_service}' was not recognized")
140
- end
141
- end
142
-
143
- def service_client
144
- begin
145
- Kernel.const_get("Stealth::Services::#{current_service.classify}::Client")
146
- rescue NameError
147
- raise(Stealth::Errors::ServiceNotRecognized, "The service '#{current_service}' was not recognized")
148
- end
149
- end
150
-
151
- def replies_folder
152
- current_session.flow_string.underscore.pluralize
153
- end
154
-
155
- def action_replies
156
- reply_file_path = File.join(Stealth.root, 'bot', 'replies', replies_folder, "#{current_session.state_string}.yml")
157
-
158
- begin
159
- File.read(reply_file_path)
160
- rescue Errno::ENOENT
161
- raise(Stealth::Errors::ReplyNotFound, "Could not find a reply in #{reply_file_path}")
162
- end
163
- end
164
-
165
105
  def update_session(flow:, state:)
166
106
  Stealth::Logger.l(topic: "session", message: "User #{current_user_id}: updating session to #{flow}->#{state}")
167
107
 
@@ -0,0 +1,99 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Stealth
5
+ class Controller
6
+ module Replies
7
+
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+
12
+ class_attribute :_preprocessors, default: [:erb]
13
+ class_attribute :_replies_path, default: [Stealth.root, 'bot', 'replies']
14
+
15
+ def send_replies
16
+ yaml_reply, preprocessor = action_replies
17
+
18
+ service_reply = Stealth::ServiceReply.new(
19
+ recipient_id: current_user_id,
20
+ yaml_reply: yaml_reply,
21
+ preprocessor: preprocessor,
22
+ context: binding
23
+ )
24
+
25
+ for reply in service_reply.replies do
26
+ handler = reply_handler.new(
27
+ recipient_id: current_user_id,
28
+ reply: reply
29
+ )
30
+
31
+ translated_reply = handler.send(reply.reply_type)
32
+ client = service_client.new(reply: translated_reply)
33
+ client.transmit
34
+
35
+ # If this was a 'delay' type of reply, we insert the delay
36
+ if reply.reply_type == 'delay'
37
+ begin
38
+ sleep_duration = Float(reply["duration"])
39
+ sleep(sleep_duration)
40
+ rescue ArgumentError, TypeError
41
+ raise(ArgumentError, 'Invalid duration specified. Duration must be a float')
42
+ end
43
+ end
44
+ end
45
+
46
+ @progressed = :sent_replies
47
+ end
48
+
49
+ private
50
+
51
+ def service_client
52
+ begin
53
+ Kernel.const_get("Stealth::Services::#{current_service.classify}::Client")
54
+ rescue NameError
55
+ raise(Stealth::Errors::ServiceNotRecognized, "The service '#{current_service}' was not recognized")
56
+ end
57
+ end
58
+
59
+ def reply_handler
60
+ begin
61
+ Kernel.const_get("Stealth::Services::#{current_service.classify}::ReplyHandler")
62
+ rescue NameError
63
+ raise(Stealth::Errors::ServiceNotRecognized, "The service '#{current_service}' was not recognized")
64
+ end
65
+ end
66
+
67
+ def replies_folder
68
+ current_session.flow_string.underscore.pluralize
69
+ end
70
+
71
+ def action_replies
72
+ reply_dir = [*self._replies_path, replies_folder]
73
+ reply_filename = "#{current_session.state_string}.yml"
74
+ reply_file_path = File.join(*reply_dir, reply_filename)
75
+ selected_preprocessor = :none
76
+
77
+ for preprocessor in self.class._preprocessors do
78
+ selected_filepath = File.join(*reply_dir, [reply_filename, preprocessor.to_s].join('.'))
79
+ if File.exists?(selected_filepath)
80
+ reply_file_path = selected_filepath
81
+ selected_preprocessor = preprocessor
82
+ break
83
+ end
84
+ end
85
+
86
+ begin
87
+ file_contents = File.read(reply_file_path)
88
+ rescue Errno::ENOENT
89
+ raise(Stealth::Errors::ReplyNotFound, "Could not find a reply in #{reply_file_path}")
90
+ end
91
+
92
+ return file_contents, selected_preprocessor
93
+ end
94
+
95
+ end # instance methods
96
+
97
+ end
98
+ end
99
+ end
@@ -1,6 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- ruby '2.5.0'
3
+ ruby '2.5.1'
4
4
 
5
5
  gem 'stealth', '~> 0.10.0'
6
6
 
@@ -0,0 +1,2 @@
1
+ web: bundle exec puma -C config/puma.rb
2
+ sidekiq: bundle exec sidekiq -C config/sidekiq.yml -q webhooks -q default -r ./config/boot.rb
@@ -1,4 +1,4 @@
1
- class <%= name.capitalize %>Flow
1
+ class <%= name.capitalize.underscore.classify %>Flow
2
2
 
3
3
  include Stealth::Flow
4
4
 
@@ -0,0 +1,2 @@
1
+ - reply_type: text
2
+ text: Oops. It looks like something went wrong. Let's try that again
@@ -0,0 +1,2 @@
1
+ - reply_type: text
2
+ text: Goodbye World!
@@ -1,2 +1,2 @@
1
1
  - reply_type: text
2
- text: 'Hello World!'
2
+ text: Hello World!
@@ -1 +1,9 @@
1
- # Stealth Bot Framework
1
+ # <a href='https://hellostealth.org'><img src='http://assets.blackops.nyc/stealth/logo.svg' height='120' alt='Stealth Logo' aria-label='hellostealth.org' /></a>
2
+
3
+ To boot this bot locally, we recommend the following:
4
+
5
+ 1. `gem install foreman`
6
+ 2. Start Redis
7
+ 3. `foreman start -f Procfile.dev`
8
+
9
+ Using `foreman` will start the web server and Sidekiq processes together.
@@ -25,7 +25,8 @@ module Stealth
25
25
  # Miscellaneous Files
26
26
  copy_file "config.ru", "#{name}/config.ru"
27
27
  copy_file "Gemfile", "#{name}/Gemfile"
28
- copy_file "readme.md", "#{name}/readme.md"
28
+ copy_file "README.md", "#{name}/README.md"
29
+ copy_file "Procfile.dev", "#{name}/Procfile.dev"
29
30
  end
30
31
 
31
32
  def change_directory_bundle
@@ -1,4 +1,4 @@
1
- class <%= name.camelize.pluralize %>Controller < BotController
1
+ class <%= name.camelize.underscore.pluralize %>Controller < BotController
2
2
 
3
3
  def ask_example
4
4
  send_replies
@@ -1,2 +1,2 @@
1
- module <%= name.camelize %>Helper < BotHelper
1
+ module <%= name.underscore.camelize %>Helper < BotHelper
2
2
  end
@@ -13,19 +13,19 @@ module Stealth
13
13
  end
14
14
 
15
15
  def create_controller
16
- template('controllers/controller.tt', "controllers/#{name.pluralize}_controller.rb")
16
+ template('controllers/controller.tt', "bot/controllers/#{name.pluralize}_controller.rb")
17
17
  end
18
18
 
19
19
  def create_replies
20
20
  # Sample Ask Reply
21
- template('replies/ask_reply.tt', "replies/#{name.pluralize}/ask_example.yml")
21
+ template('replies/ask_reply.tt', "bot/replies/#{name.pluralize}/ask_example.yml")
22
22
  # Sample Say Replies
23
- template('replies/say_yes_reply.tt', "replies/#{name.pluralize}/say_yes_example.yml")
24
- template('replies/say_no_reply.tt', "replies/#{name.pluralize}/say_no_example.yml")
23
+ template('replies/say_yes_reply.tt', "bot/replies/#{name.pluralize}/say_yes_example.yml")
24
+ template('replies/say_no_reply.tt', "bot/replies/#{name.pluralize}/say_no_example.yml")
25
25
  end
26
26
 
27
27
  def create_helper
28
- template('helpers/helper.tt', "helpers/#{name}_helper.rb")
28
+ template('helpers/helper.tt', "bot/helpers/#{name}_helper.rb")
29
29
  end
30
30
 
31
31
  def edit_flow_map
@@ -13,10 +13,28 @@ module Stealth
13
13
  end
14
14
 
15
15
  get '/' do
16
- "Welcome to stealth."
16
+ <<~WELCOME
17
+ <html>
18
+ <head>
19
+ <title>Stealth</title>
20
+ </head>
21
+ <body>
22
+ <center>
23
+ <a href='https://hellostealth.org'>
24
+ <img src='http://assets.blackops.nyc/stealth/logo.svg' height='120' alt='Stealth Logo' aria-label='hellostealth.org' />
25
+ </a>
26
+ </center>
27
+ </body>
28
+ </html>
29
+ WELCOME
17
30
  end
18
31
 
19
32
  get_or_post '/incoming/:service' do
33
+ if Stealth.env == 'development'
34
+ Stealth::Logger.l(topic: "ARGF", message: "RELOADING")
35
+ ActiveSupport::Dependencies.clear
36
+ end
37
+
20
38
  Stealth::Logger.l(topic: "incoming", message: "Received webhook from #{params[:service]}")
21
39
 
22
40
  # JSON params need to be parsed and added to the params
@@ -4,18 +4,21 @@
4
4
  module Stealth
5
5
  class ServiceReply
6
6
 
7
- attr_accessor :recipient_id, :replies
7
+ attr_accessor :recipient_id, :replies, :yaml_reply, :context
8
8
 
9
- def initialize(recipient_id:, yaml_reply:, context:)
9
+ def initialize(recipient_id:, yaml_reply:, context:, preprocessor: :none)
10
10
  @recipient_id = recipient_id
11
-
12
- begin
13
- erb_reply = ERB.new(yaml_reply).result(context)
14
- rescue NameError => e
15
- raise(Stealth::Errors::UndefinedVariable, e.message)
11
+ @yaml_reply = yaml_reply
12
+ @context = context
13
+
14
+ processed_reply = case preprocessor
15
+ when :erb
16
+ preprocess_erb
17
+ when :none
18
+ @yaml_reply
16
19
  end
17
20
 
18
- @replies = load_replies(YAML.load(erb_reply))
21
+ @replies = load_replies(YAML.load(processed_reply))
19
22
  end
20
23
 
21
24
  private
@@ -26,5 +29,13 @@ module Stealth
26
29
  end
27
30
  end
28
31
 
32
+ def preprocess_erb
33
+ begin
34
+ ERB.new(yaml_reply).result(context)
35
+ rescue NameError => e
36
+ raise(Stealth::Errors::UndefinedVariable, e.message)
37
+ end
38
+ end
39
+
29
40
  end
30
41
  end
@@ -3,7 +3,7 @@
3
3
 
4
4
  require File.expand_path(File.join(File.dirname(__FILE__), '..', '/spec_helper'))
5
5
 
6
- describe "Stealth::Controller state transitions" do
6
+ describe "Stealth::Controller" do
7
7
 
8
8
  class MrRobotsController < BotController
9
9
  def my_action
@@ -0,0 +1,153 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '/spec_helper'))
5
+
6
+ describe "Stealth::Controller replies" do
7
+
8
+ Stealth::Controller._replies_path = File.expand_path("../replies", __dir__)
9
+
10
+ let(:facebook_message) { SampleMessage.new(service: 'facebook') }
11
+ let(:controller) { MessagesController.new(service_message: facebook_message.message_with_text) }
12
+
13
+ # Stub out base Facebook integration
14
+ module Stealth
15
+ module Services
16
+ module Facebook
17
+ class ReplyHandler
18
+
19
+ end
20
+
21
+ class Client
22
+
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ class MessagesController < Stealth::Controller
29
+ def say_oi
30
+ @first_name = "Presley"
31
+ send_replies
32
+ end
33
+
34
+ def say_offer
35
+ send_replies
36
+ end
37
+ end
38
+
39
+ describe "class attributes" do
40
+ it "should have altered the _replies_path class attribute" do
41
+ expect(MessagesController._replies_path).to eq(File.expand_path("../replies", __dir__))
42
+ end
43
+
44
+ it "should have altered the _preprocessors class attribute" do
45
+ expect(MessagesController._preprocessors).to eq([:erb])
46
+ end
47
+ end
48
+
49
+ describe "action_replies" do
50
+ it "should select the :erb preprocessor when reply extension is .yml" do
51
+ allow(controller.current_session).to receive(:flow_string).and_return("message")
52
+ allow(controller.current_session).to receive(:state_string).and_return("say_oi")
53
+ file_contents, selected_preprocessor = controller.send(:action_replies)
54
+
55
+ expect(selected_preprocessor).to eq(:erb)
56
+ end
57
+
58
+ it "should select the :none preprocessor when there is no reply extension" do
59
+ allow(controller.current_session).to receive(:flow_string).and_return("message")
60
+ allow(controller.current_session).to receive(:state_string).and_return("say_offer")
61
+ file_contents, selected_preprocessor = controller.send(:action_replies)
62
+
63
+ expect(selected_preprocessor).to eq(:none)
64
+ end
65
+
66
+ it "should read the reply's file contents" do
67
+ allow(controller.current_session).to receive(:flow_string).and_return("message")
68
+ allow(controller.current_session).to receive(:state_string).and_return("say_offer")
69
+ file_contents, selected_preprocessor = controller.send(:action_replies)
70
+
71
+ expect(file_contents).to eq(File.read(File.expand_path("../replies/messages/say_offer.yml", __dir__)))
72
+ end
73
+ end
74
+
75
+ describe "reply with ERB" do
76
+ let(:stubbed_handler) { double("handler") }
77
+ let(:stubbed_client) { double("client") }
78
+
79
+ before(:each) do
80
+ allow(Stealth::Services::Facebook::ReplyHandler).to receive(:new).and_return(stubbed_handler)
81
+ allow(Stealth::Services::Facebook::Client).to receive(:new).and_return(stubbed_client)
82
+ allow(controller.current_session).to receive(:flow_string).and_return("message")
83
+ allow(controller.current_session).to receive(:state_string).and_return("say_oi")
84
+ end
85
+
86
+ it "should translate each reply_type in the reply" do
87
+ allow(stubbed_client).to receive(:transmit).and_return(true)
88
+ allow(controller).to receive(:sleep).and_return(true)
89
+
90
+ expect(stubbed_handler).to receive(:text).exactly(3).times
91
+ expect(stubbed_handler).to receive(:delay).exactly(2).times
92
+ controller.say_oi
93
+ end
94
+
95
+ it "should transmit each reply_type in the reply" do
96
+ allow(stubbed_handler).to receive(:text).exactly(3).times
97
+ allow(stubbed_handler).to receive(:delay).exactly(2).times
98
+ allow(controller).to receive(:sleep).and_return(true)
99
+
100
+ expect(stubbed_client).to receive(:transmit).exactly(5).times
101
+ controller.say_oi
102
+ end
103
+
104
+ it "should sleep on delays" do
105
+ allow(stubbed_handler).to receive(:text).exactly(3).times
106
+ allow(stubbed_handler).to receive(:delay).exactly(2).times
107
+ allow(stubbed_client).to receive(:transmit).exactly(5).times
108
+
109
+ expect(controller).to receive(:sleep).exactly(2).times
110
+ controller.say_oi
111
+ end
112
+ end
113
+
114
+ describe "plain reply" do
115
+ let(:stubbed_handler) { double("handler") }
116
+ let(:stubbed_client) { double("client") }
117
+
118
+ before(:each) do
119
+ allow(Stealth::Services::Facebook::ReplyHandler).to receive(:new).and_return(stubbed_handler)
120
+ allow(Stealth::Services::Facebook::Client).to receive(:new).and_return(stubbed_client)
121
+ allow(controller.current_session).to receive(:flow_string).and_return("message")
122
+ allow(controller.current_session).to receive(:state_string).and_return("say_offer")
123
+ end
124
+
125
+ it "should translate each reply_type in the reply" do
126
+ allow(stubbed_client).to receive(:transmit).and_return(true)
127
+ allow(controller).to receive(:sleep).and_return(true)
128
+
129
+ expect(stubbed_handler).to receive(:text).exactly(2).times
130
+ expect(stubbed_handler).to receive(:delay).exactly(1).times
131
+ controller.say_offer
132
+ end
133
+
134
+ it "should transmit each reply_type in the reply" do
135
+ allow(stubbed_handler).to receive(:text).exactly(2).times
136
+ allow(stubbed_handler).to receive(:delay).exactly(1).times
137
+ allow(controller).to receive(:sleep).and_return(true)
138
+
139
+ expect(stubbed_client).to receive(:transmit).exactly(3).times
140
+ controller.say_offer
141
+ end
142
+
143
+ it "should sleep on delays" do
144
+ allow(stubbed_handler).to receive(:text).exactly(2).times
145
+ allow(stubbed_handler).to receive(:delay).exactly(1).times
146
+ allow(stubbed_client).to receive(:transmit).exactly(3).times
147
+
148
+ expect(controller).to receive(:sleep).exactly(1).times
149
+ controller.say_offer
150
+ end
151
+ end
152
+
153
+ end
@@ -1,10 +1,10 @@
1
1
  - reply_type: text
2
2
  text: "Hi, <%= first_name %>. Welcome to Stealth bot..."
3
- - reply-type: delay
3
+ - reply_type: delay
4
4
  duration: 2
5
5
  - reply_type: text
6
6
  text: "We offer users an awesome Ruby framework for building chat bots."
7
- - reply-type: delay
7
+ - reply_type: delay
8
8
  duration: 2
9
9
  - reply_type: text
10
10
  text: "What do you think of our bot?"
@@ -13,4 +13,3 @@
13
13
  payload: cool
14
14
  - text: "Show me more"
15
15
  payload: more
16
-
@@ -0,0 +1,6 @@
1
+ - reply_type: text
2
+ text: "Hi, Morty. Welcome to Stealth bot..."
3
+ - reply_type: delay
4
+ duration: 2
5
+ - reply_type: text
6
+ text: "We offer users an awesome Ruby framework for building chat bots."
@@ -0,0 +1,15 @@
1
+ - reply_type: text
2
+ text: "Hi, <%= @first_name %>. Welcome to Stealth bot..."
3
+ - reply_type: delay
4
+ duration: 2
5
+ - reply_type: text
6
+ text: "We offer users an awesome Ruby framework for building chat bots."
7
+ - reply_type: delay
8
+ duration: 2
9
+ - reply_type: text
10
+ text: "What do you think of our bot?"
11
+ buttons:
12
+ - text: "Cool"
13
+ payload: cool
14
+ - text: "Show me more"
15
+ payload: more
@@ -5,31 +5,89 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
5
5
 
6
6
  describe "Stealth::ServiceReply" do
7
7
 
8
- describe "nested reply with ERB" do
9
- let(:recipient_id) { "8b3e0a3c-62f1-401e-8b0f-615c9d256b1f" }
10
- let(:yaml_reply) { File.read(File.join(File.dirname(__FILE__), 'replies', 'nested_reply_with_erb.yml')) }
8
+ let(:recipient_id) { "8b3e0a3c-62f1-401e-8b0f-615c9d256b1f" }
9
+ let(:yaml_reply) { File.read(File.join(File.dirname(__FILE__), 'replies', 'hello.yml.erb')) }
11
10
 
11
+ describe "nested reply with ERB" do
12
12
  it "should load all the replies" do
13
13
  first_name = "Presley"
14
14
 
15
15
  service_reply = Stealth::ServiceReply.new(
16
16
  recipient_id: recipient_id,
17
17
  yaml_reply: yaml_reply,
18
- context: binding
18
+ context: binding,
19
+ preprocessor: :erb
19
20
  )
20
21
 
21
22
  expect(service_reply.replies.size).to eq 5
22
23
  end
23
24
 
25
+ it "should load all replies as Stealth::Reply objects" do
26
+ first_name = "Presley"
27
+
28
+ service_reply = Stealth::ServiceReply.new(
29
+ recipient_id: recipient_id,
30
+ yaml_reply: yaml_reply,
31
+ context: binding,
32
+ preprocessor: :erb
33
+ )
34
+
35
+ expect(service_reply.replies).to all(be_an(Stealth::Reply))
36
+ end
37
+
38
+ it "should replace the ERB tag" do
39
+ first_name = "Presley"
40
+
41
+ service_reply = Stealth::ServiceReply.new(
42
+ recipient_id: recipient_id,
43
+ yaml_reply: yaml_reply,
44
+ context: binding,
45
+ preprocessor: :erb
46
+ )
47
+
48
+ phrase_in_reply = service_reply.replies.first['text']
49
+ expect(phrase_in_reply).to eq "Hi, Presley. Welcome to Stealth bot..."
50
+ end
51
+
24
52
  it "should raise Stealth::Errors::UndefinedVariable when local variable is not available" do
25
53
  expect {
26
54
  service_reply = Stealth::ServiceReply.new(
27
55
  recipient_id: recipient_id,
28
56
  yaml_reply: yaml_reply,
29
- context: binding
57
+ context: binding,
58
+ preprocessor: :erb
30
59
  )
31
60
  }.to raise_error(Stealth::Errors::UndefinedVariable)
32
61
  end
33
62
  end
34
63
 
64
+ describe "processing a reply without a preprocessor specified" do
65
+ it "should not replace the ERB tag when no preprocessor is specified" do
66
+ first_name = "Gisele"
67
+
68
+ service_reply = Stealth::ServiceReply.new(
69
+ recipient_id: recipient_id,
70
+ yaml_reply: yaml_reply,
71
+ context: binding
72
+ )
73
+
74
+ phrase_in_reply = service_reply.replies.first['text']
75
+ expect(phrase_in_reply).to eq "Hi, <%= first_name %>. Welcome to Stealth bot..."
76
+ end
77
+
78
+ it "should not replace the ERB tag when :none is specified as the preprocessor" do
79
+ first_name = "Gisele"
80
+
81
+ service_reply = Stealth::ServiceReply.new(
82
+ recipient_id: recipient_id,
83
+ yaml_reply: yaml_reply,
84
+ context: binding,
85
+ preprocessor: :none
86
+ )
87
+
88
+ phrase_in_reply = service_reply.replies.first['text']
89
+ expect(phrase_in_reply).to eq "Hi, <%= first_name %>. Welcome to Stealth bot..."
90
+ end
91
+ end
92
+
35
93
  end
data/stealth.gemspec CHANGED
@@ -6,18 +6,18 @@ Gem::Specification.new do |s|
6
6
  s.name = 'stealth'
7
7
  s.summary = 'Ruby framework for conversational bots'
8
8
  s.description = 'Ruby framework for building conversational bots.'
9
- s.homepage = 'https://github.com/whoisblackops/stealth'
9
+ s.homepage = 'https://github.com/hellostealth/stealth'
10
10
  s.licenses = ['MIT']
11
11
  s.version = version
12
12
  s.author = 'Mauricio Gomes'
13
13
  s.email = 'mauricio@edge14.com'
14
14
 
15
- s.add_dependency 'sinatra', '~> 2.0'
15
+ s.add_dependency 'sinatra', '~> 2.0.1'
16
16
  s.add_dependency 'puma', '~> 3.10'
17
17
  s.add_dependency 'thor', '~> 0.20'
18
18
  s.add_dependency 'multi_json', '~> 1.12'
19
19
  s.add_dependency 'sidekiq', '~> 5.0'
20
- s.add_dependency 'activesupport', '~> 5.2.0.rc1'
20
+ s.add_dependency 'activesupport', '~> 5.2.0'
21
21
 
22
22
  s.add_development_dependency 'rspec', '~> 3.6'
23
23
  s.add_development_dependency 'rspec_junit_formatter', '~> 0.3'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stealth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.6
4
+ version: 1.0.0.pre1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mauricio Gomes
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-04-04 00:00:00.000000000 Z
11
+ date: 2018-04-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sinatra
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '2.0'
19
+ version: 2.0.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '2.0'
26
+ version: 2.0.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: puma
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -86,14 +86,14 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 5.2.0.rc1
89
+ version: 5.2.0
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 5.2.0.rc1
96
+ version: 5.2.0
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: rspec
99
99
  requirement: !ruby/object:Gem::Requirement
@@ -177,6 +177,7 @@ files:
177
177
  - lib/stealth/controller/catch_all.rb
178
178
  - lib/stealth/controller/controller.rb
179
179
  - lib/stealth/controller/helpers.rb
180
+ - lib/stealth/controller/replies.rb
180
181
  - lib/stealth/dispatcher.rb
181
182
  - lib/stealth/errors.rb
182
183
  - lib/stealth/flow/base.rb
@@ -185,13 +186,14 @@ files:
185
186
  - lib/stealth/flow/state.rb
186
187
  - lib/stealth/generators/builder.rb
187
188
  - lib/stealth/generators/builder/Gemfile
189
+ - lib/stealth/generators/builder/Procfile.dev
188
190
  - lib/stealth/generators/builder/bot/controllers/bot_controller.rb
189
191
  - lib/stealth/generators/builder/bot/controllers/catch_alls_controller.rb
190
192
  - lib/stealth/generators/builder/bot/controllers/goodbyes_controller.rb
191
193
  - lib/stealth/generators/builder/bot/controllers/hellos_controller.rb
192
194
  - lib/stealth/generators/builder/bot/flow_map.rb.tt
193
195
  - lib/stealth/generators/builder/bot/helpers/bot_helper.rb
194
- - lib/stealth/generators/builder/bot/replies/catch_alls/catch_all.yml
196
+ - lib/stealth/generators/builder/bot/replies/catch_alls/level1.yml
195
197
  - lib/stealth/generators/builder/bot/replies/goodbyes/say_goodbye.yml
196
198
  - lib/stealth/generators/builder/bot/replies/hellos/say_hello.yml
197
199
  - lib/stealth/generators/builder/config.ru
@@ -224,11 +226,14 @@ files:
224
226
  - logo.svg
225
227
  - spec/configuration_spec.rb
226
228
  - spec/controller/callbacks_spec.rb
229
+ - spec/controller/controller_spec.rb
227
230
  - spec/controller/helpers_spec.rb
228
- - spec/controller/state_transitions_spec.rb
231
+ - spec/controller/replies_spec.rb
229
232
  - spec/flow/flow_spec.rb
230
233
  - spec/flow/state_spec.rb
231
- - spec/replies/nested_reply_with_erb.yml
234
+ - spec/replies/hello.yml.erb
235
+ - spec/replies/messages/say_offer.yml
236
+ - spec/replies/messages/say_oi.yml.erb
232
237
  - spec/service_reply_spec.rb
233
238
  - spec/session_spec.rb
234
239
  - spec/spec_helper.rb
@@ -242,7 +247,7 @@ files:
242
247
  - spec/support/services_with_erb.yml
243
248
  - spec/version_spec.rb
244
249
  - stealth.gemspec
245
- homepage: https://github.com/whoisblackops/stealth
250
+ homepage: https://github.com/hellostealth/stealth
246
251
  licenses:
247
252
  - MIT
248
253
  metadata: {}
@@ -257,9 +262,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
257
262
  version: '0'
258
263
  required_rubygems_version: !ruby/object:Gem::Requirement
259
264
  requirements:
260
- - - ">="
265
+ - - ">"
261
266
  - !ruby/object:Gem::Version
262
- version: '0'
267
+ version: 1.3.1
263
268
  requirements: []
264
269
  rubyforge_project:
265
270
  rubygems_version: 2.7.6
@@ -269,11 +274,14 @@ summary: Ruby framework for conversational bots
269
274
  test_files:
270
275
  - spec/configuration_spec.rb
271
276
  - spec/controller/callbacks_spec.rb
277
+ - spec/controller/controller_spec.rb
272
278
  - spec/controller/helpers_spec.rb
273
- - spec/controller/state_transitions_spec.rb
279
+ - spec/controller/replies_spec.rb
274
280
  - spec/flow/flow_spec.rb
275
281
  - spec/flow/state_spec.rb
276
- - spec/replies/nested_reply_with_erb.yml
282
+ - spec/replies/hello.yml.erb
283
+ - spec/replies/messages/say_offer.yml
284
+ - spec/replies/messages/say_oi.yml.erb
277
285
  - spec/service_reply_spec.rb
278
286
  - spec/session_spec.rb
279
287
  - spec/spec_helper.rb