alexa_ruby 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/alexa_ruby.svg)](https://badge.fury.io/rb/alexa_ruby)
|
3
4
|
[![Build Status](https://travis-ci.org/mulev/alexa-ruby.svg?branch=master)](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
|