alexa_ruby 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,19 +1,12 @@
1
1
  module AlexaRuby
2
2
  # LaunchRequest class implements Alexa "LaunchRequest" request type
3
- class LaunchRequest < Request
3
+ class LaunchRequest < BaseRequest
4
4
  # Initialize new launch request
5
5
  #
6
- # @param json [JSON] valid JSON request from Amazon
7
- def initialize(json)
6
+ # @param request [Hash] valid request from Amazon Alexa service
7
+ def initialize(request)
8
8
  @type = :launch
9
9
  super
10
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
11
  end
19
12
  end
@@ -1,23 +1,23 @@
1
1
  # Session end request class.
2
2
  module AlexaRuby
3
- # Class for Amazon "SessionEndedRequest" request type
4
- class SessionEndedRequest < Request
5
- attr_accessor :reason
6
-
3
+ # Amazon "SessionEndedRequest" request type
4
+ class SessionEndedRequest < BaseRequest
7
5
  # Initialize new SessionEndedRequest
8
6
  #
9
- # @param json [JSON] valid JSON request from Amazon
10
- def initialize(json)
7
+ # @param request [Hash] valid request from Amazon Alexa service
8
+ def initialize(request)
11
9
  @type = :session_ended
12
- @reason = json[:request][:reason]
13
10
  super
11
+ finalize_session
14
12
  end
15
13
 
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}"
14
+ private
15
+
16
+ # Set final session params
17
+ def finalize_session
18
+ @session.state = :ended
19
+ @session.end_reason = @req[:request][:reason]
20
+ @session.error = @req[:request][:error] unless @req[:request][:error].nil?
21
21
  end
22
22
  end
23
23
  end
@@ -1,51 +1,44 @@
1
- require 'securerandom'
2
- require 'uri'
3
-
4
1
  module AlexaRuby
5
2
  # AudioPlayer class encapsulates all Alexa audio player directives
6
3
  class AudioPlayer
7
- attr_accessor :directive
8
-
9
- # Initialize new directive object
10
- def initialize
11
- @directive = {}
12
- end
13
-
14
4
  # Build an AudioPlayer.Play directive
15
5
  #
16
- # @param opts [Hash] optional request parameters:
17
- # - streaming URL
18
- # - radio station token
19
- # - expected previous station token
20
- # - playback offset
6
+ # @param params [Hash] optional request parameters:
7
+ # url [String] streaming URL
8
+ # token [String] streaming service token
9
+ # offset [Integer] playback offset
21
10
  # @return [Hash] AudioPlayer.Play directive
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
11
+ def play_directive(params)
12
+ url = params[:url]
13
+ token = params[:token] || SecureRandom.uuid
14
+ offset = params[:offset] || 0
15
+ build_directive('AudioPlayer.Play', url, token, offset)
32
16
  end
33
17
 
34
18
  # Build AudioPlayer.Stop directive
35
19
  #
36
20
  # @return [Hash] AudioPlayer.Stop directive
37
- def build_stop_directive
38
- @directive[:type] = 'AudioPlayer.Stop'
21
+ def stop_directive
22
+ build_directive('AudioPlayer.Stop')
39
23
  end
40
24
 
41
25
  private
42
26
 
43
- # Is it a valid URL?
27
+ # Set play directive parameters
44
28
  #
45
- # @param url [String] streaming URL
46
- # @return [Boolean]
47
- def valid_url?(url)
48
- url =~ /\A#{URI.regexp(['https'])}\z/
29
+ # @param type [String] directive type, can be Play or Stop
30
+ # @param url [String] streaming service URL
31
+ # @param token [String] streaming service token
32
+ # @param offset [Integer] playback offset
33
+ def build_directive(type, url = nil, token = nil, offset = nil)
34
+ directive = { type: type }
35
+ return directive if type == 'AudioPlayer.Stop'
36
+ directive[:playBehavior] = 'REPLACE_ALL'
37
+ directive[:audioItem] = { stream: {} }
38
+ directive[:audioItem][:stream][:url] = url
39
+ directive[:audioItem][:stream][:token] = token
40
+ directive[:audioItem][:stream][:offsetInMilliseconds] = offset
41
+ directive
49
42
  end
50
43
  end
51
44
  end
@@ -1,25 +1,80 @@
1
1
  module AlexaRuby
2
2
  # Class for Amazon Alexa app cards
3
3
  class Card
4
- attr_accessor :obj
4
+ attr_reader :obj
5
5
 
6
6
  # Initialize new card object
7
- def initialize
7
+ #
8
+ # @param params [Hash] card parameters:
9
+ # type [String] card type, can be "Simple", "Standard" or "LinkAccount"
10
+ # title [String] card title
11
+ # content [String] card content (line breaks must be already included)
12
+ # small_image_url [String] an URL for small card image
13
+ # large_image_url [String] an URL for large card image
14
+ # @return [Hash] ready to use card object
15
+ # @raise [ArgumentError] if card type is unknown
16
+ def initialize(params)
8
17
  @obj = {}
18
+ @obj[:type] = params[:type] || 'Simple'
19
+ raise ArgumentError, 'Unknown card type' unless valid_type?
20
+ @params = params
21
+ build
22
+ end
23
+
24
+ private
25
+
26
+ # Check if card type is valid
27
+ #
28
+ # @return [Boolean]
29
+ def valid_type?
30
+ %w[Simple Standard LinkAccount].include? @obj[:type]
9
31
  end
10
32
 
11
33
  # Build card object
12
34
  #
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?
35
+ # @return [Hash] ready to use card object
36
+ def build
37
+ return if @obj[:type] == 'LinkAccount'
38
+ add_title
39
+ add_content unless @params[:content].nil?
40
+ return unless @obj[:type] == 'Standard'
41
+ return if @params[:small_image_url].nil? &&
42
+ @params[:large_image_url].nil?
43
+ add_images
44
+ end
45
+
46
+ # Add card title to object
47
+ #
48
+ # @raise [ArgumentError] if not title found
49
+ def add_title
50
+ raise ArgumentError, 'Card need a title' if @params[:title].nil?
51
+ @obj[:title] = @params[:title]
52
+ end
53
+
54
+ # Add content to card body.
55
+ # Content must be already prepared and contain needed line breaks.
56
+ # \r\n or \n can be used for line breaks
57
+ def add_content
58
+ type =
59
+ case @obj[:type]
60
+ when 'Simple'
61
+ :content
62
+ when 'Standard'
63
+ :text
64
+ end
65
+ @obj[type] = @params[:content]
66
+ end
67
+
68
+ # Add images to card
69
+ def add_images
70
+ @obj[:image] = {}
71
+
72
+ if @params[:small_image_url]
73
+ @obj[:image][:smallImageUrl] = @params[:small_image_url]
74
+ end
75
+
76
+ return unless @params[:large_image_url]
77
+ @obj[:image][:largeImageUrl] = @params[:large_image_url]
23
78
  end
24
79
  end
25
80
  end
@@ -1,176 +1,204 @@
1
- require 'alexa_ruby/response/audio_player'
2
- require 'alexa_ruby/response/card'
3
-
4
1
  module AlexaRuby
5
- # Class implements response for Amazon Alexa API
2
+ # Response for Amazon Alexa service request
6
3
  class Response
7
- attr_accessor :version, :session, :response_object, :session_attributes,
8
- :speech, :reprompt, :response, :card
9
-
10
- # Initialize new response and set response version
4
+ # Initialize new response
11
5
  #
12
- # @param version [String] response version
13
- def initialize(version = '1.0')
14
- @session_attributes = {}
15
- @version = version
16
- @directives = []
6
+ # @param request_type [Symbol] AlexaRuby::Request type
7
+ # @param version [String] Amazon Alexa SDK version
8
+ def initialize(request_type, version = '1.0')
9
+ @req_type = request_type
10
+ @resp = {
11
+ version: version,
12
+ sessionAttributes: {},
13
+ response: { shouldEndSession: true }
14
+ }
17
15
  end
18
16
 
19
- # Adds a key => value pair to the session object
17
+ # Add one session attribute
20
18
  #
21
- # @param key [String] attribute key
19
+ # @param key [String] atrribute key
22
20
  # @param value [String] attribute value
23
- def add_session_attribute(key, value)
24
- @session_attributes[key.to_sym] = value
21
+ # @raise [ArgumentError] if session key is already added and
22
+ # rewrite is set to false
23
+ def add_session_attribute(key, value, rewrite = false)
24
+ unless rewrite
25
+ if @resp[:sessionAttributes].key?(key)
26
+ raise ArgumentError, 'Duplicate session attributes not allowed'
27
+ end
28
+ end
29
+ @resp[:sessionAttributes][key] = value
25
30
  end
26
31
 
27
- # Add output speech to response object.
28
- # Speech can be plain text or SSML text, default type is plain text
32
+ # Add pack of session attributes and overwrite all existing ones
29
33
  #
30
- # @param speech_text [String] output speech
31
- # @param ssml [Boolean] is it an SSML speech or not
32
- def add_speech(speech_text, ssml = false)
33
- @speech =
34
- if ssml
35
- { type: 'SSML', ssml: check_ssml(speech_text) }
36
- else
37
- { type: 'PlainText', text: speech_text }
38
- end
39
- @speech
34
+ # @param attributes [Hash] pack of session attributes
35
+ # @raise [ArgumentError] if given paramter is not a Hash object
36
+ def add_session_attributes(attributes)
37
+ unless attributes.is_a? Hash
38
+ raise ArgumentError, 'Attributes must be a Hash'
39
+ end
40
+ session_attributes(attributes, false)
41
+ end
42
+
43
+ # Add pack of session attributes to existing ones
44
+ #
45
+ # @param attributes [Hash] pack of session attributes
46
+ # @raise [ArgumentError] if given paramter is not a Hash object
47
+ def merge_session_attributes(attributes)
48
+ unless attributes.is_a? Hash
49
+ raise ArgumentError, 'Attributes must be a Hash'
50
+ end
51
+ session_attributes(attributes, true)
52
+ end
53
+
54
+ # Add card to response object
55
+ #
56
+ # @param params [Hash] card parameters:
57
+ # type [String] card type, can be "Simple", "Standard" or "LinkAccount"
58
+ # title [String] card title
59
+ # content [String] card content (line breaks must be already included)
60
+ # small_image_url [String] an URL for small card image
61
+ # large_image_url [String] an URL for large card image
62
+ # @raise [NoMethodError] if card is not allowed
63
+ def add_card(params = {})
64
+ card_exception unless %i[launch intent].include? @req_type
65
+ card = Card.new(params)
66
+ @resp[:response][:card] = card.obj
40
67
  end
41
68
 
42
69
  # Add AudioPlayer directive
43
70
  #
44
71
  # @param directive [String] audio player directive type,
45
72
  # can be :start or :stop
46
- # @param opts [Hash] optional request parameters
47
- def add_audio_player_directive(directive, opts = {})
73
+ # @param params [Hash] optional request parameters:
74
+ # url [String] streaming URL
75
+ # token [String] streaming service token
76
+ # offset [Integer] playback offset
77
+ def add_audio_player_directive(directive, params = {})
48
78
  player = AudioPlayer.new
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
79
+ @resp[:response][:directives] = [
80
+ case directive.to_sym
81
+ when :start
82
+ player.play_directive(params)
83
+ when :stop
84
+ player.stop_directive
85
+ end
86
+ ]
57
87
  end
58
88
 
59
- # Add reprompt to outputSpeech node
89
+ # Return JSON version of current response state
60
90
  #
61
- # @param speech_text [String] output speech
62
- # @param ssml [Boolean] is it an SSML speech or not
63
- def add_reprompt(speech_text, ssml = false)
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
70
- @reprompt
91
+ # @return [JSON] response object
92
+ def json
93
+ Oj.to_json(@resp)
71
94
  end
72
95
 
73
- # Add card that will be shown in Amazon Alexa app on user smartphone/tablet
96
+ # Tell something to Alexa user and close conversation.
97
+ # Method will only add a given speech to response object
74
98
  #
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
99
+ # @param speech [Sring] output speech
100
+ # @param reprompt_speech [String] output speech if user remains idle
101
+ # @param ssml [Boolean] is it an SSML speech or not
102
+ def tell(speech, reprompt_speech = nil, ssml = false)
103
+ obj = { outputSpeech: build_speech(speech, ssml) }
104
+ if reprompt_speech
105
+ obj[:reprompt] = { outputSpeech: build_speech(reprompt_speech, ssml) }
106
+ end
107
+ @resp[:response].merge!(obj)
82
108
  end
83
109
 
84
- # Adds a speech to the object, also returns a outputspeech object
110
+ # Tell something to Alexa user and close conversation.
111
+ # Method will add given sppech to response object and
112
+ # immediately return its JSON implementation
85
113
  #
86
- # @param speech [String] output speech
87
- # @param end_session [Boolean] is it a final response, or not
114
+ # @param speech [Sring] output speech
115
+ # @param reprompt_speech [String] output speech if user remains idle
88
116
  # @param ssml [Boolean] is it an SSML speech or not
89
- def say_response(speech, end_session = true, ssml = false)
90
- output_speech = add_speech(speech, ssml)
91
- { outputSpeech: output_speech, shouldEndSession: end_session }
117
+ # @return [JSON] ready to use response object
118
+ def tell!(speech, reprompt_speech = nil, ssml = false)
119
+ obj = { outputSpeech: build_speech(speech, ssml) }
120
+ if reprompt_speech
121
+ obj[:reprompt][:outputSpeech] = build_speech(reprompt_speech, ssml)
122
+ end
123
+ @resp[:response].merge!(obj)
124
+ json
92
125
  end
93
126
 
94
- # Incorporates reprompt in the SDK 2015-05
127
+ # Ask something from user and wait for further information.
128
+ # Method will only add given sppech to response object and
129
+ # set "shouldEndSession" parameter to false
95
130
  #
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
- }
131
+ # @param speech [Sring] output speech
132
+ # @param reprompt_speech [String] output speech if user remains idle
133
+ # @param ssml [Boolean] is it an SSML speech or not
134
+ def ask(speech, reprompt_speech = nil, ssml = false)
135
+ @resp[:response][:shouldEndSession] = false
136
+ tell(speech, reprompt_speech, ssml)
110
137
  end
111
138
 
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
139
+ # Ask something from user and wait for further information.
140
+ # Method will only add given sppech to response object,
141
+ # set "shouldEndSession" parameter to false and
142
+ # immediately return response JSON implementation
114
143
  #
115
- # @return [Hash] user session parameters
116
- def build_session
117
- @session_attributes = {} if @session_attributes.nil?
118
- @session = { sessionAttributes: @session_attributes }
119
- @session
144
+ # @param speech [Sring] output speech
145
+ # @param reprompt_speech [String] output speech if user remains idle
146
+ # @param ssml [Boolean] is it an SSML speech or not
147
+ # @return [JSON] ready to use response object
148
+ def ask!(speech, reprompt_speech = nil, ssml = false)
149
+ @resp[:response][:shouldEndSession] = false
150
+ tell!(speech, reprompt_speech, ssml)
120
151
  end
121
152
 
122
- # The response object (with outputspeech, cards and session end)
123
- # Should rename this, but Amazon picked their names.
124
- # The only mandatory field is end_session which we default to true.
153
+ private
154
+
155
+ # Add pack of session attributes.
156
+ # By default all existing session attributes would be overwritten
125
157
  #
126
- # @param session_end [Boolean] is it a final response, or not
127
- # @return [Hash] response parameters
128
- def build_response_object(session_end = true)
129
- @response = {}
130
- @response[:outputSpeech] = @speech unless @speech.nil?
131
- @response[:directives] = @directives unless @directives.empty?
132
- @response[:card] = @card unless @card.nil?
133
- @response[:reprompt] = @reprompt unless session_end && @reprompt.nil?
134
- @response[:shouldEndSession] = session_end
135
- @response
158
+ # @param attributes [Hash] pack of session attributes
159
+ # @param merge [Boolean] merge attributes with existing ones?
160
+ def session_attributes(attributes, merge)
161
+ if merge
162
+ attributes.each { |k, v| add_session_attribute(k, v, true) }
163
+ else
164
+ @resp[:sessionAttributes] = {}
165
+ attributes.each { |k, v| add_session_attribute(k, v, false) }
166
+ end
136
167
  end
137
168
 
138
- # Builds a response.
139
- # Takes the version, response, should_end_session variables
140
- # and builds a JSON object
169
+ # Build speech object
141
170
  #
142
- # @param session_end [Boolean] is it a final response, or not
143
- # @return [JSON] json response for Amazon Alexa
144
- def build_response(session_end = true)
145
- response_object = build_response_object(session_end)
146
- response = {}
147
- response[:version] = @version
148
- unless @session_attributes.empty?
149
- response[:sessionAttributes] = @session_attributes
171
+ # @param speech [Sring] output speech
172
+ # @param ssml [Boolean] is it an SSML speech or not
173
+ # @return [Hash] speech object
174
+ def build_speech(speech, ssml)
175
+ obj = {}
176
+ obj[:type] = ssml ? 'SSML' : 'PlainText'
177
+ if ssml
178
+ obj[:ssml] = fix_ssml(speech)
179
+ else
180
+ obj[:text] = speech
150
181
  end
151
- response[:response] = response_object
152
- Oj.to_json(response)
182
+ obj
153
183
  end
154
184
 
155
- # Outputs the version, session object and the response object.
185
+ # Forced fix of SSML speech - manually check and fix open and close tags
156
186
  #
157
- # @return [String] version, session object and the response object
158
- def to_s
159
- "Version => #{@version}, SessionObj => #{@session}, " \
160
- "Response => #{@response}"
187
+ # @param text [String] SSML response speech
188
+ # @return [String] fixed SSML speech
189
+ def fix_ssml(text)
190
+ open_tag = text.strip[0..6]
191
+ close_tag = text.strip[-8..1]
192
+ text = open_tag == '<speak>' ? text : "<speak>#{text}"
193
+ close_tag == '</speak>' ? text : "#{text}</speak>"
161
194
  end
162
195
 
163
- private
164
-
165
- # Check and fix SSML speech
196
+ # Raise card exception
166
197
  #
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>"
198
+ # @raise [NoMethodError] if card is not allowed
199
+ def card_exception
200
+ raise NoMethodError, 'Card can only be included in response ' \
201
+ 'to a "LaunchRequest" or "IntentRequest"'
174
202
  end
175
203
  end
176
204
  end
@@ -1,3 +1,3 @@
1
1
  module AlexaRuby
2
- VERSION = '0.2.0'.freeze
2
+ VERSION = '1.0.0'.freeze
3
3
  end