stealth 0.10.6 → 1.0.0.pre1

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
  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