stealth-alexa 1.0.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +60 -0
- data/.gitignore +12 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +77 -0
- data/LICENSE +20 -0
- data/README.md +122 -0
- data/VERSION +1 -0
- data/lib/stealth-alexa.rb +1 -0
- data/lib/stealth/alexa.rb +2 -0
- data/lib/stealth/services/alexa/client.rb +29 -0
- data/lib/stealth/services/alexa/message_handler.rb +101 -0
- data/lib/stealth/services/alexa/reply_handler.rb +161 -0
- data/lib/stealth/services/alexa/setup.rb +20 -0
- data/lib/stealth/services/alexa/version.rb +16 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/version_spec.rb +16 -0
- data/stealth-alexa.gemspec +25 -0
- metadata +118 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 00d82cbb16942a0d0d6a8eed93574f89fe8668a6e8415571616e8fdc27947bbb
|
4
|
+
data.tar.gz: 7e7ff898f706709fdeaa6f4a4a2a6dafcf9923aa5118e30eddf4c665b614b263
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 77abcf122132eb2f3baaefcd155dfeb8148f54e4fc0392a770bceacf7389a63536e62134d5c45cb989f9fdd63bc4fc8623f26fadb5af5939c0c2d936c2037913
|
7
|
+
data.tar.gz: d16333d0b8d328f69a75b2df79bd75b7887053e5f47b092b700ae289135bf5e9d2522a21980d0d532a9284de505953e74ccdd0829751fe3f216ea5fdf87be9b9
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# Ruby CircleCI 2.0 configuration file
|
2
|
+
#
|
3
|
+
# Check https://circleci.com/docs/2.0/language-ruby/ for more details
|
4
|
+
#
|
5
|
+
version: 2
|
6
|
+
jobs:
|
7
|
+
build:
|
8
|
+
docker:
|
9
|
+
# specify the version you desire here
|
10
|
+
- image: circleci/ruby:2.5.1-node-browsers
|
11
|
+
environment:
|
12
|
+
STEALTH_ENV: test
|
13
|
+
|
14
|
+
# Specify service dependencies here if necessary
|
15
|
+
# CircleCI maintains a library of pre-built images
|
16
|
+
# documented at https://circleci.com/docs/2.0/circleci-images/
|
17
|
+
# - image: circleci/postgres:9.4
|
18
|
+
|
19
|
+
working_directory: ~/repo
|
20
|
+
|
21
|
+
steps:
|
22
|
+
- checkout
|
23
|
+
|
24
|
+
# Download and cache dependencies
|
25
|
+
- restore_cache:
|
26
|
+
keys:
|
27
|
+
- v1-dependencies-{{ checksum "Gemfile.lock" }}
|
28
|
+
# fallback to using the latest cache if no exact match is found
|
29
|
+
- v1-dependencies-
|
30
|
+
|
31
|
+
- run:
|
32
|
+
name: install dependencies
|
33
|
+
command: |
|
34
|
+
bundle install --jobs=4 --retry=3 --path vendor/bundle
|
35
|
+
|
36
|
+
- save_cache:
|
37
|
+
paths:
|
38
|
+
- ./vendor/bundle
|
39
|
+
key: v1-dependencies-{{ checksum "Gemfile.lock" }}
|
40
|
+
|
41
|
+
# run tests!
|
42
|
+
- run:
|
43
|
+
name: run tests
|
44
|
+
command: |
|
45
|
+
mkdir /tmp/test-results
|
46
|
+
TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)"
|
47
|
+
|
48
|
+
bundle exec rspec --format progress \
|
49
|
+
--format RspecJunitFormatter \
|
50
|
+
--out /tmp/test-results/rspec.xml \
|
51
|
+
--format progress \
|
52
|
+
-- \
|
53
|
+
$TEST_FILES
|
54
|
+
|
55
|
+
# collect reports
|
56
|
+
- store_test_results:
|
57
|
+
path: /tmp/test-results
|
58
|
+
- store_artifacts:
|
59
|
+
path: /tmp/test-results
|
60
|
+
destination: test-results
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
stealth-alexa (1.0.0)
|
5
|
+
stealth (< 2.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activesupport (5.2.2)
|
11
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
12
|
+
i18n (>= 0.7, < 2)
|
13
|
+
minitest (~> 5.1)
|
14
|
+
tzinfo (~> 1.1)
|
15
|
+
concurrent-ruby (1.1.3)
|
16
|
+
connection_pool (2.2.2)
|
17
|
+
diff-lcs (1.3)
|
18
|
+
i18n (1.1.1)
|
19
|
+
concurrent-ruby (~> 1.0)
|
20
|
+
minitest (5.11.3)
|
21
|
+
multi_json (1.13.1)
|
22
|
+
mustermann (1.0.3)
|
23
|
+
puma (3.12.0)
|
24
|
+
rack (2.0.6)
|
25
|
+
rack-protection (2.0.4)
|
26
|
+
rack
|
27
|
+
rack-test (0.8.3)
|
28
|
+
rack (>= 1.0, < 3)
|
29
|
+
redis (4.0.3)
|
30
|
+
rspec (3.8.0)
|
31
|
+
rspec-core (~> 3.8.0)
|
32
|
+
rspec-expectations (~> 3.8.0)
|
33
|
+
rspec-mocks (~> 3.8.0)
|
34
|
+
rspec-core (3.8.0)
|
35
|
+
rspec-support (~> 3.8.0)
|
36
|
+
rspec-expectations (3.8.2)
|
37
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
38
|
+
rspec-support (~> 3.8.0)
|
39
|
+
rspec-mocks (3.8.0)
|
40
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
41
|
+
rspec-support (~> 3.8.0)
|
42
|
+
rspec-support (3.8.0)
|
43
|
+
rspec_junit_formatter (0.4.1)
|
44
|
+
rspec-core (>= 2, < 4, != 2.12.0)
|
45
|
+
sidekiq (5.2.3)
|
46
|
+
connection_pool (~> 2.2, >= 2.2.2)
|
47
|
+
rack-protection (>= 1.5.0)
|
48
|
+
redis (>= 3.3.5, < 5)
|
49
|
+
sinatra (2.0.4)
|
50
|
+
mustermann (~> 1.0)
|
51
|
+
rack (~> 2.0)
|
52
|
+
rack-protection (= 2.0.4)
|
53
|
+
tilt (~> 2.0)
|
54
|
+
stealth (1.1.1)
|
55
|
+
activesupport (~> 5.2)
|
56
|
+
multi_json (~> 1.12)
|
57
|
+
puma (~> 3.10)
|
58
|
+
sidekiq (~> 5.0)
|
59
|
+
sinatra (~> 2.0)
|
60
|
+
thor (~> 0.20)
|
61
|
+
thor (0.20.3)
|
62
|
+
thread_safe (0.3.6)
|
63
|
+
tilt (2.0.9)
|
64
|
+
tzinfo (1.2.5)
|
65
|
+
thread_safe (~> 0.1)
|
66
|
+
|
67
|
+
PLATFORMS
|
68
|
+
ruby
|
69
|
+
|
70
|
+
DEPENDENCIES
|
71
|
+
rack-test (~> 0.7)
|
72
|
+
rspec (~> 3.6)
|
73
|
+
rspec_junit_formatter (~> 0.3)
|
74
|
+
stealth-alexa!
|
75
|
+
|
76
|
+
BUNDLED WITH
|
77
|
+
1.16.6
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2018 Mauricio Gomes, Black Ops Bureau Inc.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
# Stealth Alexa
|
2
|
+
|
3
|
+
The Stealth Alexa driver adds the ability to build Alexa voice bots via [Stealth](https://github.com/hellostealth/stealth). Alexa bots can be accessed via a wide array of Alexa-enabled products, including Amazon's own Echo devices (including those with displays).
|
4
|
+
|
5
|
+
This Stealth integration provides a way of building Alexa Skills using Stealth. Though the patterns for voice bots are the same as text-based bots, the reply types supported are unique.
|
6
|
+
|
7
|
+
## Configure the Integration
|
8
|
+
|
9
|
+
## Replies
|
10
|
+
|
11
|
+
Alexa replies are unique in that each request is processed synchronously (unlike most other Stealth integrations). Therefore, each reply should only contain a single reply.
|
12
|
+
|
13
|
+
Here are the supported replies for the Alexa integration:
|
14
|
+
|
15
|
+
### Speech
|
16
|
+
|
17
|
+
These are standard replies for Alexa Skills. They are similar to `text` replies in other integrations. They can be sent like:
|
18
|
+
|
19
|
+
```yaml
|
20
|
+
- reply_type: speech
|
21
|
+
text: Hello World!
|
22
|
+
```
|
23
|
+
|
24
|
+
If your speech contains difficult to pronouce words or you wish to add effects to the speech, you can send along [SSML](https://developer.amazon.com/docs/custom-skills/speech-synthesis-markup-language-ssml-reference.html) to help Alexa pronouce the words.
|
25
|
+
|
26
|
+
```yaml
|
27
|
+
- reply_type: speech
|
28
|
+
ssml: "I want to tell you a secret. <amazon:effect name="whispered">I see dead people.</amazon:effect>"
|
29
|
+
```
|
30
|
+
|
31
|
+
Optionally, you can specify a `playBehavior` for how Alexa treats your reply. More info [here](https://developer.amazon.com/docs/custom-skills/request-and-response-json-reference.html#outputspeech-object).
|
32
|
+
|
33
|
+
```yaml
|
34
|
+
- reply_type: speech
|
35
|
+
text: Hello World!
|
36
|
+
play_behavior: 'ENQUEUE'
|
37
|
+
```
|
38
|
+
|
39
|
+
### Reprompts
|
40
|
+
|
41
|
+
Reprompts are sent to the user when either they fail to respond or their response did not match one of your mapped intents. Reprompts are optional and if one is not set, nothing is sent to the user.
|
42
|
+
|
43
|
+
If you set `reprompt: true`, Stealth will re-send your speech.
|
44
|
+
|
45
|
+
```yaml
|
46
|
+
- reply_type: speech
|
47
|
+
text: Hello World!
|
48
|
+
reprompt: true
|
49
|
+
```
|
50
|
+
|
51
|
+
You may also customize your reprompt by specifying a complete `speech` object:
|
52
|
+
|
53
|
+
```yaml
|
54
|
+
- reply_type: speech
|
55
|
+
text: "Today will provide you a new learning opportunity. Stick with it and the possibilities will be endless. Can I help you with anything else?"
|
56
|
+
reprompt:
|
57
|
+
text: "Can I help you with anything else?"
|
58
|
+
```
|
59
|
+
|
60
|
+
You can also specify an SSML reprompt.
|
61
|
+
|
62
|
+
### Cards
|
63
|
+
|
64
|
+
Cards are rich media that can be displayed via the Alexa mobile app or on devices that may feature a screen. Stealth supports all four card types:
|
65
|
+
|
66
|
+
* Simple - a card that contain a title and plain text content
|
67
|
+
* Standard - a card that contain a title, plain text content, and an image
|
68
|
+
* LinkAccount - a card that displays a link to an authorization URI that the user can use to link their Alexa account with a user in another system. See [Account Linking for Custom Skills](https://developer.amazon.com/docs/account-linking/account-linking-for-custom-skills.html) for details.
|
69
|
+
* AskForPermissionsConsent - A card that asks the customer for consent to obtain specific customer information, such as Alexa lists or address information. See [Alexa Shopping and To-Do Lists](https://developer.amazon.com/docs/custom-skills/access-the-alexa-shopping-and-to-do-lists.html#missing_permissions) and [Enhance Your Skill with Address Information](https://developer.amazon.com/docs/custom-skills/device-address-api.html).
|
70
|
+
|
71
|
+
|
72
|
+
Simple card:
|
73
|
+
|
74
|
+
```yaml
|
75
|
+
- reply_type: card
|
76
|
+
type: Simple
|
77
|
+
title: Hello World
|
78
|
+
content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas pretium, sem sed placerat elementum, purus quam iaculis dolor, a euismod urna magna et sem.
|
79
|
+
```
|
80
|
+
|
81
|
+
For standard cards, you can specify a large and small image. For more info on the dimensions and file format, please check out [Alexa's documentation](https://developer.amazon.com/docs/custom-skills/include-a-card-in-your-skills-response.html#image_size).
|
82
|
+
|
83
|
+
```yaml
|
84
|
+
- reply_type: card
|
85
|
+
type: Standard
|
86
|
+
title: Hello World
|
87
|
+
content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas pretium, sem sed placerat elementum, purus quam iaculis dolor, a euismod urna magna et sem.
|
88
|
+
small_image_url: https://loremflickr.com/720/480
|
89
|
+
large_image_url: https://loremflickr.com/1200/800
|
90
|
+
```
|
91
|
+
|
92
|
+
### Sessions
|
93
|
+
|
94
|
+
By default, Stealth will keep a session open between replies. If you would like to end a session, you can set the `end_session` key to any reply:
|
95
|
+
|
96
|
+
```yaml
|
97
|
+
- reply_type: speech
|
98
|
+
text: Hello World!
|
99
|
+
end_session: true
|
100
|
+
```
|
101
|
+
|
102
|
+
### Session Attributes
|
103
|
+
|
104
|
+
While your user's session is active, you can pass along key-value pairs in your responses. These will be spent back by Alexa in their requests. This is useful for multi-step scripts, like for example ordering a pizza.
|
105
|
+
|
106
|
+
To set these session values, you can include the `session_values` key in any your responses (except `AudioPlayer` and `PlaybackController` replies).
|
107
|
+
|
108
|
+
```yaml
|
109
|
+
- reply_type: speech
|
110
|
+
text: Hello World!
|
111
|
+
session_values:
|
112
|
+
x: 1
|
113
|
+
y: blue
|
114
|
+
```
|
115
|
+
|
116
|
+
### Directives
|
117
|
+
|
118
|
+
Coming soon.
|
119
|
+
|
120
|
+
### Delays
|
121
|
+
|
122
|
+
Delays are not supported for Alexa bots.
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0.pre1
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'stealth/alexa'
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'stealth/services/alexa/message_handler'
|
5
|
+
require 'stealth/services/alexa/reply_handler'
|
6
|
+
require 'stealth/services/alexa/setup'
|
7
|
+
|
8
|
+
module Stealth
|
9
|
+
module Services
|
10
|
+
module Alexa
|
11
|
+
|
12
|
+
class Client < Stealth::Services::BaseClient
|
13
|
+
|
14
|
+
attr_reader :reply
|
15
|
+
|
16
|
+
def initialize(reply:)
|
17
|
+
@reply = reply
|
18
|
+
Thread.current[:alexa_reply] = reply
|
19
|
+
end
|
20
|
+
|
21
|
+
def transmit
|
22
|
+
Stealth::Logger.l(topic: "alexa", message: "Response sent.")
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'digest'
|
5
|
+
|
6
|
+
module Stealth
|
7
|
+
module Services
|
8
|
+
module Alexa
|
9
|
+
class MessageHandler < Stealth::Services::BaseMessageHandler
|
10
|
+
|
11
|
+
attr_reader :service_message, :params, :headers
|
12
|
+
|
13
|
+
def initialize(params:, headers:)
|
14
|
+
@params = params
|
15
|
+
@headers = headers
|
16
|
+
end
|
17
|
+
|
18
|
+
def coordinate
|
19
|
+
# Alexa requires an inline response
|
20
|
+
process
|
21
|
+
end
|
22
|
+
|
23
|
+
def process
|
24
|
+
@service_message = ServiceMessage.new(service: 'alexa')
|
25
|
+
|
26
|
+
# Load the request attributes
|
27
|
+
service_message.sender_id = get_sender_id
|
28
|
+
service_message.session_values = get_session_values
|
29
|
+
get_request_details
|
30
|
+
|
31
|
+
# Craft the reply
|
32
|
+
bot_controller = BotController.new(service_message: service_message)
|
33
|
+
bot_controller.route
|
34
|
+
|
35
|
+
send_alexa_reply
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def send_alexa_reply
|
41
|
+
puts MultiJson.dump(Thread.current[:alexa_reply]).inspect
|
42
|
+
MultiJson.dump(Thread.current[:alexa_reply])
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_sender_id
|
46
|
+
Digest::MD5.hexdigest(params.dig('session', 'user', 'userId'))
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_session_values
|
50
|
+
params.dig('session', 'attributes')
|
51
|
+
end
|
52
|
+
|
53
|
+
def get_request_details
|
54
|
+
case params.dig('request', 'type')
|
55
|
+
when 'CanFulfillIntentRequest'
|
56
|
+
Stealth::Logger.l(topic: "alexa", message: "CanFulfillIntentRequest received.")
|
57
|
+
# Not yet implemented
|
58
|
+
when 'LaunchRequest'
|
59
|
+
service_message.timestamp = DateTime.parse(params.dig('request', 'timestamp'))
|
60
|
+
service_message.locale = params.dig('request', 'locale')
|
61
|
+
service_message.payload = 'LaunchRequest'
|
62
|
+
Stealth::Logger.l(topic: "alexa", message: "LaunchRequest received.")
|
63
|
+
when 'IntentRequest'
|
64
|
+
service_message.timestamp = DateTime.parse(params.dig('request', 'timestamp'))
|
65
|
+
service_message.locale = params.dig('request', 'locale')
|
66
|
+
service_message.payload = params.dig('request', 'intent', 'name')
|
67
|
+
Stealth::Logger.l(topic: "alexa", message: "IntentRequest [#{service_message.payload}] received.")
|
68
|
+
|
69
|
+
# Load the slots (if any)
|
70
|
+
# Alexa uses the SlotName as the key here 😡
|
71
|
+
params.dig('request', 'intent', 'slots')&.each do |slot_name, slot|
|
72
|
+
service_message.slots[slot_name] = slot.dig('value')
|
73
|
+
end
|
74
|
+
when 'SessionEndedRequest'
|
75
|
+
service_message.timestamp = DateTime.parse(params.dig('request', 'timestamp'))
|
76
|
+
service_message.locale = params.dig('request', 'locale')
|
77
|
+
service_message.payload = 'SessionEndedRequest'
|
78
|
+
Stealth::Logger.l(topic: "alexa", message: "SessionEndedRequest received.")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
module Extensions
|
85
|
+
attr_accessor :slots, :session_values, :locale
|
86
|
+
|
87
|
+
def initialize(service:)
|
88
|
+
@slots = @session_values = {}
|
89
|
+
@locale = nil
|
90
|
+
|
91
|
+
super
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class ServiceMessage
|
98
|
+
prepend Services::Alexa::Extensions
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Stealth
|
5
|
+
module Services
|
6
|
+
module Alexa
|
7
|
+
|
8
|
+
class ReplyHandler < Stealth::Services::BaseReplyHandler
|
9
|
+
|
10
|
+
attr_reader :recipient_id, :reply
|
11
|
+
|
12
|
+
def initialize(recipient_id: nil, reply: nil)
|
13
|
+
@recipient_id = recipient_id
|
14
|
+
@reply = reply
|
15
|
+
end
|
16
|
+
|
17
|
+
def speech
|
18
|
+
if reply['ssml'].present? && reply['text'].present?
|
19
|
+
raise(ArgumentError, 'A speech reply cannot contain booth "text" and "SSML" values.')
|
20
|
+
end
|
21
|
+
|
22
|
+
response = generate_alexa_response
|
23
|
+
output_speech = generate_speech_response(
|
24
|
+
text: reply['text'],
|
25
|
+
ssml: reply['ssml'],
|
26
|
+
play_behavior: reply['play_behavior']
|
27
|
+
)
|
28
|
+
|
29
|
+
response['response'].merge!(output_speech)
|
30
|
+
|
31
|
+
# Session values
|
32
|
+
if reply['session_values'].present?
|
33
|
+
session_values = generate_session_response(session_values: reply['session_values'])
|
34
|
+
response.merge!(session_values)
|
35
|
+
end
|
36
|
+
|
37
|
+
# End session
|
38
|
+
response['response'].merge!(generate_end_session_response(reply['end_session']))
|
39
|
+
|
40
|
+
# Reprompts
|
41
|
+
if reply['reprompt'].present?
|
42
|
+
reprompt_response = generate_reprompt_response
|
43
|
+
response['response'].merge!(reprompt_response)
|
44
|
+
end
|
45
|
+
|
46
|
+
response
|
47
|
+
end
|
48
|
+
|
49
|
+
def card
|
50
|
+
response = generate_alexa_response
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
response
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def generate_alexa_response
|
60
|
+
{
|
61
|
+
'version' => '1.0',
|
62
|
+
'response' => { }
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def generate_speech_response(text:, ssml:, play_behavior: nil)
|
67
|
+
response = { 'outputSpeech' => { } }
|
68
|
+
|
69
|
+
if ssml.present?
|
70
|
+
response['outputSpeech']['type'] = 'SSML'
|
71
|
+
response['outputSpeech']['ssml'] = ssml
|
72
|
+
else
|
73
|
+
if text.size > 8_000
|
74
|
+
raise(ArgumentError, 'Speech replies cannot exceed 8000 characters.')
|
75
|
+
end
|
76
|
+
|
77
|
+
response['outputSpeech']['type'] = 'PlainText'
|
78
|
+
response['outputSpeech']['text'] = text
|
79
|
+
end
|
80
|
+
|
81
|
+
if play_behavior.present?
|
82
|
+
response['outputSpeech']['playBehavior'] = play_behavior
|
83
|
+
end
|
84
|
+
|
85
|
+
response
|
86
|
+
end
|
87
|
+
|
88
|
+
def generate_session_response(session_values:)
|
89
|
+
session_map = { 'sessionAttributes' => { } }
|
90
|
+
|
91
|
+
session_values.each do |k,v|
|
92
|
+
session_map['sessionAttributes'][k] = v
|
93
|
+
end
|
94
|
+
|
95
|
+
session_map
|
96
|
+
end
|
97
|
+
|
98
|
+
def generate_end_session_response(end_session=false)
|
99
|
+
{ 'shouldEndSession' => end_session }
|
100
|
+
end
|
101
|
+
|
102
|
+
def generate_reprompt_response
|
103
|
+
if reply['reprompt'].is_a?(Boolean)
|
104
|
+
speech_response = generate_speech_response(
|
105
|
+
text: reply['text'],
|
106
|
+
ssml: reply['ssml'],
|
107
|
+
play_behavior: reply['play_behavior']
|
108
|
+
)
|
109
|
+
elsif reply['reprompt'].is_a?(Hash)
|
110
|
+
speech_response = generate_speech_response(
|
111
|
+
text: reply['reprompt']['text'],
|
112
|
+
ssml: reply['reprompt']['ssml'],
|
113
|
+
play_behavior: reply['reprompt']['play_behavior']
|
114
|
+
)
|
115
|
+
end
|
116
|
+
|
117
|
+
{ 'reprompt': speech_response }
|
118
|
+
end
|
119
|
+
|
120
|
+
def generate_card_image_response
|
121
|
+
response = { }
|
122
|
+
|
123
|
+
if reply['small_image_url'].present?
|
124
|
+
response['smallImageUrl'] = reply['small_image_url']
|
125
|
+
end
|
126
|
+
|
127
|
+
if reply['large_image_url'].present?
|
128
|
+
response['largeImageUrl'] = reply['large_image_url']
|
129
|
+
end
|
130
|
+
|
131
|
+
response
|
132
|
+
end
|
133
|
+
|
134
|
+
def generate_card_response
|
135
|
+
response = { 'card' => { } }
|
136
|
+
|
137
|
+
case reply['type']
|
138
|
+
when 'Simple'
|
139
|
+
response['card']['type'] = 'Simple'
|
140
|
+
response['card']['title'] = reply['title']
|
141
|
+
response['card']['content'] = reply['content']
|
142
|
+
when 'Standard'
|
143
|
+
response['card']['type'] = 'Standard'
|
144
|
+
response['card']['title'] = reply['title']
|
145
|
+
response['card']['text'] = reply['content']
|
146
|
+
|
147
|
+
if generate_card_image_response.present?
|
148
|
+
response['card']['image'] = generate_card_image_response
|
149
|
+
end
|
150
|
+
when 'LinkAccount'
|
151
|
+
# Todo
|
152
|
+
when 'AskForPermissionsConsent'
|
153
|
+
# Todo
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Stealth
|
5
|
+
module Services
|
6
|
+
module Alexa
|
7
|
+
|
8
|
+
class Setup
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def trigger
|
12
|
+
Stealth::Logger.l(topic: "alexa", message: "There is no setup needed!")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Stealth
|
5
|
+
module Services
|
6
|
+
module Alexa
|
7
|
+
module Version
|
8
|
+
def self.version
|
9
|
+
File.read(File.join(File.dirname(__FILE__), '..', '..', '..', '..', 'VERSION')).strip
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
VERSION = Version.version
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
|
5
|
+
require 'stealth'
|
6
|
+
require 'stealth-alexa'
|
7
|
+
|
8
|
+
# Requires supporting files with custom matchers and macros, etc,
|
9
|
+
# in ./support/ and its subdirectories.
|
10
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
4
|
+
|
5
|
+
describe "Stealth::Services::Alexa::Version" do
|
6
|
+
|
7
|
+
let(:version_in_file) { File.read(File.join(File.dirname(__FILE__), '..', 'VERSION')).strip }
|
8
|
+
|
9
|
+
it "should return the current gem version" do
|
10
|
+
expect(Stealth::Services::Alexa::Version.version).to eq version_in_file
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should return the current gem version via a constant" do
|
14
|
+
expect(Stealth::Services::Alexa::VERSION).to eq version_in_file
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
|
2
|
+
|
3
|
+
version = File.read(File.join(File.dirname(__FILE__), 'VERSION')).strip
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'stealth-alexa'
|
7
|
+
s.summary = 'Stealth Alexa driver'
|
8
|
+
s.description = 'Alexa driver for Stealth.'
|
9
|
+
s.homepage = 'https://github.com/hellostealth/stealth-alexa'
|
10
|
+
s.licenses = ['MIT']
|
11
|
+
s.version = version
|
12
|
+
s.author = 'Mauricio Gomes'
|
13
|
+
s.email = 'mauricio@edge14.com'
|
14
|
+
|
15
|
+
s.add_dependency 'stealth', '< 2.0'
|
16
|
+
|
17
|
+
s.add_development_dependency 'rspec', '~> 3.6'
|
18
|
+
s.add_development_dependency 'rspec_junit_formatter', '~> 0.3'
|
19
|
+
s.add_development_dependency 'rack-test', '~> 0.7'
|
20
|
+
|
21
|
+
s.files = `git ls-files`.split("\n")
|
22
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
23
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
24
|
+
s.require_paths = ['lib']
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: stealth-alexa
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0.pre1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mauricio Gomes
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-12-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: stealth
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "<"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "<"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.6'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec_junit_formatter
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rack-test
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.7'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.7'
|
69
|
+
description: Alexa driver for Stealth.
|
70
|
+
email: mauricio@edge14.com
|
71
|
+
executables: []
|
72
|
+
extensions: []
|
73
|
+
extra_rdoc_files: []
|
74
|
+
files:
|
75
|
+
- ".circleci/config.yml"
|
76
|
+
- ".gitignore"
|
77
|
+
- Gemfile
|
78
|
+
- Gemfile.lock
|
79
|
+
- LICENSE
|
80
|
+
- README.md
|
81
|
+
- VERSION
|
82
|
+
- lib/stealth-alexa.rb
|
83
|
+
- lib/stealth/alexa.rb
|
84
|
+
- lib/stealth/services/alexa/client.rb
|
85
|
+
- lib/stealth/services/alexa/message_handler.rb
|
86
|
+
- lib/stealth/services/alexa/reply_handler.rb
|
87
|
+
- lib/stealth/services/alexa/setup.rb
|
88
|
+
- lib/stealth/services/alexa/version.rb
|
89
|
+
- spec/spec_helper.rb
|
90
|
+
- spec/version_spec.rb
|
91
|
+
- stealth-alexa.gemspec
|
92
|
+
homepage: https://github.com/hellostealth/stealth-alexa
|
93
|
+
licenses:
|
94
|
+
- MIT
|
95
|
+
metadata: {}
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options: []
|
98
|
+
require_paths:
|
99
|
+
- lib
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 1.3.1
|
110
|
+
requirements: []
|
111
|
+
rubyforge_project:
|
112
|
+
rubygems_version: 2.7.7
|
113
|
+
signing_key:
|
114
|
+
specification_version: 4
|
115
|
+
summary: Stealth Alexa driver
|
116
|
+
test_files:
|
117
|
+
- spec/spec_helper.rb
|
118
|
+
- spec/version_spec.rb
|