alexa_ruby 0.1.0 → 0.2.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 +4 -4
- data/README.md +59 -1
- data/lib/alexa_ruby/request/audio_player_request.rb +50 -0
- data/lib/alexa_ruby/request/intent_request/slot.rb +23 -0
- data/lib/alexa_ruby/request/intent_request.rb +61 -0
- data/lib/alexa_ruby/request/launch_request.rb +19 -0
- data/lib/alexa_ruby/request/session_ended_request.rb +23 -0
- data/lib/alexa_ruby/request.rb +18 -37
- data/lib/alexa_ruby/response/audio_player.rb +37 -19
- data/lib/alexa_ruby/response/card.rb +25 -0
- data/lib/alexa_ruby/response.rb +112 -66
- data/lib/alexa_ruby/session.rb +34 -16
- data/lib/alexa_ruby/version.rb +1 -1
- data/lib/alexa_ruby.rb +76 -15
- metadata +21 -4
- data/lib/alexa_ruby/intent_request.rb +0 -60
- data/lib/alexa_ruby/launch_request.rb +0 -15
- data/lib/alexa_ruby/session_ended_request.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 05efe37b6e97b7278bbfe995867983640c07b90d
|
4
|
+
data.tar.gz: '08e039b53252f7a87961e7d21cd02a45a2917c64'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 75606a0692119854f7ed8492892b64be91e52554c6d271b0e8a4b3d04f545f254942b16e616144945bfccd0fba79fdd58f366a1df195e44352a49a724c67dab7
|
7
|
+
data.tar.gz: 632e697b97175adfe62a22a8b1978b9bae2027d6c69fb901d1c6de4f06e4dacd0557a7faa9ea80b28eb45316bc40508876d6148bf51ac985fc9d02be6cee0722
|
data/README.md
CHANGED
@@ -1,5 +1,63 @@
|
|
1
1
|
# AlexaRuby
|
2
2
|
|
3
|
+
[](https://badge.fury.io/rb/alexa_ruby)
|
3
4
|
[](https://travis-ci.org/mulev/alexa-ruby)
|
4
5
|
|
5
|
-
Originally forked from damianFC's AlexaRubykit, this gem implements a back-end service for interaction with Amazon Alexa API.
|
6
|
+
Originally forked from [damianFC's AlexaRubykit](https://github.com/damianFC/alexa-rubykit), this gem implements a back-end service for interaction with Amazon Alexa API.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
To install and use gem in your project just add this to your Gemfile:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem 'alexa_ruby'
|
14
|
+
```
|
15
|
+
|
16
|
+
And bundle it with:
|
17
|
+
|
18
|
+
```bash
|
19
|
+
$ bundle install
|
20
|
+
```
|
21
|
+
|
22
|
+
Or install it as a separate gem:
|
23
|
+
|
24
|
+
```bash
|
25
|
+
$ gem install alexa_ruby
|
26
|
+
```
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
Gem provides methods to handle requests from Amazon Alexa skill and to sent it a proper response.
|
31
|
+
|
32
|
+
You can quickly try it with that sample code:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
require 'alexa_ruby'
|
36
|
+
|
37
|
+
response = AlexaRuby::Response.new
|
38
|
+
response.add_speech('Yay, ruby is running!')
|
39
|
+
response.build_response
|
40
|
+
```
|
41
|
+
|
42
|
+
It will generate valid outspeech JSON:
|
43
|
+
|
44
|
+
```JSON
|
45
|
+
{
|
46
|
+
"version": "1.0",
|
47
|
+
"response": {
|
48
|
+
"outputSpeech": {
|
49
|
+
"type": "PlainText",
|
50
|
+
"text": "Yay, ruby is running!"
|
51
|
+
},
|
52
|
+
"shouldEndSession": true
|
53
|
+
}
|
54
|
+
}
|
55
|
+
```
|
56
|
+
|
57
|
+
## Testing
|
58
|
+
|
59
|
+
Run the tests using
|
60
|
+
|
61
|
+
```bash
|
62
|
+
bundle exec rspec --color --format=documentation
|
63
|
+
```
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module AlexaRuby
|
2
|
+
# AudioPlayerRequest class implements Alexa "AudioPlayer" request type
|
3
|
+
class AudioPlayerRequest < Request
|
4
|
+
attr_accessor :playback_state, :playback_offset, :error_type,
|
5
|
+
:error_message, :error_playback_token, :error_player_activity
|
6
|
+
|
7
|
+
# Initialize new AudioPlayer request
|
8
|
+
#
|
9
|
+
# @param json [Hash] valid JSON request from Amazon
|
10
|
+
def initialize(json)
|
11
|
+
@type = :audio_player
|
12
|
+
req = json[:request]
|
13
|
+
@playback_state = req[:type].gsub!('AudioPlayer.', '')
|
14
|
+
@playback_offset = current_offset(req)
|
15
|
+
define_error_params(req) if @playback_state == 'PlaybackFailed'
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
# Outputs the launch requestID
|
20
|
+
#
|
21
|
+
# @return [String] launch request ID
|
22
|
+
def to_s
|
23
|
+
"AudioPlayerRequest requestID: #{request_id}"
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# Get current playback offset from request
|
29
|
+
#
|
30
|
+
# @param json [Hash] "request" part of json request from Amazon
|
31
|
+
# @return [Integer] playback offset in milliseconds at the moment of request
|
32
|
+
def current_offset(json)
|
33
|
+
if @playback_state == 'PlaybackFailed'
|
34
|
+
json[:currentPlaybackState][:offsetInMilliseconds]
|
35
|
+
else
|
36
|
+
json[:offsetInMilliseconds]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Define all error parameters if an error occured during playback
|
41
|
+
#
|
42
|
+
# @param json [Hash] "request" part of json request from Amazon
|
43
|
+
def define_error_params(json)
|
44
|
+
@error_type = json[:error][:type]
|
45
|
+
@error_message = json[:error][:message]
|
46
|
+
@error_playback_token = json[:currentPlaybackState][:token]
|
47
|
+
@error_player_activity = json[:currentPlaybackState][:playerActivity]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module AlexaRuby
|
2
|
+
# Class that encapsulates each slot
|
3
|
+
class Slot
|
4
|
+
attr_accessor :name, :value
|
5
|
+
|
6
|
+
# Initialize slot and define its name and value
|
7
|
+
#
|
8
|
+
# @param name [String] slot name
|
9
|
+
# @param value [String] slot value
|
10
|
+
def initialize(name, value)
|
11
|
+
raise ArgumentError, 'Need a name and a value' if name.nil? || value.nil?
|
12
|
+
@name = name
|
13
|
+
@value = value
|
14
|
+
end
|
15
|
+
|
16
|
+
# Outputs Slot name and value
|
17
|
+
#
|
18
|
+
# @return [String] Slot name and value
|
19
|
+
def to_s
|
20
|
+
"Slot Name: #{name}, Value: #{value}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'alexa_ruby/request/intent_request/slot'
|
2
|
+
|
3
|
+
module AlexaRuby
|
4
|
+
# IntentRequest class implements Alexa "IntentRequest" request type
|
5
|
+
class IntentRequest < Request
|
6
|
+
attr_accessor :intent, :name, :slots
|
7
|
+
|
8
|
+
# Initialize new Intent request
|
9
|
+
#
|
10
|
+
# @param json [JSON] valid JSON request from Amazon
|
11
|
+
def initialize(json)
|
12
|
+
@intent = json[:request][:intent]
|
13
|
+
if @intent.nil?
|
14
|
+
raise ArgumentError, 'Intent should exist on an IntentRequest'
|
15
|
+
end
|
16
|
+
|
17
|
+
@type = :intent
|
18
|
+
@name = @intent[:name]
|
19
|
+
@slots = @intent[:slots]
|
20
|
+
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
# Takes a Hash object with slots and add it to slots node
|
25
|
+
#
|
26
|
+
# @param slots [Hash] hash with slots data
|
27
|
+
def add_hash_slots(slots)
|
28
|
+
raise ArgumentError, 'Slots can\'t be empty' if slots.nil?
|
29
|
+
slots.each do |slot|
|
30
|
+
@slots[:slot[:name]] = Slot.new(slot[:name], slot[:value])
|
31
|
+
end
|
32
|
+
@slots
|
33
|
+
end
|
34
|
+
|
35
|
+
# Takes a JSON with slots and add it to slots node
|
36
|
+
#
|
37
|
+
# @param slots [JSON] json object with slots data
|
38
|
+
def add_slots(slots)
|
39
|
+
slot_hash = Oj.load(slots, symbol_keys: true)
|
40
|
+
add_hash_slots(slot_hash)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Adds a slot from a name and a value
|
44
|
+
#
|
45
|
+
# @param name [String] slot name
|
46
|
+
# @param value [String] slot value
|
47
|
+
# @return [Object] new slot object
|
48
|
+
def add_slot(name, value)
|
49
|
+
slot = Slot.new(name, value)
|
50
|
+
@slots[:name] = slot
|
51
|
+
slot
|
52
|
+
end
|
53
|
+
|
54
|
+
# Outputs the Intent Name, request Id and slot information
|
55
|
+
#
|
56
|
+
# @return [String] Intent Name, request Id and slot information
|
57
|
+
def to_s
|
58
|
+
"IntentRequest: #{name} requestID: #{request_id} Slots: #{slots}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module AlexaRuby
|
2
|
+
# LaunchRequest class implements Alexa "LaunchRequest" request type
|
3
|
+
class LaunchRequest < Request
|
4
|
+
# Initialize new launch request
|
5
|
+
#
|
6
|
+
# @param json [JSON] valid JSON request from Amazon
|
7
|
+
def initialize(json)
|
8
|
+
@type = :launch
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
# Outputs the launch requestID
|
13
|
+
#
|
14
|
+
# @return [String] launch request ID
|
15
|
+
def to_s
|
16
|
+
"LaunchRequest requestID: #{request_id}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Session end request class.
|
2
|
+
module AlexaRuby
|
3
|
+
# Class for Amazon "SessionEndedRequest" request type
|
4
|
+
class SessionEndedRequest < Request
|
5
|
+
attr_accessor :reason
|
6
|
+
|
7
|
+
# Initialize new SessionEndedRequest
|
8
|
+
#
|
9
|
+
# @param json [JSON] valid JSON request from Amazon
|
10
|
+
def initialize(json)
|
11
|
+
@type = :session_ended
|
12
|
+
@reason = json[:request][:reason]
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
# Ouputs the request_id and the reason of session end
|
17
|
+
#
|
18
|
+
# @return [String] request_id and the reason of session end
|
19
|
+
def to_s
|
20
|
+
"Session Ended for requestID: #{request_id} with reason #{reason}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/alexa_ruby/request.rb
CHANGED
@@ -1,47 +1,28 @@
|
|
1
|
+
require 'alexa_ruby/session'
|
2
|
+
|
3
|
+
# AlexaRuby implements a back-end service for interaction with Amazon Alexa API
|
1
4
|
module AlexaRuby
|
2
|
-
#
|
3
|
-
# - LaunchRequest: The start of the app.
|
4
|
-
# - IntentRequest: The intent of the app.
|
5
|
-
# - SessionEndedRequest: Session has ended.
|
5
|
+
# Abstract request class
|
6
6
|
class Request
|
7
|
-
require 'json'
|
8
|
-
require 'alexa_ruby/session'
|
9
7
|
attr_accessor :version, :type, :session, :json # global
|
10
8
|
attr_accessor :request_id, :locale # on request
|
11
9
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
@
|
17
|
-
@
|
10
|
+
# Initialize new request object and set object parameters
|
11
|
+
#
|
12
|
+
# @param json [JSON] valid JSON request from Amazon
|
13
|
+
def initialize(json)
|
14
|
+
@request_id = json[:request][:requestId]
|
15
|
+
if @request_id.nil?
|
16
|
+
raise ArgumentError, 'Request ID should exist on all Requests'
|
17
|
+
end
|
18
|
+
@version = json[:version]
|
19
|
+
@locale = json[:request][:locale]
|
20
|
+
@json = json
|
18
21
|
|
19
|
-
|
20
|
-
@session = AlexaRuby::Session.new(json_request['session'])
|
21
|
-
end
|
22
|
-
end
|
22
|
+
return if @type == :audio_player
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
raise ArgumentError, 'Invalid Alexa Request.' unless AlexaRuby.valid_alexa?(json_request)
|
27
|
-
@request = nil
|
28
|
-
case json_request['request']['type']
|
29
|
-
when /Launch/
|
30
|
-
@request = LaunchRequest.new(json_request)
|
31
|
-
when /Intent/
|
32
|
-
@request = IntentRequest.new(json_request)
|
33
|
-
when /SessionEnded/
|
34
|
-
@request = SessionEndedRequest.new(json_request)
|
35
|
-
else
|
36
|
-
raise ArgumentError, 'Invalid Request Type.'
|
24
|
+
# TODO: We probably need better session handling
|
25
|
+
@session = AlexaRuby::Session.new(json[:session])
|
37
26
|
end
|
38
|
-
@request
|
39
|
-
end
|
40
|
-
|
41
|
-
# Take keys of hash and transform those to a symbols
|
42
|
-
def self.transform_keys_to_symbols(value)
|
43
|
-
return value if not value.is_a?(Hash)
|
44
|
-
hash = value.inject({}){|memo,(k,v)| memo[k.to_sym] = self.transform_keys_to_symbols(v); memo}
|
45
|
-
return hash
|
46
27
|
end
|
47
28
|
end
|
@@ -1,33 +1,51 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'uri'
|
3
|
+
|
1
4
|
module AlexaRuby
|
2
5
|
# AudioPlayer class encapsulates all Alexa audio player directives
|
3
6
|
class AudioPlayer
|
7
|
+
attr_accessor :directive
|
8
|
+
|
9
|
+
# Initialize new directive object
|
10
|
+
def initialize
|
11
|
+
@directive = {}
|
12
|
+
end
|
13
|
+
|
4
14
|
# Build an AudioPlayer.Play directive
|
5
15
|
#
|
6
|
-
# @param
|
7
|
-
#
|
8
|
-
#
|
16
|
+
# @param opts [Hash] optional request parameters:
|
17
|
+
# - streaming URL
|
18
|
+
# - radio station token
|
19
|
+
# - expected previous station token
|
20
|
+
# - playback offset
|
9
21
|
# @return [Hash] AudioPlayer.Play directive
|
10
|
-
def
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
}
|
21
|
-
}
|
22
|
+
def build_play_directive(opts)
|
23
|
+
raise ArgumentError, 'Invalid streaming URL' unless valid_url?(opts[:url])
|
24
|
+
token = opts[:token] || SecureRandom.uuid
|
25
|
+
offset = opts[:offset] || 0
|
26
|
+
@directive[:type] = 'AudioPlayer.Play'
|
27
|
+
@directive[:playBehavior] = 'REPLACE_ALL'
|
28
|
+
@directive[:audioItem] = { stream: {} }
|
29
|
+
@directive[:audioItem][:stream][:url] = opts[:url]
|
30
|
+
@directive[:audioItem][:stream][:token] = token
|
31
|
+
@directive[:audioItem][:stream][:offsetInMilliseconds] = offset
|
22
32
|
end
|
23
33
|
|
24
34
|
# Build AudioPlayer.Stop directive
|
25
35
|
#
|
26
36
|
# @return [Hash] AudioPlayer.Stop directive
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
37
|
+
def build_stop_directive
|
38
|
+
@directive[:type] = 'AudioPlayer.Stop'
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# Is it a valid URL?
|
44
|
+
#
|
45
|
+
# @param url [String] streaming URL
|
46
|
+
# @return [Boolean]
|
47
|
+
def valid_url?(url)
|
48
|
+
url =~ /\A#{URI.regexp(['https'])}\z/
|
31
49
|
end
|
32
50
|
end
|
33
51
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module AlexaRuby
|
2
|
+
# Class for Amazon Alexa app cards
|
3
|
+
class Card
|
4
|
+
attr_accessor :obj
|
5
|
+
|
6
|
+
# Initialize new card object
|
7
|
+
def initialize
|
8
|
+
@obj = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
# Build card object
|
12
|
+
#
|
13
|
+
# @param opts [Hash] card parameters:
|
14
|
+
# - type - card type
|
15
|
+
# - title - card title
|
16
|
+
# - subtitle - card subtitle
|
17
|
+
# - content - card body content
|
18
|
+
def build(opts)
|
19
|
+
@obj[:type] = opts[:type]
|
20
|
+
@obj[:title] = opts[:title] unless opts[:title].nil?
|
21
|
+
@obj[:subtitle] = opts[:subtitle] unless opts[:subtitle].nil?
|
22
|
+
@obj[:content] = opts[:content] unless opts[:content].nil?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/alexa_ruby/response.rb
CHANGED
@@ -1,101 +1,132 @@
|
|
1
|
+
require 'alexa_ruby/response/audio_player'
|
2
|
+
require 'alexa_ruby/response/card'
|
3
|
+
|
1
4
|
module AlexaRuby
|
5
|
+
# Class implements response for Amazon Alexa API
|
2
6
|
class Response
|
3
|
-
|
4
|
-
|
5
|
-
attr_accessor :version, :session, :response_object, :session_attributes, :speech, :reprompt, :response, :card
|
7
|
+
attr_accessor :version, :session, :response_object, :session_attributes,
|
8
|
+
:speech, :reprompt, :response, :card
|
6
9
|
|
7
|
-
#
|
8
|
-
#
|
10
|
+
# Initialize new response and set response version
|
11
|
+
#
|
12
|
+
# @param version [String] response version
|
9
13
|
def initialize(version = '1.0')
|
10
|
-
@session_attributes =
|
14
|
+
@session_attributes = {}
|
11
15
|
@version = version
|
12
16
|
@directives = []
|
13
17
|
end
|
14
18
|
|
15
|
-
# Adds a key
|
19
|
+
# Adds a key => value pair to the session object
|
20
|
+
#
|
21
|
+
# @param key [String] attribute key
|
22
|
+
# @param value [String] attribute value
|
16
23
|
def add_session_attribute(key, value)
|
17
24
|
@session_attributes[key.to_sym] = value
|
18
25
|
end
|
19
26
|
|
27
|
+
# Add output speech to response object.
|
28
|
+
# Speech can be plain text or SSML text, default type is plain text
|
29
|
+
#
|
30
|
+
# @param speech_text [String] output speech
|
31
|
+
# @param ssml [Boolean] is it an SSML speech or not
|
20
32
|
def add_speech(speech_text, ssml = false)
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
33
|
+
@speech =
|
34
|
+
if ssml
|
35
|
+
{ type: 'SSML', ssml: check_ssml(speech_text) }
|
36
|
+
else
|
37
|
+
{ type: 'PlainText', text: speech_text }
|
38
|
+
end
|
26
39
|
@speech
|
27
40
|
end
|
28
41
|
|
29
|
-
# Add
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
# Add directive to stop audio playback
|
36
|
-
def stop_audio_playback
|
42
|
+
# Add AudioPlayer directive
|
43
|
+
#
|
44
|
+
# @param directive [String] audio player directive type,
|
45
|
+
# can be :start or :stop
|
46
|
+
# @param opts [Hash] optional request parameters
|
47
|
+
def add_audio_player_directive(directive, opts = {})
|
37
48
|
player = AudioPlayer.new
|
38
|
-
@directives = [
|
49
|
+
@directives = []
|
50
|
+
case directive.to_sym
|
51
|
+
when :start
|
52
|
+
player.build_play_directive(opts)
|
53
|
+
when :stop
|
54
|
+
player.build_stop_directive
|
55
|
+
end
|
56
|
+
@directives << player.directive
|
39
57
|
end
|
40
58
|
|
59
|
+
# Add reprompt to outputSpeech node
|
60
|
+
#
|
61
|
+
# @param speech_text [String] output speech
|
62
|
+
# @param ssml [Boolean] is it an SSML speech or not
|
41
63
|
def add_reprompt(speech_text, ssml = false)
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
64
|
+
@reprompt =
|
65
|
+
if ssml
|
66
|
+
{ outputSpeech: { type: 'SSML', ssml: check_ssml(speech_text) } }
|
67
|
+
else
|
68
|
+
{ outputSpeech: { type: 'PlainText', text: speech_text } }
|
69
|
+
end
|
47
70
|
@reprompt
|
48
71
|
end
|
49
72
|
|
73
|
+
# Add card that will be shown in Amazon Alexa app on user smartphone/tablet
|
50
74
|
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
@card =
|
58
|
-
@card[:type] = type || 'Simple'
|
59
|
-
@card[:title] = title unless title.nil?
|
60
|
-
@card[:subtitle] = subtitle unless subtitle.nil?
|
61
|
-
@card[:content] = content unless content.nil?
|
62
|
-
@card
|
75
|
+
# @param opts [Hash] hash with card parameters
|
76
|
+
# @return [Hash] card object
|
77
|
+
def add_card(opts = {})
|
78
|
+
opts[:type] = 'Simple' if opts[:type].nil?
|
79
|
+
card = Card.new
|
80
|
+
card.build(opts)
|
81
|
+
@card = card.obj
|
63
82
|
end
|
64
83
|
|
65
|
-
#
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
71
|
-
|
72
|
-
# Adds a speech to the object, also returns a outputspeech object.
|
84
|
+
# Adds a speech to the object, also returns a outputspeech object
|
85
|
+
#
|
86
|
+
# @param speech [String] output speech
|
87
|
+
# @param end_session [Boolean] is it a final response, or not
|
88
|
+
# @param ssml [Boolean] is it an SSML speech or not
|
73
89
|
def say_response(speech, end_session = true, ssml = false)
|
74
|
-
output_speech = add_speech(speech,ssml)
|
75
|
-
{ :
|
90
|
+
output_speech = add_speech(speech, ssml)
|
91
|
+
{ outputSpeech: output_speech, shouldEndSession: end_session }
|
76
92
|
end
|
77
93
|
|
78
94
|
# Incorporates reprompt in the SDK 2015-05
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
95
|
+
#
|
96
|
+
# @param speech [String] output speech
|
97
|
+
# @param reprompt_speech [String] output repromt speech
|
98
|
+
# @param end_session [Boolean] is it a final response, or not
|
99
|
+
# @param speech_ssml [Boolean] is it an SSML speech or not
|
100
|
+
# @param reprompt_ssml [Boolean] is it an SSML repromt speech or not
|
101
|
+
def say_response_with_reprompt(speech, reprompt_speech, end_session = true,
|
102
|
+
speech_ssml = false, reprompt_ssml = false)
|
103
|
+
output_speech = add_speech(speech, speech_ssml)
|
104
|
+
reprompt_speech = add_reprompt(reprompt_speech, reprompt_ssml)
|
105
|
+
{
|
106
|
+
outputSpeech: output_speech,
|
107
|
+
reprompt: reprompt_speech,
|
108
|
+
shouldEndSession: end_session
|
109
|
+
}
|
83
110
|
end
|
84
111
|
|
85
|
-
|
86
112
|
# Creates a session object. We pretty much only use this in testing.
|
113
|
+
# If it's empty assume user doesn't need session attributes
|
114
|
+
#
|
115
|
+
# @return [Hash] user session parameters
|
87
116
|
def build_session
|
88
|
-
|
89
|
-
@
|
90
|
-
@session = { :sessionAttributes => @session_attributes }
|
117
|
+
@session_attributes = {} if @session_attributes.nil?
|
118
|
+
@session = { sessionAttributes: @session_attributes }
|
91
119
|
@session
|
92
120
|
end
|
93
121
|
|
94
122
|
# The response object (with outputspeech, cards and session end)
|
95
123
|
# Should rename this, but Amazon picked their names.
|
96
124
|
# The only mandatory field is end_session which we default to true.
|
125
|
+
#
|
126
|
+
# @param session_end [Boolean] is it a final response, or not
|
127
|
+
# @return [Hash] response parameters
|
97
128
|
def build_response_object(session_end = true)
|
98
|
-
@response =
|
129
|
+
@response = {}
|
99
130
|
@response[:outputSpeech] = @speech unless @speech.nil?
|
100
131
|
@response[:directives] = @directives unless @directives.empty?
|
101
132
|
@response[:card] = @card unless @card.nil?
|
@@ -105,26 +136,41 @@ module AlexaRuby
|
|
105
136
|
end
|
106
137
|
|
107
138
|
# Builds a response.
|
108
|
-
# Takes the version, response
|
139
|
+
# Takes the version, response, should_end_session variables
|
140
|
+
# and builds a JSON object
|
141
|
+
#
|
142
|
+
# @param session_end [Boolean] is it a final response, or not
|
143
|
+
# @return [JSON] json response for Amazon Alexa
|
109
144
|
def build_response(session_end = true)
|
110
145
|
response_object = build_response_object(session_end)
|
111
|
-
response =
|
146
|
+
response = {}
|
112
147
|
response[:version] = @version
|
113
|
-
|
148
|
+
unless @session_attributes.empty?
|
149
|
+
response[:sessionAttributes] = @session_attributes
|
150
|
+
end
|
114
151
|
response[:response] = response_object
|
115
|
-
|
152
|
+
Oj.to_json(response)
|
116
153
|
end
|
117
154
|
|
118
155
|
# Outputs the version, session object and the response object.
|
156
|
+
#
|
157
|
+
# @return [String] version, session object and the response object
|
119
158
|
def to_s
|
120
|
-
"Version => #{@version}, SessionObj => #{@session},
|
159
|
+
"Version => #{@version}, SessionObj => #{@session}, " \
|
160
|
+
"Response => #{@response}"
|
121
161
|
end
|
122
162
|
|
123
163
|
private
|
124
164
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
165
|
+
# Check and fix SSML speech
|
166
|
+
#
|
167
|
+
# @param ssml [String] SSML string
|
168
|
+
# @return [String] valid SSML string
|
169
|
+
def check_ssml(ssml)
|
170
|
+
open_tag = ssml.strip[0..6]
|
171
|
+
close_tag = ssml.strip[-8..1]
|
172
|
+
ssml = open_tag == '<speak>' ? ssml : "<speak>#{ssml}"
|
173
|
+
close_tag == '</speak>' ? ssml : "#{ssml}</speak>"
|
174
|
+
end
|
129
175
|
end
|
130
176
|
end
|
data/lib/alexa_ruby/session.rb
CHANGED
@@ -1,36 +1,54 @@
|
|
1
1
|
module AlexaRuby
|
2
|
-
#
|
2
|
+
# Class handles the session object in request
|
3
3
|
class Session
|
4
4
|
attr_accessor :new, :session_id, :attributes, :user
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
|
6
|
+
# Initialize new user session
|
7
|
+
#
|
8
|
+
# @param session [JSON] part of json request from Amazon with user session
|
9
|
+
def initialize(session)
|
10
|
+
if session.nil? || session[:new].nil? || session[:sessionId].nil?
|
11
|
+
raise ArgumentError, 'Invalid Session'
|
12
|
+
end
|
13
|
+
|
14
|
+
@new = session[:new]
|
15
|
+
@session_id = session[:sessionId]
|
16
|
+
@attributes = session[:attributes].nil? ? {} : session[:attributes]
|
17
|
+
@user = session[:user]
|
11
18
|
end
|
12
19
|
|
13
|
-
#
|
20
|
+
# Is it a new user session?
|
21
|
+
#
|
22
|
+
# @return [Boolean]
|
14
23
|
def new?
|
15
|
-
|
24
|
+
@new
|
16
25
|
end
|
17
26
|
|
18
|
-
#
|
27
|
+
# Is user defined?
|
28
|
+
#
|
29
|
+
# @return [Boolean]
|
19
30
|
def user_defined?
|
20
|
-
!@user.nil? || !@user[
|
31
|
+
!@user.nil? || !@user[:userId].nil?
|
21
32
|
end
|
22
33
|
|
23
|
-
#
|
34
|
+
# Get user ID
|
35
|
+
#
|
36
|
+
# @return [String] Amazon user ID
|
24
37
|
def user_id
|
25
|
-
@user[
|
38
|
+
@user[:userId] if @user
|
26
39
|
end
|
27
40
|
|
41
|
+
# Get user access token
|
42
|
+
#
|
43
|
+
# @return [String] Amazon user access token
|
28
44
|
def access_token
|
29
|
-
@user[
|
45
|
+
@user[:accessToken] if @user
|
30
46
|
end
|
31
47
|
|
32
|
-
#
|
33
|
-
|
48
|
+
# Session attributes present?
|
49
|
+
#
|
50
|
+
# @return [Boolean]
|
51
|
+
def attributes?
|
34
52
|
!@attributes.empty?
|
35
53
|
end
|
36
54
|
end
|
data/lib/alexa_ruby/version.rb
CHANGED
data/lib/alexa_ruby.rb
CHANGED
@@ -1,24 +1,85 @@
|
|
1
|
+
require 'oj'
|
2
|
+
|
1
3
|
require 'alexa_ruby/request'
|
2
4
|
require 'alexa_ruby/version'
|
3
5
|
require 'alexa_ruby/response'
|
4
|
-
require 'alexa_ruby/intent_request'
|
5
|
-
require 'alexa_ruby/launch_request'
|
6
|
-
require 'alexa_ruby/session_ended_request'
|
6
|
+
require 'alexa_ruby/request/intent_request'
|
7
|
+
require 'alexa_ruby/request/launch_request'
|
8
|
+
require 'alexa_ruby/request/session_ended_request'
|
9
|
+
require 'alexa_ruby/request/audio_player_request'
|
7
10
|
|
11
|
+
# AlexaRuby implements a back-end service for interaction with Amazon Alexa API
|
8
12
|
module AlexaRuby
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
+
class << self
|
14
|
+
# Prints some JSON
|
15
|
+
#
|
16
|
+
# @param json [JSON] some JSON object
|
17
|
+
def print_json(json)
|
18
|
+
p json
|
19
|
+
end
|
13
20
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
21
|
+
# Prints the Gem version
|
22
|
+
def print_version
|
23
|
+
p AlexaRuby::VERSION
|
24
|
+
end
|
25
|
+
|
26
|
+
# Builds a new request for Alexa
|
27
|
+
#
|
28
|
+
# @param json_request [JSON] json request from Amazon Alexa
|
29
|
+
# @return [Object] instance of request class
|
30
|
+
def build_request(json_request)
|
31
|
+
json = load_json(json_request)
|
32
|
+
|
33
|
+
unless AlexaRuby.valid_request?(json)
|
34
|
+
raise ArgumentError, 'Invalid Alexa Request'
|
35
|
+
end
|
36
|
+
|
37
|
+
@request = define_request(json)
|
38
|
+
raise ArgumentError, 'Invalid Request Type' if @request.nil?
|
39
|
+
@request
|
40
|
+
end
|
41
|
+
|
42
|
+
# Check if it is a valid Amazon Alexa request
|
43
|
+
#
|
44
|
+
# @param json [JSON] json request from Amazon Alexa
|
45
|
+
# @return [Boolean]
|
46
|
+
def valid_request?(json)
|
47
|
+
session =
|
48
|
+
if json[:request].nil? || /AudioPlayer/.match(json[:request][:type])
|
49
|
+
true
|
50
|
+
else
|
51
|
+
!json[:session].nil?
|
52
|
+
end
|
53
|
+
!json.nil? && session && !json[:version].nil? && !json[:request].nil?
|
54
|
+
end
|
55
|
+
|
56
|
+
# Define proper request class and instantinate it
|
57
|
+
#
|
58
|
+
# @param json [JSON] json request from Amazon Alexa
|
59
|
+
# @return [Object] instance of request class
|
60
|
+
def define_request(json)
|
61
|
+
case json[:request][:type]
|
62
|
+
when /Launch/
|
63
|
+
LaunchRequest.new(json)
|
64
|
+
when /Intent/
|
65
|
+
IntentRequest.new(json)
|
66
|
+
when /SessionEnded/
|
67
|
+
SessionEndedRequest.new(json)
|
68
|
+
when /AudioPlayer/
|
69
|
+
AudioPlayerRequest.new(json)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
18
74
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
75
|
+
# Parse JSON request, validate it and convert it to hash
|
76
|
+
#
|
77
|
+
# @param request [String] json request from Amazon Alexa
|
78
|
+
# @return [Hash] loaded and validated request
|
79
|
+
def load_json(request)
|
80
|
+
Oj.load(request, symbol_keys: true)
|
81
|
+
rescue StandardError
|
82
|
+
raise ArgumentError, 'Invalid JSON in request'
|
83
|
+
end
|
23
84
|
end
|
24
85
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: alexa_ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Mulev
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: oj
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: rspec
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -91,13 +105,16 @@ files:
|
|
91
105
|
- README.md
|
92
106
|
- Rakefile
|
93
107
|
- lib/alexa_ruby.rb
|
94
|
-
- lib/alexa_ruby/intent_request.rb
|
95
|
-
- lib/alexa_ruby/launch_request.rb
|
96
108
|
- lib/alexa_ruby/request.rb
|
109
|
+
- lib/alexa_ruby/request/audio_player_request.rb
|
110
|
+
- lib/alexa_ruby/request/intent_request.rb
|
111
|
+
- lib/alexa_ruby/request/intent_request/slot.rb
|
112
|
+
- lib/alexa_ruby/request/launch_request.rb
|
113
|
+
- lib/alexa_ruby/request/session_ended_request.rb
|
97
114
|
- lib/alexa_ruby/response.rb
|
98
115
|
- lib/alexa_ruby/response/audio_player.rb
|
116
|
+
- lib/alexa_ruby/response/card.rb
|
99
117
|
- lib/alexa_ruby/session.rb
|
100
|
-
- lib/alexa_ruby/session_ended_request.rb
|
101
118
|
- lib/alexa_ruby/version.rb
|
102
119
|
homepage: https://github.com/mulev/alexa-ruby
|
103
120
|
licenses:
|
@@ -1,60 +0,0 @@
|
|
1
|
-
module AlexaRuby
|
2
|
-
class IntentRequest < Request
|
3
|
-
attr_accessor :intent, :name, :slots
|
4
|
-
|
5
|
-
# We still don't know if all of the parameters in the request are required.
|
6
|
-
# Checking for the presence of intent on an IntentRequest.
|
7
|
-
def initialize(json_request)
|
8
|
-
super
|
9
|
-
@intent = json_request['request']['intent']
|
10
|
-
raise ArgumentError, 'Intent should exist on an IntentRequest' if @intent.nil?
|
11
|
-
@type = 'INTENT_REQUEST'
|
12
|
-
@name = @intent['name']
|
13
|
-
@slots = @intent['slots']
|
14
|
-
end
|
15
|
-
|
16
|
-
# Takes a Hash object.
|
17
|
-
def add_hash_slots(slots)
|
18
|
-
raise ArgumentError, 'Slots can\'t be empty'
|
19
|
-
slots.each do |slot|
|
20
|
-
@slots[:slot[:name]] = Slot.new(slot[:name], slot[:value])
|
21
|
-
end
|
22
|
-
@slots
|
23
|
-
end
|
24
|
-
|
25
|
-
# Takes a JSON Object and symbolizes its keys.
|
26
|
-
def add_slots(slots)
|
27
|
-
slot_hash = AlexaRuby.transform_keys_to_symbols(value)
|
28
|
-
add_hash_slots(slot_hash)
|
29
|
-
end
|
30
|
-
|
31
|
-
# Adds a slot from a name and a value.
|
32
|
-
def add_slot(name, value)
|
33
|
-
slot = Slot.new(name, value)
|
34
|
-
@slots[:name] = slot
|
35
|
-
slot
|
36
|
-
end
|
37
|
-
|
38
|
-
# Outputs the Intent Name, request Id and slot information.
|
39
|
-
def to_s
|
40
|
-
"IntentRequest: #{name} requestID: #{request_id} Slots: #{slots}"
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
# Class that encapsulates each slot.
|
45
|
-
class Slot
|
46
|
-
attr_accessor :name, :value
|
47
|
-
|
48
|
-
# Each slot has a name and a value.
|
49
|
-
def initialize(name, value)
|
50
|
-
raise ArgumentError, 'Need a name and a value' if name.nil? || value.nil?
|
51
|
-
@name = name
|
52
|
-
@value = value
|
53
|
-
end
|
54
|
-
|
55
|
-
# Outputs Slot name and value.
|
56
|
-
def to_s
|
57
|
-
"Slot Name: #{name}, Value: #{value}"
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
module AlexaRuby
|
2
|
-
class LaunchRequest < Request
|
3
|
-
# We still don't know if all of the parameters in the request are required.
|
4
|
-
# Checking for the presence of intent on an IntentRequest.
|
5
|
-
def initialize(json_request)
|
6
|
-
super
|
7
|
-
@type = 'LAUNCH_REQUEST'
|
8
|
-
end
|
9
|
-
|
10
|
-
# Outputs the launch requestID.
|
11
|
-
def to_s
|
12
|
-
"LaunchRequest requestID: #{request_id}"
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
# Session end request class.
|
2
|
-
module AlexaRuby
|
3
|
-
class SessionEndedRequest < Request
|
4
|
-
attr_accessor :reason
|
5
|
-
|
6
|
-
# TODO: Validate the reason.
|
7
|
-
# We still don't know if all of the parameters in the request are required.
|
8
|
-
# Checking for the presence of intent on an IntentRequest.
|
9
|
-
def initialize(json_request)
|
10
|
-
super
|
11
|
-
@type = 'SESSION_ENDED_REQUEST'
|
12
|
-
@reason = json_request['request']['reason']
|
13
|
-
end
|
14
|
-
|
15
|
-
# Ouputs the request_id and the reason why.
|
16
|
-
def to_s
|
17
|
-
"Session Ended for requestID: #{request_id} with reason #{reason}"
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|