alexa_ruby 0.2.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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