alexa_ruby 0.1.0 → 0.2.0

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