alexa_toolbox 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +38 -0
- data/LICENSE.txt +22 -0
- data/README.md +169 -0
- data/Rakefile +1 -0
- data/lib/alexa_toolbox/address.rb +106 -0
- data/lib/alexa_toolbox/audioplayer.rb +16 -0
- data/lib/alexa_toolbox/context.rb +27 -0
- data/lib/alexa_toolbox/intent.rb +21 -0
- data/lib/alexa_toolbox/request.rb +59 -0
- data/lib/alexa_toolbox/response.rb +161 -0
- data/lib/alexa_toolbox/session.rb +61 -0
- data/lib/alexa_toolbox/version.rb +3 -0
- data/lib/alexa_toolbox.rb +28 -0
- metadata +140 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 410e7ebcc29c183541f1c4d5ad5b13e898ca1263
|
4
|
+
data.tar.gz: 3507752cb6aa7791bf0c99db20fd00ceaf063e7a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a21accae38d9aad6543ace3ab03eddd6d327b88999aee89c87b8e3deeba4b5a32b7ab03de1413022ebe0da3cbb0631ff58d5bf9a10955ca39a9fa77ef6a6cbb8
|
7
|
+
data.tar.gz: 56918a9a6c88a5b562e5514b2e0b80dbbdc779ddc8c0869ef28696fbd9e5abda5e196c2889c17bafce50198bb64ed44ed21467d3a067f7d3bb60f211b7cbc488
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
alexa_toolbox (1.0.0)
|
5
|
+
bundler (~> 1.14)
|
6
|
+
deep_merge
|
7
|
+
rake
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
deep_merge (1.1.1)
|
13
|
+
diff-lcs (1.3)
|
14
|
+
rake (12.0.0)
|
15
|
+
rspec (3.6.0)
|
16
|
+
rspec-core (~> 3.6.0)
|
17
|
+
rspec-expectations (~> 3.6.0)
|
18
|
+
rspec-mocks (~> 3.6.0)
|
19
|
+
rspec-core (3.6.0)
|
20
|
+
rspec-support (~> 3.6.0)
|
21
|
+
rspec-expectations (3.6.0)
|
22
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
23
|
+
rspec-support (~> 3.6.0)
|
24
|
+
rspec-mocks (3.6.0)
|
25
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
26
|
+
rspec-support (~> 3.6.0)
|
27
|
+
rspec-support (3.6.0)
|
28
|
+
|
29
|
+
PLATFORMS
|
30
|
+
ruby
|
31
|
+
|
32
|
+
DEPENDENCIES
|
33
|
+
alexa_toolbox!
|
34
|
+
rspec (~> 3.6, >= 3.6.0)
|
35
|
+
rspec-mocks (~> 3.6, >= 3.6.0)
|
36
|
+
|
37
|
+
BUNDLED WITH
|
38
|
+
1.14.6
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2017 Paul McMahon
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
# AlexaToolbox
|
2
|
+
|
3
|
+
This gem implements a full suite of tools for deploying applications for Amazon's Alexa.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
### For Ruby Projects:
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'alexa_toolbox'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install alexa_toolbox
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
This Gem provides methods to create and handle requests, create response objects, and miscellaneous support functions for Alexa skills.
|
26
|
+
|
27
|
+
### Requests
|
28
|
+
|
29
|
+
[Amazon Request Documentation](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/alexa-skills-kit-interface-reference#request-format)
|
30
|
+
|
31
|
+
#### Handling Request JSON:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
require 'alexa_toolbox'
|
35
|
+
request = AlexaToolbox::Request.new(params,{ :application_id => 'YOUR_APP_ID' })
|
36
|
+
# params variable is json request retrieved by server (default is params in Ruby on Rails)
|
37
|
+
# request application IDs are checked vs your application ID to ensure they are valid requests as per Amazon's guidelines
|
38
|
+
```
|
39
|
+
#### Check Request Validity:
|
40
|
+
|
41
|
+
You should always check request validity as per Amazon's guidelines
|
42
|
+
[Amazon Guidelines](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/handling-requests-sent-by-alexa#verifying-that-the-request-is-intended-for-your-service)
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
require 'alexa_toolbox'
|
46
|
+
request = AlexaToolbox::Request.new(params,{ :application_id => 'YOUR_APP_ID' })
|
47
|
+
if !request.valid?
|
48
|
+
# return 500 status
|
49
|
+
end
|
50
|
+
```
|
51
|
+
#### Getting Information From the Request
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
require 'alexa_toolbox'
|
55
|
+
request = AlexaToolbox::Request.new(params,{ :application_id => 'YOUR_APP_ID' })
|
56
|
+
|
57
|
+
request.type
|
58
|
+
# Returns request type:
|
59
|
+
# LaunchRequest
|
60
|
+
# IntentRequest
|
61
|
+
# SessionEndedRequest
|
62
|
+
# AudioPlayer
|
63
|
+
# PlaybackController
|
64
|
+
# System.ExceptionEncountered
|
65
|
+
|
66
|
+
# Check if new session
|
67
|
+
request.session.new?
|
68
|
+
# Get sessionId
|
69
|
+
request.session.session_id
|
70
|
+
# Get userId
|
71
|
+
request.session.user_id
|
72
|
+
```
|
73
|
+
|
74
|
+
#### Utilizing Request Hash
|
75
|
+
|
76
|
+
If you are more comfortable navigating the request hash and are familiar with the setup you can still acces it.
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
require 'alexa_toolbox'
|
80
|
+
request = AlexaToolbox::Request.new(params,{ :application_id => 'YOUR_APP_ID' })
|
81
|
+
|
82
|
+
# Get Session
|
83
|
+
request.json[:session]
|
84
|
+
# Get sessionId
|
85
|
+
request.json[:session][:sessionId]
|
86
|
+
```
|
87
|
+
|
88
|
+
#### Utilizing Address Consent
|
89
|
+
|
90
|
+
[Alexa Documentation on Address Data](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/device-address-api)
|
91
|
+
|
92
|
+
If you want to access the user's address, you'll first require their permission and then you'll receive a consent token and deviceId in the request.
|
93
|
+
Once you have those, you will need to utilize Amazon's API to retrieve the address data. (Supporting feature a WIP.)
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
require 'alexa_toolbox'
|
97
|
+
request = AlexaToolbox::Request.new(params,{ :application_id => 'YOUR_APP_ID' })
|
98
|
+
|
99
|
+
consent_token = request.session.consent_token
|
100
|
+
device_id = request.session.device_id
|
101
|
+
```
|
102
|
+
|
103
|
+
|
104
|
+
### Responses
|
105
|
+
|
106
|
+
[Amazon Response Documentation](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/alexa-skills-kit-interface-reference#response-format)
|
107
|
+
|
108
|
+
#### Simple Response Object Example:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
require 'alexa_toolbox'
|
112
|
+
response = AlexaToolbox::Response.new
|
113
|
+
response.add_speech('Creating a response is so easy!')
|
114
|
+
response.build_response
|
115
|
+
```
|
116
|
+
|
117
|
+
#### If you want to utilize SSML (will add speak tags if you forget!):
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
require 'alexa_toolbox'
|
121
|
+
response = AlexaToolbox::Response.new
|
122
|
+
response.add_speech('Creating a response is so easy!',true)
|
123
|
+
# or
|
124
|
+
# response.add_speech('<speak>Creating a response is so easy!</speak>',true)
|
125
|
+
response.build_response
|
126
|
+
```
|
127
|
+
|
128
|
+
#### To add a card to your response:
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
require 'alexa_toolbox'
|
132
|
+
response = AlexaToolbox::Response.new
|
133
|
+
# add_card(type,title,subtitle,content)
|
134
|
+
# or
|
135
|
+
# add_hash_card({ :type => type, :title => title, :subtitle => subtitle, :content => content })
|
136
|
+
response.add_card("PlainText","Card Title","Card Subtitle","This card should have some interesting content for your user.")
|
137
|
+
response.build_response
|
138
|
+
```
|
139
|
+
|
140
|
+
#### Will generate a valid outputspeech response in JSON format:
|
141
|
+
|
142
|
+
```JSON
|
143
|
+
{
|
144
|
+
"version": "1.0",
|
145
|
+
"response": {
|
146
|
+
"outputSpeech": {
|
147
|
+
"type": "PlainText",
|
148
|
+
"text": "Ruby is running ready!"
|
149
|
+
},
|
150
|
+
"shouldEndSession": true
|
151
|
+
}
|
152
|
+
}
|
153
|
+
```
|
154
|
+
|
155
|
+
## Troubleshooting
|
156
|
+
|
157
|
+
|
158
|
+
## Contributing
|
159
|
+
|
160
|
+
1. Fork it
|
161
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
162
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
163
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
164
|
+
5. Create a new Pull Request
|
165
|
+
|
166
|
+
Please make sure to write specs for any new features!
|
167
|
+
|
168
|
+
# Team Members
|
169
|
+
* "Paul McMahon" <colpan@sircolpaniusjackson.com>
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module AlexaToolbox
|
2
|
+
# Handles the retrieving address information about a device.
|
3
|
+
class Address
|
4
|
+
require 'net/http'
|
5
|
+
attr_reader :json
|
6
|
+
attr_accessor :consent_token, :device_id, :full_address, :app_location
|
7
|
+
|
8
|
+
def initialize (consent_token = nil, device_id = nil, app_location = "US", full_address = false)
|
9
|
+
@consent_token = consent_token
|
10
|
+
@device_id = device_id
|
11
|
+
@country_code = nil
|
12
|
+
@postal_code = nil
|
13
|
+
@city = nil
|
14
|
+
@state_or_region = nil
|
15
|
+
@address_line_1 = nil
|
16
|
+
@address_line_2 = nil
|
17
|
+
@address_line_3 = nil
|
18
|
+
@district_or_county = nil
|
19
|
+
@status = nil
|
20
|
+
@full_address = full_address
|
21
|
+
@app_location = app_location
|
22
|
+
@json = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_request(request)
|
26
|
+
@consent_token = request.session.consent_token
|
27
|
+
@device_id = request.session.device_id
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_token(token)
|
31
|
+
@consent_token = token
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_device(device)
|
35
|
+
@device_id = device
|
36
|
+
end
|
37
|
+
|
38
|
+
def retrieve_full_address(consent_token = nil, device_id = nil)
|
39
|
+
token = consent_token.nil? ? @consent_token : consent_token
|
40
|
+
device = device_id.nil? ? @device_id : device_id
|
41
|
+
if !token.nil? && !device.nil?
|
42
|
+
if self.base_uri.nil?
|
43
|
+
raise ArgumentError, 'Invalid Request, Invalid app_location set'
|
44
|
+
else
|
45
|
+
full_uri = self.base_uri + "/v1/devices/" + device + "/settings/address"
|
46
|
+
self.execute_request(full_uri,token)
|
47
|
+
end
|
48
|
+
else
|
49
|
+
raise ArgumentError, 'Invalid Request, No Token or Device'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def retrieve_partial_address(consent_token = nil, device_id = nil)
|
54
|
+
token = consent_token.nil? ? @consent_token : consent_token
|
55
|
+
device = device_id.nil? ? @device_id : device_id
|
56
|
+
if !token.nil? && !device.nil?
|
57
|
+
if self.base_uri.nil?
|
58
|
+
raise ArgumentError, 'Invalid Request, Invalid app_location set'
|
59
|
+
else
|
60
|
+
full_uri = self.base_uri + "/v1/devices/" + device + "/settings/address/countryAndPostalCode"
|
61
|
+
self.execute_request(full_uri,token)
|
62
|
+
end
|
63
|
+
else
|
64
|
+
raise ArgumentError, 'Invalid Request, No Token or Device'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def execute_request(endpoint,token)
|
69
|
+
uri = URI(endpoint)
|
70
|
+
|
71
|
+
Net::HTTP.start(uri.hostname, uri.port, :use_ssl => uri.scheme == 'https') do |http|
|
72
|
+
request = Net::HTTP::Get.new(uri)
|
73
|
+
request['Authorization'] = token
|
74
|
+
|
75
|
+
response = http.request(request)
|
76
|
+
self.status = response.code
|
77
|
+
self.json = response.body
|
78
|
+
if response.code == 200
|
79
|
+
res_body = response.body
|
80
|
+
@country_code = res_body.key?("countryCode") ? res_body["countryCode"] : self.country_code
|
81
|
+
@postal_code = res_body.key?("postalCode") ? res_body["postalCode"] : self.postal_code
|
82
|
+
@city = res_body.key?("city") ? res_body["city"] : self.city
|
83
|
+
@state_or_region = res_body.key?("stateOrRegion") ? res_body["stateOrRegion"] : self.state_or_region
|
84
|
+
@address_line_1 = res_body.key?("addressLine1") ? res_body["addressLine1"] : self.address_line_1
|
85
|
+
@address_line_2 = res_body.key?("addressLine2") ? res_body["addressLine2"] : self.address_line_2
|
86
|
+
@address_line_3 = res_body.key?("addressLine3") ? res_body["addressLine3"] : self.address_line_3
|
87
|
+
@district_or_county = res_body.key?("districtOrCounty") ? res_body["districtOrCounty"] : self.district_or_county
|
88
|
+
else
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def base_uri
|
94
|
+
case self.app_location
|
95
|
+
when "US"
|
96
|
+
return "https://api.amazonalexa.com"
|
97
|
+
when "UK"
|
98
|
+
return "https://api.eu.amazonalexa.com"
|
99
|
+
when "DE"
|
100
|
+
return "https://api.eu.amazonalexa.com"
|
101
|
+
else
|
102
|
+
return nil
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module AlexaToolbox
|
2
|
+
# Handles the audioplayer object in requests.
|
3
|
+
class AudioPlayer
|
4
|
+
attr_reader :type, :offset_in_milliseconds, :token, :current_playback_state, :error, :json
|
5
|
+
|
6
|
+
def initialize (audioplayer)
|
7
|
+
raise ArgumentError, 'Invalid AudioPlayer' if !audioplayer.key?(:type) && audioplayer[:type][0..10] != "AudioPlayer"
|
8
|
+
@type = audioplayer[:type][12..-1]
|
9
|
+
@offset_in_milliseconds = audioplayer[:offsetInMilliseconds]
|
10
|
+
@token = audioplayer[:token]
|
11
|
+
@current_playback_state = audioplayer[:currentPlaybackState]
|
12
|
+
@json = audioplayer
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module AlexaToolbox
|
2
|
+
# Handles the session object in requests.
|
3
|
+
class Context
|
4
|
+
require 'alexa_toolbox/audioplayer'
|
5
|
+
attr_reader :audioplayer, :system, :json
|
6
|
+
|
7
|
+
def initialize (context)
|
8
|
+
raise ArgumentError, 'Invalid Context' if false
|
9
|
+
@system = context[:System]
|
10
|
+
@audioplayer = context.key?(:AudioPlayer) ? AlexaToolbox::AudioPlayer.new(context[:AudioPlayer]) : nil
|
11
|
+
@json = context
|
12
|
+
end
|
13
|
+
|
14
|
+
def application_id
|
15
|
+
return self.system[:application][:applicationId]
|
16
|
+
end
|
17
|
+
|
18
|
+
def user_id
|
19
|
+
return self.system[:user][:userId]
|
20
|
+
end
|
21
|
+
|
22
|
+
def device_id
|
23
|
+
return self.system[:device][:deviceId]
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module AlexaToolbox
|
2
|
+
# Handles the intent object in requests.
|
3
|
+
class Intent
|
4
|
+
attr_reader :name, :confirmation_status, :json
|
5
|
+
|
6
|
+
def initialize (intent)
|
7
|
+
raise ArgumentError, 'Invalid Intent' if intent[:name].empty?
|
8
|
+
@name = intent[:name]
|
9
|
+
@confirmation_status = intent[:confirmationStatus]
|
10
|
+
@json = intent
|
11
|
+
end
|
12
|
+
|
13
|
+
def has_slots?
|
14
|
+
return self.slots != {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def slots
|
18
|
+
return self.json[:slots]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module AlexaToolbox
|
2
|
+
# Echo can send 3 types of requests
|
3
|
+
# - LaunchRequest: The app was launched using phrasing such as 'Alexa open ______'.
|
4
|
+
# - IntentRequest: An intent was requested.
|
5
|
+
# - SessionEndedRequest: Session was closed either by user or timeout.
|
6
|
+
|
7
|
+
class Request
|
8
|
+
require 'json'
|
9
|
+
require 'deep_merge'
|
10
|
+
require 'alexa_toolbox/session'
|
11
|
+
require 'alexa_toolbox/intent'
|
12
|
+
require 'alexa_toolbox/audioplayer'
|
13
|
+
require 'alexa_toolbox/context'
|
14
|
+
attr_reader :version, :session, :json, :options, :request_id, :locale, :type, :intent, :audioplayer, :error, :cause, :context
|
15
|
+
|
16
|
+
def self.default_options
|
17
|
+
@default_options ||= {
|
18
|
+
:application_id => ''
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(json_request, options = {})
|
23
|
+
options = options.deep_merge(self.class.default_options)
|
24
|
+
@options = options
|
25
|
+
|
26
|
+
@request_id = json_request[:request][:requestId]
|
27
|
+
raise ArgumentError, 'Request ID not present' if @request_id.nil?
|
28
|
+
@version = json_request[:version]
|
29
|
+
@locale = json_request[:request][:locale]
|
30
|
+
@error = json_request[:request][:error]
|
31
|
+
@cause = json_request[:request][:cause]
|
32
|
+
@json = json_request
|
33
|
+
@type = json_request[:request][:type][0..10] == "AudioPlayer" ? "AudioPlayer" : json_request[:request][:type]
|
34
|
+
|
35
|
+
@intent = @type == "IntentRequest" ? AlexaToolbox::Intent.new(json_request[:request][:intent]) : nil
|
36
|
+
@audioplayer = @type == "AudioPlayer" ? AlexaToolbox::AudioPlayer.new(json_request[:request]) : nil
|
37
|
+
@session = json_request.key?(:session) ? AlexaToolbox::Session.new(json_request[:session]) : nil
|
38
|
+
@context = json_request.key?(:context) ? AlexaToolbox::Context.new(json_request[:context]) : nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def valid?
|
42
|
+
self.options[:application_id] && ((!self.session.nil? && self.options[:application_id] == self.session.application_id) || (!self.context.nil? && self.options[:application_id] == self.context.application_id))
|
43
|
+
end
|
44
|
+
|
45
|
+
def user_terminated?
|
46
|
+
if self.type != "SessionEndedRequest"
|
47
|
+
return nil
|
48
|
+
end
|
49
|
+
return self.json[:request][:reason] == "USER_INITIATED"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Builds a new request for Alexa, primarily for testing purposes
|
54
|
+
def self.build_request(json_request, application_id = "")
|
55
|
+
json_request = self.transform_keys_to_symbols(json_request)
|
56
|
+
raise ArgumentError, 'Invalid Alexa Request. Missing session, request, version, or application id.' unless AlexaToolbox.valid_alexa?(json_request)
|
57
|
+
Request.new(json_request,{ :application_id => application_id })
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
module AlexaToolbox
|
2
|
+
class Response
|
3
|
+
require 'json'
|
4
|
+
attr_reader :version, :reprompt, :speech, :response, :session_attributes, :card
|
5
|
+
attr_accessor :player_response, :session_end
|
6
|
+
|
7
|
+
# Every response will have a version, response, and shouldEndSession
|
8
|
+
def initialize(player_response = false, ssml = false, version = '1.0')
|
9
|
+
@session_attributes = Hash.new
|
10
|
+
@version = version
|
11
|
+
@directives = []
|
12
|
+
@player_response = player_response
|
13
|
+
@ssml = ssml
|
14
|
+
@session_end = true
|
15
|
+
@reprompt = nil
|
16
|
+
@speech = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
# Adds a key,value pair to the session object.
|
20
|
+
def add_session_attribute(key, value)
|
21
|
+
@session_attributes[key.to_sym] = AlexaToolbox.transform_keys_to_symbols(value)
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_speech(speech_text, ssml = nil)
|
25
|
+
ssml = ssml.nil? ? @ssml : ssml
|
26
|
+
if ssml
|
27
|
+
@speech = { :type => 'SSML', :ssml => check_ssml(speech_text) }
|
28
|
+
else
|
29
|
+
@speech = { :type => 'PlainText', :text => speech_text }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_plain_speech(speech_text)
|
34
|
+
@speech = { :type => 'PlainText', :text => speech_text }
|
35
|
+
end
|
36
|
+
|
37
|
+
def add_ssml_speech(speech_text)
|
38
|
+
@speech = { :type => 'SSML', :ssml => check_ssml(speech_text) }
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_reprompt(speech_text, ssml = nil)
|
42
|
+
ssml = ssml.nil? ? @ssml : ssml
|
43
|
+
if ssml
|
44
|
+
@reprompt = { "outputSpeech" => { :type => 'SSML', :ssml => check_ssml(speech_text) } }
|
45
|
+
else
|
46
|
+
@reprompt = { "outputSpeech" => { :type => 'PlainText', :text => speech_text } }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_ssml_reprompt(speech_text)
|
51
|
+
@reprompt = { "outputSpeech" => { :type => 'SSML', :ssml => check_ssml(speech_text) } }
|
52
|
+
end
|
53
|
+
|
54
|
+
def add_audio_play_directive(url, play_behavior = '', token = '', expected_previous_token = '', offset = 0)
|
55
|
+
directive = {
|
56
|
+
'type' => 'AudioPlayer.Play',
|
57
|
+
'playBehavior' => play_behavior,
|
58
|
+
'audioItem' => {
|
59
|
+
'stream' => {
|
60
|
+
'token' => token,
|
61
|
+
'url' => url,
|
62
|
+
'offsetInMilliseconds' => offset
|
63
|
+
}
|
64
|
+
}
|
65
|
+
}
|
66
|
+
directive['audioItem']['stream']['expectedPreviousToken'] = expected_previous_token if play_behavior == "ENQUEUE"
|
67
|
+
@directives << directive
|
68
|
+
end
|
69
|
+
|
70
|
+
def add_audio_clear_directive(clear_all = false)
|
71
|
+
@directives << {
|
72
|
+
'type' => 'AudioPlayer.ClearQueue',
|
73
|
+
'clearBehavior' => clear_all ? "CLEAR_ALL" : "CLEAR_ENQUEUED"
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
def add_audio_stop_directive
|
78
|
+
@directives << {
|
79
|
+
'type' => 'AudioPlayer.Stop'
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
# "type": "string",
|
84
|
+
# "title": "string",
|
85
|
+
# "content": "string"
|
86
|
+
# Standard uses :text instead of :content
|
87
|
+
def add_card(type = nil, title = nil, content = nil, smallImageUrl = nil, largeImageUrl = nil)
|
88
|
+
@card = Hash.new
|
89
|
+
@card[:type] = type.nil? ? 'Simple' : type
|
90
|
+
if @card[:type] != "LinkAccount"
|
91
|
+
@card[:title] = title unless title.nil?
|
92
|
+
@card[:type] = 'Simple' if smallImageUrl.nil? && largeImageUrl.nil?
|
93
|
+
if @card[:type] == 'Simple'
|
94
|
+
@card[:content] = content unless content.nil?
|
95
|
+
else
|
96
|
+
@card[:text] = content unless content.nil?
|
97
|
+
@card[:image] = {
|
98
|
+
:smallImageUrl => smallImageUrl,
|
99
|
+
:largeImageUrl => largeImageUrl
|
100
|
+
}
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Add Permission Card for Location Information
|
106
|
+
def add_permission_card(full_address = false)
|
107
|
+
@card = Hash.new
|
108
|
+
@card[:type] = 'AskForPermissionsConsent'
|
109
|
+
permission = full_address ? "read::alexa:device:all:address" : "read::alexa:device:all:address:country_and_postal_code"
|
110
|
+
@card[:permissions] = [permission]
|
111
|
+
end
|
112
|
+
|
113
|
+
# Add a card as a single hash
|
114
|
+
def add_hash_card(card)
|
115
|
+
card[:type] = 'Simple' if card[:type].nil?
|
116
|
+
@card = card
|
117
|
+
end
|
118
|
+
|
119
|
+
# The response object as hash (with outputspeech, cards and session end)
|
120
|
+
def build_standard_response_object(session_end = true)
|
121
|
+
@response = Hash.new
|
122
|
+
@response[:outputSpeech] = @speech unless @speech.nil?
|
123
|
+
@response[:directives] = @directives unless @directives.empty?
|
124
|
+
@response[:card] = @card unless @card.nil?
|
125
|
+
@response[:reprompt] = @reprompt unless @reprompt.nil?
|
126
|
+
@response[:shouldEndSession] = session_end
|
127
|
+
@response
|
128
|
+
end
|
129
|
+
|
130
|
+
# For Responses to AudioPlayer or PlaybackController Requests
|
131
|
+
# Cannot Include: outputSpeech, card, reprompt, shouldEndSession
|
132
|
+
def build_player_response_object(session_end = true)
|
133
|
+
@response = Hash.new
|
134
|
+
@response[:directives] = @directives unless @directives.empty?
|
135
|
+
@response
|
136
|
+
end
|
137
|
+
|
138
|
+
# Builds a response object, can be json or hash if json is false
|
139
|
+
def build_response(session_end = nil, player_response = nil, json = true)
|
140
|
+
is_player_response = player_response.nil? ? @player_response : player_response
|
141
|
+
end_session = session_end.nil? ? @session_end : session_end
|
142
|
+
response = Hash.new
|
143
|
+
if is_player_response
|
144
|
+
response_object = build_player_response_object(end_session)
|
145
|
+
else
|
146
|
+
response_object = build_standard_response_object(end_session)
|
147
|
+
response[:sessionAttributes] = @session_attributes unless @session_attributes.empty?
|
148
|
+
end
|
149
|
+
response[:version] = @version
|
150
|
+
response[:response] = response_object
|
151
|
+
json ? JSON.parse(response.to_json) : response
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
def check_ssml(ssml_string)
|
157
|
+
ssml_string = ssml_string.strip[0..6] == "<speak>" ? ssml_string : "<speak>" + ssml_string
|
158
|
+
ssml_string.strip[-8..1] == "</speak>" ? ssml_string : ssml_string + "</speak>"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module AlexaToolbox
|
2
|
+
# Handles the session object in requests.
|
3
|
+
class Session
|
4
|
+
attr_reader :new, :session_id, :attributes, :user, :application_id, :device
|
5
|
+
|
6
|
+
def initialize (session)
|
7
|
+
raise ArgumentError, 'Invalid Session' if session.nil? || session[:new].nil? || session[:sessionId].nil?
|
8
|
+
@new = session[:new]
|
9
|
+
@session_id = session[:sessionId]
|
10
|
+
@attributes = session[:attributes].nil? ? Hash.new : session[:attributes]
|
11
|
+
@user = session[:user]
|
12
|
+
@application_id = session[:application][:applicationId]
|
13
|
+
@device = session[:device]
|
14
|
+
end
|
15
|
+
|
16
|
+
# Checks whether this is a new session
|
17
|
+
def new?
|
18
|
+
@new
|
19
|
+
end
|
20
|
+
|
21
|
+
# Checks if user is defined
|
22
|
+
def user_defined?
|
23
|
+
!@user.nil? || !@user[:userId].nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns user's id
|
27
|
+
def user_id
|
28
|
+
@user[:userId] if @user
|
29
|
+
end
|
30
|
+
|
31
|
+
def access_token?
|
32
|
+
@user.key?(:accessToken) && !@user[:accessToken].empty?
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns user's access token if provided
|
36
|
+
def access_token
|
37
|
+
@user[:accessToken] if @user
|
38
|
+
end
|
39
|
+
|
40
|
+
def consent_token?
|
41
|
+
@user.key?(:permissions) && @user[:permissions].key?(:consentToken) && !@user[:permissions][:consentToken].empty?
|
42
|
+
end
|
43
|
+
|
44
|
+
def consent_token
|
45
|
+
@user[:permissions][:consentToken] if @user && @user.key?(:permissions) && @user[:permissions].key?(:consentToken)
|
46
|
+
end
|
47
|
+
|
48
|
+
def device_id
|
49
|
+
@device[:deviceId]
|
50
|
+
end
|
51
|
+
|
52
|
+
def supported_interfaces
|
53
|
+
@device[:supportedInterfaces]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Check to see if attributes are present.
|
57
|
+
def has_attributes?
|
58
|
+
!@attributes.empty?
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'alexa_toolbox/request'
|
2
|
+
require 'alexa_toolbox/version'
|
3
|
+
require 'alexa_toolbox/response'
|
4
|
+
require 'alexa_toolbox/address'
|
5
|
+
|
6
|
+
module AlexaToolbox
|
7
|
+
# Prints a JSON object.
|
8
|
+
def self.print_json(json)
|
9
|
+
p json
|
10
|
+
end
|
11
|
+
|
12
|
+
# Prints the Gem version.
|
13
|
+
def self.print_version
|
14
|
+
p AlexaToolbox::VERSION
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns true if all the Alexa objects are set.
|
18
|
+
def self.valid_alexa?(request_json)
|
19
|
+
!request_json.nil? && !request_json[:version].nil? && !request_json[:request].nil?
|
20
|
+
end
|
21
|
+
|
22
|
+
# Take keys of hash and transform those to a symbols
|
23
|
+
def self.transform_keys_to_symbols(input)
|
24
|
+
return input if not input.is_a?(Hash)
|
25
|
+
hash = input.inject({}){|store,(key,val)| store[key.to_sym] = self.transform_keys_to_symbols(val); store}
|
26
|
+
return hash
|
27
|
+
end
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: alexa_toolbox
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Paul McMahon
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-05-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.14'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.14'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: deep_merge
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.6'
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: 3.6.0
|
65
|
+
type: :development
|
66
|
+
prerelease: false
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - "~>"
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '3.6'
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: 3.6.0
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rspec-mocks
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '3.6'
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: 3.6.0
|
85
|
+
type: :development
|
86
|
+
prerelease: false
|
87
|
+
version_requirements: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - "~>"
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '3.6'
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: 3.6.0
|
95
|
+
description: Full top to bottom toolbox for creating a custom Alexa skill.
|
96
|
+
email:
|
97
|
+
- colpan@sircolpaniusjackson.com
|
98
|
+
executables: []
|
99
|
+
extensions: []
|
100
|
+
extra_rdoc_files: []
|
101
|
+
files:
|
102
|
+
- Gemfile
|
103
|
+
- Gemfile.lock
|
104
|
+
- LICENSE.txt
|
105
|
+
- README.md
|
106
|
+
- Rakefile
|
107
|
+
- lib/alexa_toolbox.rb
|
108
|
+
- lib/alexa_toolbox/address.rb
|
109
|
+
- lib/alexa_toolbox/audioplayer.rb
|
110
|
+
- lib/alexa_toolbox/context.rb
|
111
|
+
- lib/alexa_toolbox/intent.rb
|
112
|
+
- lib/alexa_toolbox/request.rb
|
113
|
+
- lib/alexa_toolbox/response.rb
|
114
|
+
- lib/alexa_toolbox/session.rb
|
115
|
+
- lib/alexa_toolbox/version.rb
|
116
|
+
homepage: https://github.com/colpan/alexa-toolbox
|
117
|
+
licenses:
|
118
|
+
- MIT
|
119
|
+
metadata: {}
|
120
|
+
post_install_message:
|
121
|
+
rdoc_options: []
|
122
|
+
require_paths:
|
123
|
+
- lib
|
124
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
requirements: []
|
135
|
+
rubyforge_project:
|
136
|
+
rubygems_version: 2.4.5.1
|
137
|
+
signing_key:
|
138
|
+
specification_version: 4
|
139
|
+
summary: Full top to bottom toolbox for creating a custom Alexa skill.
|
140
|
+
test_files: []
|