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 +4 -4
- data/Gemfile.lock +22 -22
- data/README.md +46 -1
- data/VERSION +1 -1
- data/lib/stealth/base.rb +36 -16
- data/lib/stealth/controller/controller.rb +1 -61
- data/lib/stealth/controller/replies.rb +99 -0
- data/lib/stealth/generators/builder/Gemfile +1 -1
- data/lib/stealth/generators/builder/Procfile.dev +2 -0
- data/lib/stealth/generators/builder/bot/flow_map.rb.tt +1 -1
- data/lib/stealth/generators/builder/bot/replies/catch_alls/level1.yml +2 -0
- data/lib/stealth/generators/builder/bot/replies/goodbyes/say_goodbye.yml +2 -0
- data/lib/stealth/generators/builder/bot/replies/hellos/say_hello.yml +1 -1
- data/lib/stealth/generators/builder/readme.md +9 -1
- data/lib/stealth/generators/builder.rb +2 -1
- data/lib/stealth/generators/generate/flow/controllers/controller.tt +1 -1
- data/lib/stealth/generators/generate/flow/helpers/helper.tt +1 -1
- data/lib/stealth/generators/generate.rb +5 -5
- data/lib/stealth/server.rb +19 -1
- data/lib/stealth/service_reply.rb +19 -8
- data/spec/controller/{state_transitions_spec.rb → controller_spec.rb} +1 -1
- data/spec/controller/replies_spec.rb +153 -0
- data/spec/replies/{nested_reply_with_erb.yml → hello.yml.erb} +2 -3
- data/spec/replies/messages/say_offer.yml +6 -0
- data/spec/replies/messages/say_oi.yml.erb +15 -0
- data/spec/service_reply_spec.rb +63 -5
- data/stealth.gemspec +3 -3
- metadata +22 -14
- data/lib/stealth/generators/builder/bot/replies/catch_alls/catch_all.yml +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a447e930b543fe9125bb09baaaf12f160f5fa715e346fd6b304285f5a31f56f2
|
4
|
+
data.tar.gz: f5ff084dcb475b87ff972458751b16cc05779bed6eebf67f3feb15ba97909d68
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
5
|
-
activesupport (~> 5.2.0
|
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
|
15
|
+
activesupport (5.2.0)
|
16
16
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
17
|
-
i18n (
|
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.
|
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.
|
30
|
-
puma (3.11.
|
31
|
-
rack (2.0.
|
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.
|
34
|
+
rack-test (0.8.3)
|
35
35
|
rack (>= 1.0, < 3)
|
36
36
|
redis (4.0.1)
|
37
|
-
rspec (3.
|
38
|
-
rspec-core (~> 3.
|
39
|
-
rspec-expectations (~> 3.
|
40
|
-
rspec-mocks (~> 3.
|
41
|
-
rspec-core (3.
|
42
|
-
rspec-support (~> 3.
|
43
|
-
rspec-expectations (3.
|
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.
|
46
|
-
rspec-mocks (3.
|
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.
|
49
|
-
rspec-support (3.
|
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.
|
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.
|
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
|
-
|
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|
|
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,2 +1,2 @@
|
|
1
1
|
- reply_type: text
|
2
|
-
text:
|
2
|
+
text: Hello World!
|
@@ -1 +1,9 @@
|
|
1
|
-
# Stealth
|
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 "
|
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,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
|
data/lib/stealth/server.rb
CHANGED
@@ -13,10 +13,28 @@ module Stealth
|
|
13
13
|
end
|
14
14
|
|
15
15
|
get '/' do
|
16
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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(
|
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
|
@@ -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
|
-
-
|
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
|
-
-
|
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,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
|
data/spec/service_reply_spec.rb
CHANGED
@@ -5,31 +5,89 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
|
5
5
|
|
6
6
|
describe "Stealth::ServiceReply" do
|
7
7
|
|
8
|
-
|
9
|
-
|
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/
|
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
|
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.
|
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-
|
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:
|
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:
|
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
|
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
|
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/
|
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/
|
231
|
+
- spec/controller/replies_spec.rb
|
229
232
|
- spec/flow/flow_spec.rb
|
230
233
|
- spec/flow/state_spec.rb
|
231
|
-
- spec/replies/
|
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/
|
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:
|
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/
|
279
|
+
- spec/controller/replies_spec.rb
|
274
280
|
- spec/flow/flow_spec.rb
|
275
281
|
- spec/flow/state_spec.rb
|
276
|
-
- spec/replies/
|
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
|
File without changes
|