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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 93ed501db9265d681f6b82b67eefff6879c60cdd
4
- data.tar.gz: 62dcf0154f9e5dc67d091f45d0bff9ccef19ebfa
3
+ metadata.gz: 05efe37b6e97b7278bbfe995867983640c07b90d
4
+ data.tar.gz: '08e039b53252f7a87961e7d21cd02a45a2917c64'
5
5
  SHA512:
6
- metadata.gz: d771952eb751937874abe1f2143b0e3d029ebd19b0c058fd91e23fadc70f0bf61673ba2ed330ef7dee57fd2dd63202349cbfb925ffeb86b95697946b1a6668b2
7
- data.tar.gz: '0082c8ee544e27754be23a0132e7ddeb42924840e19684182891d6094325bb56c1300624d418d876d2fbb9fc86279a4fd6e9bdb4facea0daa2cf73207b5ec703'
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
@@ -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
- # Echo can send 3 types of requests
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
- def initialize(json_request)
13
- @request_id = json_request['request']['requestId']
14
- raise ArgumentError, 'Request ID should exist on all Requests' if @request_id.nil?
15
- @version = json_request['version']
16
- @locale = json_request['request']['locale']
17
- @json = json_request
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
- # TODO: We probably need better session handling.
20
- @session = AlexaRuby::Session.new(json_request['session'])
21
- end
22
- end
22
+ return if @type == :audio_player
23
23
 
24
- # Builds a new request for Alexa.
25
- def self.build_request(json_request)
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 url [String] audio stream URL
7
- # @param token [String] some token that represents the audio stream
8
- # @param offset [Integer] the timestamp in the stream from which Alexa should begin playback
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 play_directive(url, token, offset)
11
- {
12
- type: 'AudioPlayer.Play',
13
- playBehavior: 'REPLACE_ALL',
14
- audioItem: {
15
- stream: {
16
- token: token,
17
- url: url,
18
- offsetInMilliseconds: offset
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 stop_directive
28
- {
29
- type: 'AudioPlayer.Stop'
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
@@ -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
- require 'json'
4
- require 'alexa_ruby/response/audio_player'
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
- # Every response needs a shouldendsession and a version attribute
8
- # We initialize version to 1.0, use add_version to set your own.
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 = Hash.new
14
+ @session_attributes = {}
11
15
  @version = version
12
16
  @directives = []
13
17
  end
14
18
 
15
- # Adds a key,value pair to the session object.
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
- if ssml
22
- @speech = { :type => 'SSML', :ssml => check_ssml(speech_text) }
23
- else
24
- @speech = { :type => 'PlainText', :text => speech_text }
25
- end
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 a directive to start audio playback
30
- def start_audio_playback(url, token = '', offset = 0)
31
- player = AudioPlayer.new
32
- @directives = [ player.play_directive(url, token, offset) ]
33
- end
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 = [ player.stop_directive ]
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
- if ssml
43
- @reprompt = { "outputSpeech" => { :type => 'SSML', :ssml => check_ssml(speech_text) } }
44
- else
45
- @reprompt = { "outputSpeech" => { :type => 'PlainText', :text => speech_text } }
46
- end
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
- #"type": "string",
52
- # "title": "string",
53
- # "subtitle": "string",
54
- # "content": "string"
55
- def add_card(type = nil, title = nil , subtitle = nil, content = nil)
56
- # A Card must have a type which the default is Simple.
57
- @card = Hash.new()
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
- # The JSON Spec says order shouldn't matter.
66
- def add_hash_card(card)
67
- card[:type] = 'Simple' if card[:type].nil?
68
- @card = card
69
- @card
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
- { :outputSpeech => output_speech, :shouldEndSession => end_session }
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
- def say_response_with_reprompt(speech, reprompt_speech, end_session = true, speech_ssml = false, reprompt_ssml = false)
80
- output_speech = add_speech(speech,speech_ssml)
81
- reprompt_speech = add_reprompt(reprompt_speech,reprompt_ssml)
82
- { :outputSpeech => output_speech, :reprompt => reprompt_speech, :shouldEndSession => end_session }
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
- # If it's empty assume user doesn't need session attributes.
89
- @session_attributes = Hash.new if @session_attributes.nil?
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 = Hash.new
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 and should_end_session variables and builds a JSON object.
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 = Hash.new
146
+ response = {}
112
147
  response[:version] = @version
113
- response[:sessionAttributes] = @session_attributes unless @session_attributes.empty?
148
+ unless @session_attributes.empty?
149
+ response[:sessionAttributes] = @session_attributes
150
+ end
114
151
  response[:response] = response_object
115
- response.to_json
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}, Response => #{@response}"
159
+ "Version => #{@version}, SessionObj => #{@session}, " \
160
+ "Response => #{@response}"
121
161
  end
122
162
 
123
163
  private
124
164
 
125
- def check_ssml(ssml_string)
126
- ssml_string = ssml_string.strip[0..6] == "<speak>" ? ssml_string : "<speak>" + ssml_string
127
- ssml_string.strip[-8..1] == "</speak>" ? ssml_string : ssml_string + "</speak>"
128
- end
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
@@ -1,36 +1,54 @@
1
1
  module AlexaRuby
2
- # Handles the session object in request.
2
+ # Class handles the session object in request
3
3
  class Session
4
4
  attr_accessor :new, :session_id, :attributes, :user
5
- def initialize (session)
6
- raise ArgumentError, 'Invalid Session' if session.nil? || session['new'].nil? || session['sessionId'].nil?
7
- @new = session['new']
8
- @session_id = session['sessionId']
9
- session['attributes'].nil? ? @attributes = Hash.new : @attributes = session['attributes']
10
- @user = session['user']
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
- # Returns whether this is a new session or not.
20
+ # Is it a new user session?
21
+ #
22
+ # @return [Boolean]
14
23
  def new?
15
- !!@new
24
+ @new
16
25
  end
17
26
 
18
- # Returns true if a user is defined.
27
+ # Is user defined?
28
+ #
29
+ # @return [Boolean]
19
30
  def user_defined?
20
- !@user.nil? || !@user['userId'].nil?
31
+ !@user.nil? || !@user[:userId].nil?
21
32
  end
22
33
 
23
- # Returns the user_id.
34
+ # Get user ID
35
+ #
36
+ # @return [String] Amazon user ID
24
37
  def user_id
25
- @user['userId'] if @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['accessToken'] if @user
45
+ @user[:accessToken] if @user
30
46
  end
31
47
 
32
- # Check to see if attributes are present.
33
- def has_attributes?
48
+ # Session attributes present?
49
+ #
50
+ # @return [Boolean]
51
+ def attributes?
34
52
  !@attributes.empty?
35
53
  end
36
54
  end
@@ -1,3 +1,3 @@
1
1
  module AlexaRuby
2
- VERSION = '0.1.0'.freeze
2
+ VERSION = '0.2.0'.freeze
3
3
  end
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
- # Prints a JSON object.
10
- def self.print_json(json)
11
- p json
12
- end
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
- # Prints the Gem version.
15
- def self.print_version
16
- p AlexaRuby::VERSION
17
- end
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
- # Returns true if all the Alexa request objects are set.
20
- def self.valid_alexa?(request_json)
21
- !request_json.nil? && !request_json['session'].nil? &&
22
- !request_json['version'].nil? && !request_json['request'].nil?
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.1.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