stealth 2.0.0.beta2 → 2.0.0.beta5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +112 -2
- data/CHANGELOG.md +7 -2
- data/Gemfile.lock +19 -19
- data/LICENSE +1 -1
- data/README.md +4 -2
- data/VERSION +1 -1
- data/lib/stealth/base.rb +1 -1
- data/lib/stealth/controller/controller.rb +9 -0
- data/lib/stealth/controller/replies.rb +34 -25
- data/lib/stealth/errors.rb +11 -0
- data/lib/stealth/generators/builder/bot/controllers/bot_controller.rb +14 -2
- data/lib/stealth/helpers/redis.rb +3 -3
- data/lib/stealth/logger.rb +1 -1
- data/lib/stealth/server.rb +6 -1
- data/lib/stealth/service_message.rb +1 -1
- data/logo.svg +28 -1
- data/spec/configuration_spec.rb +1 -1
- data/spec/controller/catch_all_spec.rb +7 -1
- data/spec/controller/controller_spec.rb +34 -0
- data/spec/controller/replies_spec.rb +56 -2
- data/stealth.gemspec +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 24ba7ba55d264e21395db7b9166ecf96064c2c0d8c8bc51d09dd274ecaf5fd34
|
4
|
+
data.tar.gz: c42254f819550577f249fddb13266cfa6bc8e2f27760994e3efdf7798447ecdd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3f1af77ae1b811b4f53f85df2365fae399efe6caf9db77a801898250dfcdb732e366b06efa1641b255eda53e2a22fff38ca7a8ebd6577d6dcf860509d002c261
|
7
|
+
data.tar.gz: 7146a30ad60302ccc322e6691c6676b6b281dee1625bc1acf674e133b87d371b5cc470c5d3d4eb6d50526d33992a81f424364fbf4b10e23d93910aed815b5864
|
data/.circleci/config.yml
CHANGED
@@ -3,7 +3,7 @@ version: 2
|
|
3
3
|
jobs:
|
4
4
|
ruby_2_6:
|
5
5
|
docker:
|
6
|
-
- image:
|
6
|
+
- image: cimg/ruby:2.6
|
7
7
|
environment:
|
8
8
|
STEALTH_ENV: test
|
9
9
|
|
@@ -56,7 +56,115 @@ jobs:
|
|
56
56
|
destination: test-results
|
57
57
|
ruby_2_7:
|
58
58
|
docker:
|
59
|
-
- image:
|
59
|
+
- image: cimg/ruby:2.7
|
60
|
+
environment:
|
61
|
+
STEALTH_ENV: test
|
62
|
+
|
63
|
+
working_directory: ~/repo
|
64
|
+
|
65
|
+
steps:
|
66
|
+
- checkout
|
67
|
+
|
68
|
+
# Download and cache dependencies
|
69
|
+
- restore_cache:
|
70
|
+
keys:
|
71
|
+
- v1-dependencies-{{ checksum "Gemfile.lock" }}
|
72
|
+
# fallback to using the latest cache if no exact match is found
|
73
|
+
- v1-dependencies-
|
74
|
+
- run:
|
75
|
+
name: Configure Bundler
|
76
|
+
command: |
|
77
|
+
echo 'export BUNDLER_VERSION=$(cat Gemfile.lock | tail -1 | tr -d " ")' >> $BASH_ENV
|
78
|
+
source $BASH_ENV
|
79
|
+
gem install bundler
|
80
|
+
- run:
|
81
|
+
name: install dependencies
|
82
|
+
command: |
|
83
|
+
bundle install --jobs=4 --retry=3 --path vendor/bundle
|
84
|
+
|
85
|
+
- save_cache:
|
86
|
+
paths:
|
87
|
+
- ./vendor/bundle
|
88
|
+
key: v1-dependencies-{{ checksum "Gemfile.lock" }}
|
89
|
+
|
90
|
+
# run tests!
|
91
|
+
- run:
|
92
|
+
name: run tests
|
93
|
+
command: |
|
94
|
+
mkdir /tmp/test-results
|
95
|
+
TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)"
|
96
|
+
|
97
|
+
bundle exec rspec --format progress \
|
98
|
+
--format RspecJunitFormatter \
|
99
|
+
--out /tmp/test-results/rspec.xml \
|
100
|
+
--format progress \
|
101
|
+
-- \
|
102
|
+
$TEST_FILES
|
103
|
+
|
104
|
+
# collect reports
|
105
|
+
- store_test_results:
|
106
|
+
path: /tmp/test-results
|
107
|
+
- store_artifacts:
|
108
|
+
path: /tmp/test-results
|
109
|
+
destination: test-results
|
110
|
+
|
111
|
+
ruby_3_0:
|
112
|
+
docker:
|
113
|
+
- image: cimg/ruby:3.0
|
114
|
+
environment:
|
115
|
+
STEALTH_ENV: test
|
116
|
+
|
117
|
+
working_directory: ~/repo
|
118
|
+
|
119
|
+
steps:
|
120
|
+
- checkout
|
121
|
+
|
122
|
+
# Download and cache dependencies
|
123
|
+
- restore_cache:
|
124
|
+
keys:
|
125
|
+
- v1-dependencies-{{ checksum "Gemfile.lock" }}
|
126
|
+
# fallback to using the latest cache if no exact match is found
|
127
|
+
- v1-dependencies-
|
128
|
+
- run:
|
129
|
+
name: Configure Bundler
|
130
|
+
command: |
|
131
|
+
echo 'export BUNDLER_VERSION=$(cat Gemfile.lock | tail -1 | tr -d " ")' >> $BASH_ENV
|
132
|
+
source $BASH_ENV
|
133
|
+
gem install bundler
|
134
|
+
- run:
|
135
|
+
name: install dependencies
|
136
|
+
command: |
|
137
|
+
bundle install --jobs=4 --retry=3 --path vendor/bundle
|
138
|
+
|
139
|
+
- save_cache:
|
140
|
+
paths:
|
141
|
+
- ./vendor/bundle
|
142
|
+
key: v1-dependencies-{{ checksum "Gemfile.lock" }}
|
143
|
+
|
144
|
+
# run tests!
|
145
|
+
- run:
|
146
|
+
name: run tests
|
147
|
+
command: |
|
148
|
+
mkdir /tmp/test-results
|
149
|
+
TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)"
|
150
|
+
|
151
|
+
bundle exec rspec --format progress \
|
152
|
+
--format RspecJunitFormatter \
|
153
|
+
--out /tmp/test-results/rspec.xml \
|
154
|
+
--format progress \
|
155
|
+
-- \
|
156
|
+
$TEST_FILES
|
157
|
+
|
158
|
+
# collect reports
|
159
|
+
- store_test_results:
|
160
|
+
path: /tmp/test-results
|
161
|
+
- store_artifacts:
|
162
|
+
path: /tmp/test-results
|
163
|
+
destination: test-results
|
164
|
+
|
165
|
+
ruby_3_1:
|
166
|
+
docker:
|
167
|
+
- image: cimg/ruby:3.1
|
60
168
|
environment:
|
61
169
|
STEALTH_ENV: test
|
62
170
|
|
@@ -114,3 +222,5 @@ workflows:
|
|
114
222
|
jobs:
|
115
223
|
- ruby_2_6
|
116
224
|
- ruby_2_7
|
225
|
+
- ruby_3_0
|
226
|
+
- ruby_3_1
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
## Enhancements
|
4
4
|
|
5
|
+
* Added support for Ruby 3.0
|
5
6
|
* [Controllers] Added support for Dev Jumping. This feature allows developers to jump around flows and states for bot's in development.
|
6
7
|
* [NLP] Added base classes for `Stealth::Nlp::Result` and `Stealth::Nlp::Client` to be used by NLP drivers.
|
7
8
|
* [Controllers] Scheduled replies no longer call `controller.route` when they run. Instead we `step_to` to the flow and state directly. This ensures the `route` is reserved for incoming messages.
|
@@ -13,7 +14,8 @@
|
|
13
14
|
* [Logging] `primary_session`, `previous_session`, and `back_to_session` now explicitly logged
|
14
15
|
* [Sessions] The session is no longer set on update or stepping witht destination flow and state match the existing session.
|
15
16
|
* [Scheduled Replies] The `service_message.target_id` is now set for scheduled replies. NOTE: scheduled replies that are already enqueued will NOT have this set.
|
16
|
-
* [Server] Updated to Puma
|
17
|
+
* [Server] Updated to Puma 5.5
|
18
|
+
* [Server] Updated to Sidekiq 6.3
|
17
19
|
* [Server] Updated to Sinatra 2.1
|
18
20
|
* [Sessions] Added `to_s` for sessions to pretty print the slug. Useful when debugging.
|
19
21
|
* `send_reples` now supports two additional options for replies:
|
@@ -40,7 +42,7 @@
|
|
40
42
|
* [Controllers] `normalized_msg` and `homophone_translated_msg` are now memoized for performance.
|
41
43
|
* [Errors] `Stealth::Errors::MessageNotRecognized` has been renamed to `Stealth::Errors::UnrecognizedMessage`
|
42
44
|
* [Controllers] When `handle_message` or `get_match` raise a `Stealth::Errors::UnrecognizedMessage`, the user is first routed to a new `UnrecognizedMessagesController` to perform NLP. If that controller fails to match, the `catch_all` is run as normal.
|
43
|
-
* [Errors] Client errors now call respective BotController actions: `handle_opt_out`
|
45
|
+
* [Errors] Client errors now call respective BotController actions: `handle_opt_out`, `handle_invalid_session_id`, `handle_message_filtered`, `handle_unknown_error`. Each client is responsible for raising `Stealth::Errors::UserOptOut`, `Stealth::Errors::InvalidSessionId`, `Stealth::Errors::MessageFiltered`, `Stealth::Errors::UnknownError` errors, respectively.
|
44
46
|
* [Controllers] `handle_message` and `get_match` now detect homophones for alpha ordinals (A-Z)
|
45
47
|
* [Controllers] `handle_message` and `get_match` now ignore single and double quotes for alpha-ordinals
|
46
48
|
* [CoreExt] Strings now have a `normalize` method for removing padding and quotes
|
@@ -51,6 +53,9 @@
|
|
51
53
|
* [Controllers] `handle_message` now supports `Regexp` keys.
|
52
54
|
* [Configuration] `database.yml` is now parsed with ERB in order to support environment variables. Thanks @malkovro.
|
53
55
|
* [Replies] Speech and SSML replies now use `speech` and `ssml` as keys, respectively, instead of `text`
|
56
|
+
* [Replies] Voice services (determined by having "voice" in the name) now automatically skip auto-delays.
|
57
|
+
* [Controllers] `current_message` now has a `confidence` attribute containing a float with the confidence value of the transcription (from 0 to 1).
|
58
|
+
* [Controllers] Added a `halt!` method that can be used with the controller error handlers to stop code execution.
|
54
59
|
|
55
60
|
## Bug Fixes
|
56
61
|
|
data/Gemfile.lock
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
stealth (2.0.0.
|
4
|
+
stealth (2.0.0.beta5)
|
5
5
|
activesupport (~> 6.0)
|
6
6
|
multi_json (~> 1.12)
|
7
|
-
puma (>= 4.2, <
|
7
|
+
puma (>= 4.2, < 6.0)
|
8
8
|
sidekiq (~> 6.0)
|
9
9
|
sinatra (~> 2.0)
|
10
10
|
thor (~> 1.0)
|
@@ -12,32 +12,32 @@ PATH
|
|
12
12
|
GEM
|
13
13
|
remote: https://rubygems.org/
|
14
14
|
specs:
|
15
|
-
activesupport (6.1.
|
15
|
+
activesupport (6.1.5)
|
16
16
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
17
17
|
i18n (>= 1.6, < 2)
|
18
18
|
minitest (>= 5.1)
|
19
19
|
tzinfo (~> 2.0)
|
20
20
|
zeitwerk (~> 2.3)
|
21
|
-
concurrent-ruby (1.1.
|
22
|
-
connection_pool (2.2.
|
21
|
+
concurrent-ruby (1.1.10)
|
22
|
+
connection_pool (2.2.5)
|
23
23
|
diff-lcs (1.3)
|
24
|
-
i18n (1.
|
24
|
+
i18n (1.10.0)
|
25
25
|
concurrent-ruby (~> 1.0)
|
26
|
-
minitest (5.
|
26
|
+
minitest (5.15.0)
|
27
27
|
mock_redis (0.23.0)
|
28
28
|
multi_json (1.15.0)
|
29
29
|
mustermann (1.1.1)
|
30
30
|
ruby2_keywords (~> 0.0.1)
|
31
|
-
nio4r (2.5.
|
31
|
+
nio4r (2.5.8)
|
32
32
|
oj (3.10.6)
|
33
|
-
puma (
|
33
|
+
puma (5.6.2)
|
34
34
|
nio4r (~> 2.0)
|
35
35
|
rack (2.2.3)
|
36
|
-
rack-protection (2.
|
36
|
+
rack-protection (2.2.0)
|
37
37
|
rack
|
38
38
|
rack-test (1.1.0)
|
39
39
|
rack (>= 1.0, < 3)
|
40
|
-
redis (4.
|
40
|
+
redis (4.6.0)
|
41
41
|
rspec (3.9.0)
|
42
42
|
rspec-core (~> 3.9.0)
|
43
43
|
rspec-expectations (~> 3.9.0)
|
@@ -53,21 +53,21 @@ GEM
|
|
53
53
|
rspec-support (3.9.2)
|
54
54
|
rspec_junit_formatter (0.4.1)
|
55
55
|
rspec-core (>= 2, < 4, != 2.12.0)
|
56
|
-
ruby2_keywords (0.0.
|
57
|
-
sidekiq (6.1
|
56
|
+
ruby2_keywords (0.0.5)
|
57
|
+
sidekiq (6.4.1)
|
58
58
|
connection_pool (>= 2.2.2)
|
59
59
|
rack (~> 2.0)
|
60
60
|
redis (>= 4.2.0)
|
61
|
-
sinatra (2.
|
61
|
+
sinatra (2.2.0)
|
62
62
|
mustermann (~> 1.0)
|
63
63
|
rack (~> 2.2)
|
64
|
-
rack-protection (= 2.
|
64
|
+
rack-protection (= 2.2.0)
|
65
65
|
tilt (~> 2.0)
|
66
|
-
thor (1.
|
66
|
+
thor (1.2.1)
|
67
67
|
tilt (2.0.10)
|
68
|
-
tzinfo (2.0.
|
68
|
+
tzinfo (2.0.4)
|
69
69
|
concurrent-ruby (~> 1.0)
|
70
|
-
zeitwerk (2.4
|
70
|
+
zeitwerk (2.5.4)
|
71
71
|
|
72
72
|
PLATFORMS
|
73
73
|
ruby
|
@@ -81,4 +81,4 @@ DEPENDENCIES
|
|
81
81
|
stealth!
|
82
82
|
|
83
83
|
BUNDLED WITH
|
84
|
-
2.
|
84
|
+
2.2.32
|
data/LICENSE
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c) 2017-2020 Mauricio Gomes
|
1
|
+
Copyright (c) 2017-2020 Mauricio Gomes
|
2
2
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
4
|
|
data/README.md
CHANGED
@@ -2,7 +2,8 @@
|
|
2
2
|
|
3
3
|
Stealth is a Ruby framework for creating text and voice chatbots. 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
4
|
|
5
|
-
|
5
|
+
![CircleCI](https://img.shields.io/circleci/build/github/hellostealth/stealth?style=for-the-badge)
|
6
|
+
![Gem (including prereleases)](https://img.shields.io/gem/v/stealth?include_prereleases&style=for-the-badge)
|
6
7
|
|
7
8
|
## Features
|
8
9
|
|
@@ -37,6 +38,7 @@ Currently, there are gems for:
|
|
37
38
|
* [Alexa Skill](https://github.com/hellostealth/stealth-alexa) (Early alpha)
|
38
39
|
|
39
40
|
### Natural Language Processing
|
41
|
+
* [Microsoft LUIS](https://github.com/hellostealth/stealth-luis)
|
40
42
|
* [AWS Comprehend](https://github.com/hellostealth/stealth-aws-comprehend)
|
41
43
|
|
42
44
|
### Analytics
|
@@ -52,4 +54,4 @@ Stealth is versioned using [Semantic Versioning](https://semver.org), but it's m
|
|
52
54
|
|
53
55
|
## License
|
54
56
|
|
55
|
-
"Stealth" and the Stealth logo are Copyright (c)
|
57
|
+
"Stealth" and the Stealth logo are Copyright (c) 2017-2022 MAV Automated Ventures Inc.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.0.0.
|
1
|
+
2.0.0.beta5
|
data/lib/stealth/base.rb
CHANGED
@@ -88,7 +88,7 @@ module Stealth
|
|
88
88
|
|
89
89
|
Thread.current[:configuration] ||= begin
|
90
90
|
@semaphore.synchronize do
|
91
|
-
services_config = YAML.
|
91
|
+
services_config = YAML.safe_load(ERB.new(services_yaml).result, aliases: true)
|
92
92
|
|
93
93
|
unless services_config.has_key?(env)
|
94
94
|
raise Stealth::Errors::ConfigurationError, "Could not find services.yml configuration for #{env} environment"
|
@@ -91,6 +91,11 @@ module Stealth
|
|
91
91
|
unless flow_controller.progressed?
|
92
92
|
run_catch_all(reason: 'Did not send replies, update session, or step')
|
93
93
|
end
|
94
|
+
rescue Stealth::Errors::Halted
|
95
|
+
Stealth::Logger.l(
|
96
|
+
topic: "session",
|
97
|
+
message: "User #{current_session_id}: session halted."
|
98
|
+
)
|
94
99
|
rescue StandardError => e
|
95
100
|
if e.class == Stealth::Errors::UnrecognizedMessage
|
96
101
|
run_unrecognized_message(err: e)
|
@@ -215,6 +220,10 @@ module Stealth
|
|
215
220
|
@progressed = :do_nothing
|
216
221
|
end
|
217
222
|
|
223
|
+
def halt!
|
224
|
+
raise Stealth::Errors::Halted
|
225
|
+
end
|
226
|
+
|
218
227
|
private
|
219
228
|
|
220
229
|
def update_session(flow:, state:)
|
@@ -33,10 +33,32 @@ module Stealth
|
|
33
33
|
begin
|
34
34
|
send_reply(reply: reply)
|
35
35
|
rescue Stealth::Errors::UserOptOut => e
|
36
|
-
|
36
|
+
msg = "User #{current_session_id} opted out. [#{e.message}]"
|
37
|
+
service_error_dispatcher(
|
38
|
+
handler_method: :handle_opt_out,
|
39
|
+
error_msg: msg
|
40
|
+
)
|
37
41
|
return
|
38
42
|
rescue Stealth::Errors::InvalidSessionID => e
|
39
|
-
|
43
|
+
msg = "User #{current_session_id} has an invalid session_id. [#{e.message}]"
|
44
|
+
service_error_dispatcher(
|
45
|
+
handler_method: :handle_invalid_session_id,
|
46
|
+
error_msg: msg
|
47
|
+
)
|
48
|
+
return
|
49
|
+
rescue Stealth::Errors::MessageFiltered => e
|
50
|
+
msg = "Message to user #{current_session_id} was filtered. [#{e.message}]"
|
51
|
+
service_error_dispatcher(
|
52
|
+
handler_method: :handle_message_filtered,
|
53
|
+
error_msg: msg
|
54
|
+
)
|
55
|
+
return
|
56
|
+
rescue Stealth::Errors::UnknownServiceError => e
|
57
|
+
msg = "User #{current_session_id} had an unknown error. [#{e.message}]"
|
58
|
+
service_error_dispatcher(
|
59
|
+
handler_method: :handle_unknown_error,
|
60
|
+
error_msg: msg
|
61
|
+
)
|
40
62
|
return
|
41
63
|
end
|
42
64
|
|
@@ -50,8 +72,12 @@ module Stealth
|
|
50
72
|
|
51
73
|
private
|
52
74
|
|
75
|
+
def voice_service?
|
76
|
+
current_service.match?(/voice/)
|
77
|
+
end
|
78
|
+
|
53
79
|
def send_reply(reply:)
|
54
|
-
if !reply.delay? && Stealth.config.auto_insert_delays
|
80
|
+
if !reply.delay? && Stealth.config.auto_insert_delays && !voice_service?
|
55
81
|
# if it's the first reply in the service_reply or the previous reply
|
56
82
|
# wasn't a custom delay, then insert a delay
|
57
83
|
if @previous_reply.blank? || !@previous_reply.delay?
|
@@ -211,34 +237,17 @@ module Stealth
|
|
211
237
|
return file_contents, selected_preprocessor
|
212
238
|
end
|
213
239
|
|
214
|
-
def
|
215
|
-
if self.respond_to?(
|
216
|
-
self.send(:handle_opt_out)
|
217
|
-
Stealth::Logger.l(
|
218
|
-
topic: current_service,
|
219
|
-
message: "User #{current_session_id} opted out. [#{msg}]"
|
220
|
-
)
|
221
|
-
else
|
222
|
-
Stealth::Logger.l(
|
223
|
-
topic: :err,
|
224
|
-
message: "User #{current_session_id} unhandled exception due to opt-out."
|
225
|
-
)
|
226
|
-
end
|
227
|
-
|
228
|
-
do_nothing
|
229
|
-
end
|
230
|
-
|
231
|
-
def invalid_session_id_handler(msg:)
|
232
|
-
if self.respond_to?(:handle_invalid_session_id, true)
|
233
|
-
self.send(:handle_invalid_session_id)
|
240
|
+
def service_error_dispatcher(handler_method:, error_msg:)
|
241
|
+
if self.respond_to?(handler_method, true)
|
234
242
|
Stealth::Logger.l(
|
235
243
|
topic: current_service,
|
236
|
-
message:
|
244
|
+
message: error_msg
|
237
245
|
)
|
246
|
+
self.send(handler_method)
|
238
247
|
else
|
239
248
|
Stealth::Logger.l(
|
240
249
|
topic: :err,
|
241
|
-
message: "
|
250
|
+
message: "Unhandled service exception for user #{current_session_id}. No error handler for `#{handler_method}` found."
|
242
251
|
)
|
243
252
|
end
|
244
253
|
|
data/lib/stealth/errors.rb
CHANGED
@@ -43,14 +43,25 @@ module Stealth
|
|
43
43
|
class FlowDefinitionError < Errors
|
44
44
|
end
|
45
45
|
|
46
|
+
# Service errors
|
46
47
|
class InvalidSessionID < Errors
|
47
48
|
end
|
48
49
|
|
49
50
|
class UserOptOut < Errors
|
50
51
|
end
|
51
52
|
|
53
|
+
class MessageFiltered < Errors
|
54
|
+
end
|
55
|
+
|
56
|
+
class UnknownServiceError < Errors
|
57
|
+
end
|
58
|
+
|
59
|
+
# Homophone errors
|
52
60
|
class ReservedHomophoneUsed < Errors
|
53
61
|
end
|
54
62
|
|
63
|
+
class Halted < Errors
|
64
|
+
end
|
65
|
+
|
55
66
|
end
|
56
67
|
end
|
@@ -40,16 +40,28 @@ private
|
|
40
40
|
end
|
41
41
|
|
42
42
|
# Automatically called when clients receive an opt-out error from
|
43
|
-
# the platform. You
|
43
|
+
# the platform. You may write your own steps for handling.
|
44
44
|
def handle_opt_out
|
45
45
|
do_nothing
|
46
46
|
end
|
47
47
|
|
48
48
|
# Automatically called when clients receive an invalid session_id error from
|
49
49
|
# the platform. For example, attempting to text a landline.
|
50
|
-
# You
|
50
|
+
# You may write your own steps for handling.
|
51
51
|
def handle_invalid_session_id
|
52
52
|
do_nothing
|
53
53
|
end
|
54
54
|
|
55
|
+
# Automatically called when a transmitted message is filtered/marked as spam.
|
56
|
+
# You may write your own steps for handling.
|
57
|
+
def handle_message_filtered
|
58
|
+
do_nothing
|
59
|
+
end
|
60
|
+
|
61
|
+
# Automatically called when an unknown error is returned by the platform.
|
62
|
+
# You may write your own steps for handling.
|
63
|
+
def handle_unknown_error
|
64
|
+
do_nothing
|
65
|
+
end
|
66
|
+
|
55
67
|
end
|
@@ -21,9 +21,9 @@ module Stealth
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def getex(key, expiration=Stealth.config.session_ttl)
|
24
|
-
$redis.multi do
|
25
|
-
|
26
|
-
|
24
|
+
$redis.multi do |pipeline|
|
25
|
+
pipeline.expire(key, expiration)
|
26
|
+
pipeline.get(key)
|
27
27
|
end.last
|
28
28
|
end
|
29
29
|
|
data/lib/stealth/logger.rb
CHANGED
data/lib/stealth/server.rb
CHANGED
@@ -21,7 +21,7 @@ module Stealth
|
|
21
21
|
<body>
|
22
22
|
<center>
|
23
23
|
<a href='https://hellostealth.org'>
|
24
|
-
<img src='
|
24
|
+
<img src='https://raw.githubusercontent.com/hellostealth/stealth/master/logo.svg' height='120' alt='Stealth Logo' aria-label='hellostealth.org' />
|
25
25
|
</a>
|
26
26
|
</center>
|
27
27
|
</body>
|
@@ -44,6 +44,11 @@ module Stealth
|
|
44
44
|
headers: get_helpers_from_request(request)
|
45
45
|
)
|
46
46
|
|
47
|
+
headers 'Access-Control-Allow-Origin' => '*',
|
48
|
+
'Access-Control-Allow-Methods' => ['OPTIONS', 'GET', 'POST']
|
49
|
+
# content_type 'audio/mp3'
|
50
|
+
content_type 'application/octet-stream'
|
51
|
+
|
47
52
|
dispatcher.coordinate
|
48
53
|
end
|
49
54
|
|
data/logo.svg
CHANGED
@@ -1 +1,28 @@
|
|
1
|
-
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<svg width="180px" height="180px" viewBox="0 0 180 180" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
3
|
+
<title>stealth_app_logo</title>
|
4
|
+
<defs>
|
5
|
+
<rect id="path-1" x="0" y="0" width="180" height="180"></rect>
|
6
|
+
<path d="M51.3778558,-2.07572687e-15 L128.622144,2.07572687e-15 C146.487344,-1.20605709e-15 152.965697,1.86013822 159.496947,5.35308895 C166.028197,8.84603967 171.15396,13.9718029 174.646911,20.5030529 C178.139862,27.0343029 180,33.512656 180,51.3778558 L180,128.622144 C180,146.487344 178.139862,152.965697 174.646911,159.496947 C171.15396,166.028197 166.028197,171.15396 159.496947,174.646911 C152.965697,178.139862 146.487344,180 128.622144,180 L51.3778558,180 C33.512656,180 27.0343029,178.139862 20.5030529,174.646911 C13.9718029,171.15396 8.84603967,166.028197 5.35308895,159.496947 C1.86013822,152.965697 8.04038057e-16,146.487344 -1.38381791e-15,128.622144 L1.38381791e-15,51.3778558 C-8.04038057e-16,33.512656 1.86013822,27.0343029 5.35308895,20.5030529 C8.84603967,13.9718029 13.9718029,8.84603967 20.5030529,5.35308895 C27.0343029,1.86013822 33.512656,1.20605709e-15 51.3778558,-2.07572687e-15 Z" id="path-3"></path>
|
7
|
+
</defs>
|
8
|
+
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
9
|
+
<g id="icon@180">
|
10
|
+
<g id="icon">
|
11
|
+
<mask id="mask-2" fill="white">
|
12
|
+
<use xlink:href="#path-1"></use>
|
13
|
+
</mask>
|
14
|
+
<g id="Mask"></g>
|
15
|
+
<g id="Rectangle" mask="url(#mask-2)">
|
16
|
+
<mask id="mask-4" fill="white">
|
17
|
+
<use xlink:href="#path-3"></use>
|
18
|
+
</mask>
|
19
|
+
<use id="Mask" fill="#FFFFFF" xlink:href="#path-3"></use>
|
20
|
+
</g>
|
21
|
+
</g>
|
22
|
+
<g id="logo" transform="translate(24.960938, 18.984375)" fill="#000A38">
|
23
|
+
<path d="M65.0390625,0 L44.0583093,20.9046079 L41.9615065,22.9953504 L23.077556,41.8106246 L20.9807532,43.9013672 L0,64.8073839 L23.077556,87.8027344 L44.0583093,66.8967176 L65.0390625,87.8027344 L86.0198157,66.8967176 L106.999155,87.8027344 L130.078125,64.8073839 L109.098786,43.9013672 L106.999155,41.8106246 L88.1180324,22.9953504 L86.0198157,20.9046079 L65.0390625,0 Z M55.5970873,32.403692 L46.156526,22.9953504 L65.0390625,4.18007626 L83.921599,22.9953504 L74.4810377,32.403692 L65.0390625,41.8106246 L55.5970873,32.403692 Z M67.1372792,43.9013672 L76.5778405,34.4944345 L86.0198157,25.086093 L104.902352,43.9013672 L95.460377,53.3097087 L86.0198157,62.7166414 L67.1372792,43.9013672 Z M34.617748,53.3097087 L25.1771867,43.9013672 L44.0583093,25.086093 L53.5002845,34.4944345 L62.9408458,43.9013672 L44.0583093,62.7166414 L34.617748,53.3097087 Z M88.1180324,64.8073839 L97.5585938,55.3990424 L106.999155,45.9921097 L125.883105,64.8073839 L106.999155,83.6226581 L88.1180324,64.8073839 Z M46.156526,64.8073839 L65.0390625,45.9921097 L83.921599,64.8073839 L65.0390625,83.6226581 L46.156526,64.8073839 Z M4.19501953,64.8073839 L23.077556,45.9921097 L32.5195313,55.3990424 L41.9615065,64.8073839 L23.077556,83.6226581 L4.19501953,64.8073839 Z" id="glyph"></path>
|
24
|
+
<path d="M7.93868906,127.899703 C11.7243984,127.899703 14.7190641,125.865591 14.7190641,122.305894 C14.7190641,119.621995 13.0804734,118.011656 10.1140594,117.305367 L6.72387188,116.514323 C5.81982188,116.316562 5.28304219,115.892789 5.28304219,115.129997 C5.28304219,114.169444 6.15884063,113.604412 7.4584125,113.604412 C8.95574531,113.604412 9.74678906,114.338953 10.1140594,115.299506 L14.5495547,115.299506 C14.0692781,112.304841 11.6678953,109.988213 7.43016094,109.988213 C3.58794844,109.988213 0.762792188,112.220086 0.762792188,115.610273 C0.762792188,118.548436 2.68389844,119.876259 5.22653906,120.441291 L8.56022344,121.14758 C9.66203438,121.401844 10.1988141,121.769114 10.1988141,122.588409 C10.1988141,123.661969 9.23826094,124.255252 7.85393438,124.255252 C6.44135625,124.255252 5.4243,123.661969 5.05702969,122.475403 L0.33901875,122.475403 C0.960553125,125.893842 3.78570938,127.899703 7.93868906,127.899703 Z M28.539987,127.617187 L28.539987,114.169444 L33.9077839,114.169444 L33.9077839,110.270728 L18.6519401,110.270728 L18.6519401,114.169444 L23.9914855,114.169444 L23.9914855,127.617187 L28.539987,127.617187 Z M51.1753975,127.617187 L51.1753975,123.69022 L43.2367084,123.69022 L43.2367084,120.695555 L50.49736,120.695555 L50.49736,116.881594 L43.2367084,116.881594 L43.2367084,114.169444 L51.1753975,114.169444 L51.1753975,110.270728 L38.7729615,110.270728 L38.7729615,127.617187 L51.1753975,127.617187 Z M59.8262845,127.617187 L61.0411017,124.255252 L67.3412001,124.255252 L68.5842689,127.617187 L73.1892736,127.617187 L66.5784079,110.270728 L61.9451517,110.270728 L55.3342861,127.617187 L59.8262845,127.617187 Z M65.9851251,120.526045 L62.4254283,120.526045 L64.2052767,115.638525 L65.9851251,120.526045 Z M90.0048622,127.617187 L90.0048622,123.69022 L82.2356825,123.69022 L82.2356825,110.270728 L77.6871809,110.270728 L77.6871809,127.617187 L90.0048622,127.617187 Z M101.650415,127.617187 L101.650415,114.169444 L107.018212,114.169444 L107.018212,110.270728 L91.7623679,110.270728 L91.7623679,114.169444 L97.1019132,114.169444 L97.1019132,127.617187 L101.650415,127.617187 Z M116.431891,127.617187 L116.431891,120.752058 L123.127511,120.752058 L123.127511,127.617187 L127.676013,127.617187 L127.676013,110.270728 L123.127511,110.270728 L123.127511,116.853342 L116.431891,116.853342 L116.431891,110.270728 L111.883389,110.270728 L111.883389,127.617187 L116.431891,127.617187 Z" id="STEALTH" fill-rule="nonzero"></path>
|
25
|
+
</g>
|
26
|
+
</g>
|
27
|
+
</g>
|
28
|
+
</svg>
|
data/spec/configuration_spec.rb
CHANGED
@@ -6,7 +6,7 @@ describe "Stealth::Configuration" do
|
|
6
6
|
|
7
7
|
describe "accessing via method calling" do
|
8
8
|
let(:services_yml) { File.read(File.join(File.dirname(__FILE__), 'support', 'services.yml')) }
|
9
|
-
let(:parsed_config) { YAML.
|
9
|
+
let(:parsed_config) { YAML.safe_load(ERB.new(services_yml).result, aliases: true)[Stealth.env] }
|
10
10
|
let(:config) { Stealth.load_services_config!(services_yml) }
|
11
11
|
|
12
12
|
it "should return the root node" do
|
@@ -115,7 +115,13 @@ describe "Stealth::Controller::CatchAll" do
|
|
115
115
|
end
|
116
116
|
|
117
117
|
it "should log the error message" do
|
118
|
-
|
118
|
+
err_klass = if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.0.0')
|
119
|
+
"RuntimeError"
|
120
|
+
else
|
121
|
+
"OpenStruct"
|
122
|
+
end
|
123
|
+
|
124
|
+
expect(Stealth::Logger).to receive(:l).with(topic: 'catch_all', message: "[Level 1] for user #{facebook_message.sender_id} #{err_klass}\noops\n/stealth/lib/stealth/controller/controller.rb\n/stealth/lib/stealth/controller/catch_all.rb")
|
119
125
|
expect(Stealth::Logger).to receive(:l).with(topic: 'catch_all', message: "CatchAll triggered for user #{facebook_message.sender_id} from within CatchAll; ignoring.")
|
120
126
|
controller.run_catch_all(err: e)
|
121
127
|
end
|
@@ -42,6 +42,11 @@ describe "Stealth::Controller" do
|
|
42
42
|
def parts_unknown
|
43
43
|
step_to flow: :parts, state: :unknown
|
44
44
|
end
|
45
|
+
|
46
|
+
def halted
|
47
|
+
halt!
|
48
|
+
step_to state: :other_action2
|
49
|
+
end
|
45
50
|
end
|
46
51
|
|
47
52
|
class FlowMap
|
@@ -60,6 +65,7 @@ describe "Stealth::Controller" do
|
|
60
65
|
state :other_action4
|
61
66
|
state :broken_action
|
62
67
|
state :part_unknown
|
68
|
+
state :halted
|
63
69
|
state :deprecated_action, redirects_to: :other_action
|
64
70
|
state :deprecated_action2, redirects_to: 'mr_robot->my_action'
|
65
71
|
end
|
@@ -884,6 +890,34 @@ describe "Stealth::Controller" do
|
|
884
890
|
controller.action(action: :parts_unknown)
|
885
891
|
end
|
886
892
|
end
|
893
|
+
|
894
|
+
describe "halt!" do
|
895
|
+
it "should catch the error and log the sessio halt" do
|
896
|
+
### It's lame we have to include these two
|
897
|
+
expect(Stealth::Logger).to receive(:l).with(
|
898
|
+
topic: "primary_session",
|
899
|
+
message: "User #{facebook_message.sender_id}: setting session to mr_tron->halted"
|
900
|
+
)
|
901
|
+
expect(Stealth::Logger).to receive(:l).with(
|
902
|
+
topic: "previous_session",
|
903
|
+
message: "User #{facebook_message.sender_id}: setting to parts->unknown"
|
904
|
+
)
|
905
|
+
###
|
906
|
+
|
907
|
+
expect(Stealth::Logger).to receive(:l).with(
|
908
|
+
topic: "session",
|
909
|
+
message: "User #{facebook_message.sender_id}: session halted."
|
910
|
+
)
|
911
|
+
|
912
|
+
controller.step_to(flow: :mr_tron, state: :halted)
|
913
|
+
end
|
914
|
+
|
915
|
+
it "should NOT continue with the rest of the controller code" do
|
916
|
+
expect_any_instance_of(MrTronsController).to_not receive(:other_action2)
|
917
|
+
expect_any_instance_of(MrTronsController).to_not receive(:step_to).with(state: :other_action2)
|
918
|
+
controller.step_to(flow: :mr_tron, state: :halted)
|
919
|
+
end
|
920
|
+
end
|
887
921
|
end
|
888
922
|
|
889
923
|
end
|
@@ -632,7 +632,7 @@ describe "Stealth::Controller replies" do
|
|
632
632
|
it "should log the unhandled exception if the controller does not have a handle_opt_out method" do
|
633
633
|
expect(Stealth::Logger).to receive(:l).with(
|
634
634
|
topic: :err,
|
635
|
-
message: "
|
635
|
+
message: "Unhandled service exception for user #{facebook_message.sender_id}. No error handler for `handle_opt_out` found."
|
636
636
|
)
|
637
637
|
expect(controller).to receive(:do_nothing)
|
638
638
|
controller.say_offer
|
@@ -659,7 +659,7 @@ describe "Stealth::Controller replies" do
|
|
659
659
|
it "should log the unhandled exception if the controller does not have a handle_invalid_session_id method" do
|
660
660
|
expect(Stealth::Logger).to receive(:l).with(
|
661
661
|
topic: :err,
|
662
|
-
message: "
|
662
|
+
message: "Unhandled service exception for user #{facebook_message.sender_id}. No error handler for `handle_invalid_session_id` found."
|
663
663
|
)
|
664
664
|
expect(controller).to receive(:do_nothing)
|
665
665
|
controller.say_offer
|
@@ -676,6 +676,60 @@ describe "Stealth::Controller replies" do
|
|
676
676
|
end
|
677
677
|
end
|
678
678
|
|
679
|
+
describe "Stealth::Errors::MessageFiltered" do
|
680
|
+
before(:each) do
|
681
|
+
expect(stubbed_client).to receive(:transmit).and_raise(
|
682
|
+
Stealth::Errors::MessageFiltered.new('boom')
|
683
|
+
).once # Retuns early; doesn't send the remaining replies in the file
|
684
|
+
end
|
685
|
+
|
686
|
+
it "should log the unhandled exception if the controller does not have a handle_message_filtered method" do
|
687
|
+
expect(Stealth::Logger).to receive(:l).with(
|
688
|
+
topic: :err,
|
689
|
+
message: "Unhandled service exception for user #{facebook_message.sender_id}. No error handler for `handle_message_filtered` found."
|
690
|
+
)
|
691
|
+
expect(controller).to receive(:do_nothing)
|
692
|
+
controller.say_offer
|
693
|
+
end
|
694
|
+
|
695
|
+
it "should call handle_message_filtered method" do
|
696
|
+
expect(controller).to receive(:handle_message_filtered)
|
697
|
+
expect(Stealth::Logger).to receive(:l).with(
|
698
|
+
topic: 'facebook',
|
699
|
+
message: "Message to user #{facebook_message.sender_id} was filtered. [boom]"
|
700
|
+
)
|
701
|
+
expect(controller).to receive(:do_nothing)
|
702
|
+
controller.say_offer
|
703
|
+
end
|
704
|
+
end
|
705
|
+
|
706
|
+
describe "Stealth::Errors::UnknownServiceError" do
|
707
|
+
before(:each) do
|
708
|
+
expect(stubbed_client).to receive(:transmit).and_raise(
|
709
|
+
Stealth::Errors::UnknownServiceError.new('boom')
|
710
|
+
).once # Retuns early; doesn't send the remaining replies in the file
|
711
|
+
end
|
712
|
+
|
713
|
+
it "should log the unhandled exception if the controller does not have a handle_unknown_error method" do
|
714
|
+
expect(Stealth::Logger).to receive(:l).with(
|
715
|
+
topic: :err,
|
716
|
+
message: "Unhandled service exception for user #{facebook_message.sender_id}. No error handler for `handle_unknown_error` found."
|
717
|
+
)
|
718
|
+
expect(controller).to receive(:do_nothing)
|
719
|
+
controller.say_offer
|
720
|
+
end
|
721
|
+
|
722
|
+
it "should call handle_unknown_error method" do
|
723
|
+
expect(controller).to receive(:handle_unknown_error)
|
724
|
+
expect(Stealth::Logger).to receive(:l).with(
|
725
|
+
topic: 'facebook',
|
726
|
+
message: "User #{facebook_message.sender_id} had an unknown error. [boom]"
|
727
|
+
)
|
728
|
+
expect(controller).to receive(:do_nothing)
|
729
|
+
controller.say_offer
|
730
|
+
end
|
731
|
+
end
|
732
|
+
|
679
733
|
describe 'an unknown client error' do
|
680
734
|
before(:each) do
|
681
735
|
allow(stubbed_client).to receive(:transmit).and_raise(
|
data/stealth.gemspec
CHANGED
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.email = 'mauricio@edge14.com'
|
14
14
|
|
15
15
|
s.add_dependency 'sinatra', '~> 2.0'
|
16
|
-
s.add_dependency 'puma', '>= 4.2', '<
|
16
|
+
s.add_dependency 'puma', '>= 4.2', '< 6.0'
|
17
17
|
s.add_dependency 'thor', '~> 1.0'
|
18
18
|
s.add_dependency 'multi_json', '~> 1.12'
|
19
19
|
s.add_dependency 'sidekiq', '~> 6.0'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stealth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.0.
|
4
|
+
version: 2.0.0.beta5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mauricio Gomes
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2022-03-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sinatra
|
@@ -34,7 +34,7 @@ dependencies:
|
|
34
34
|
version: '4.2'
|
35
35
|
- - "<"
|
36
36
|
- !ruby/object:Gem::Version
|
37
|
-
version: '
|
37
|
+
version: '6.0'
|
38
38
|
type: :runtime
|
39
39
|
prerelease: false
|
40
40
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -44,7 +44,7 @@ dependencies:
|
|
44
44
|
version: '4.2'
|
45
45
|
- - "<"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '6.0'
|
48
48
|
- !ruby/object:Gem::Dependency
|
49
49
|
name: thor
|
50
50
|
requirement: !ruby/object:Gem::Requirement
|
@@ -327,7 +327,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
327
327
|
- !ruby/object:Gem::Version
|
328
328
|
version: 1.3.1
|
329
329
|
requirements: []
|
330
|
-
rubygems_version: 3.
|
330
|
+
rubygems_version: 3.3.7
|
331
331
|
signing_key:
|
332
332
|
specification_version: 4
|
333
333
|
summary: Ruby framework for conversational bots
|