alexa_skills_ruby 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +36 -0
  3. data/Gemfile +3 -0
  4. data/LICENSE +21 -0
  5. data/README.md +58 -0
  6. data/Rakefile +7 -0
  7. data/alexa_skills_ruby.gemspec +26 -0
  8. data/lib/alexa_skills_ruby.rb +27 -0
  9. data/lib/alexa_skills_ruby/errors.rb +4 -0
  10. data/lib/alexa_skills_ruby/handler.rb +87 -0
  11. data/lib/alexa_skills_ruby/json_object.rb +122 -0
  12. data/lib/alexa_skills_ruby/json_objects/application.rb +7 -0
  13. data/lib/alexa_skills_ruby/json_objects/base_request.rb +40 -0
  14. data/lib/alexa_skills_ruby/json_objects/card.rb +16 -0
  15. data/lib/alexa_skills_ruby/json_objects/image.rb +7 -0
  16. data/lib/alexa_skills_ruby/json_objects/intent.rb +11 -0
  17. data/lib/alexa_skills_ruby/json_objects/intent_request.rb +7 -0
  18. data/lib/alexa_skills_ruby/json_objects/launch_request.rb +7 -0
  19. data/lib/alexa_skills_ruby/json_objects/output_speech.rb +21 -0
  20. data/lib/alexa_skills_ruby/json_objects/reprompt.rb +7 -0
  21. data/lib/alexa_skills_ruby/json_objects/response.rb +29 -0
  22. data/lib/alexa_skills_ruby/json_objects/session.rb +9 -0
  23. data/lib/alexa_skills_ruby/json_objects/session_ended_request.rb +7 -0
  24. data/lib/alexa_skills_ruby/json_objects/skills_request.rb +9 -0
  25. data/lib/alexa_skills_ruby/json_objects/skills_response.rb +14 -0
  26. data/lib/alexa_skills_ruby/json_objects/user.rb +7 -0
  27. data/lib/alexa_skills_ruby/version.rb +3 -0
  28. data/spec/fixtures/example_intent.json +34 -0
  29. data/spec/fixtures/example_launch.json +19 -0
  30. data/spec/fixtures/example_response.json +28 -0
  31. data/spec/fixtures/example_session_ended.json +26 -0
  32. data/spec/spec_helper.rb +15 -0
  33. data/spec/support/fixture_support.rb +11 -0
  34. data/spec/unit/handler_spec.rb +142 -0
  35. data/spec/unit/json_object_spec.rb +59 -0
  36. data/spec/unit/json_objects/skills_request_spec.rb +56 -0
  37. data/spec/unit/json_objects/skills_response_spec.rb +48 -0
  38. metadata +173 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1101b97d3f88c45eb43d643d75e3e8e8ca6b26c6
4
+ data.tar.gz: 0b6350b302c7ed1b643471986233069fe0a139c7
5
+ SHA512:
6
+ metadata.gz: fc1a9404b923d9575c8a75aceed09e7bd0b9dda09230f08d475b199c7f7d53923160c39b9093d3d645872e8d0611eb3e805d31f40c35c11e56683a30aa9e2bdf
7
+ data.tar.gz: e3f2725899cd0147118eb291c6ba8f6753bb521d67232722695eaa78e9cd0eb6c58e9f4c1d12254da9d8796d7d36abe0a52a941a896cc88357f976a5f5869bbf
data/.gitignore ADDED
@@ -0,0 +1,36 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ ## Specific to RubyMotion:
14
+ .dat*
15
+ .repl_history
16
+ build/
17
+
18
+ ## Documentation cache and generated files:
19
+ /.yardoc/
20
+ /_yardoc/
21
+ /doc/
22
+ /rdoc/
23
+
24
+ ## Environment normalization:
25
+ /.bundle/
26
+ /vendor/bundle
27
+ /lib/bundler/man/
28
+
29
+ # for a library or gem, you might want to ignore these files since the code is
30
+ # intended to run in multiple environments; otherwise, check them in:
31
+ Gemfile.lock
32
+ # .ruby-version
33
+ # .ruby-gemset
34
+
35
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
36
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Dan Elbert
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # alexa-skills-ruby
2
+ Simple library to interface with the Alexa Skills Kit
3
+
4
+ The primary way to interact with this library is by extending the `AlexaSkillsRuby::Handler` class. Create a subclass and
5
+ register event handlers.
6
+
7
+ The following handlers are available:
8
+
9
+ * `on_authenticate` - called before checking the ApplicationID
10
+ * `on_session_start` - called first if the request is flagged as a new session
11
+ * `on_launch` - called for a LaunchRequest
12
+ * `on_session_end` - called for a SessionEndedRequest
13
+ * `on_intent` called for an IntentRequest. Takes an optional string argument that specifies which intents it will be trigged on
14
+
15
+ In event handlers, the following methods are available:
16
+
17
+ * `request` - returns the current Request object, a `AlexaSkillsRuby::JsonObjects::BaseRequest`
18
+ * `session` - returns the current Session object, a `AlexaSkillsRuby::JsonObjects::Session`
19
+ * `session_attributes` - Hash containing the current session attributes. If the response is not flagged to end the session, all values will be sent with the response
20
+ * `response` - returns the current Response object, a `AlexaSkillsRuby::JsonObjects::Response`
21
+ * `application_id` - returns the value specified in the constructor options
22
+ * `logger` - returns the value specified in the constructor options
23
+
24
+ The `AlexaSkillsRuby::Handler` constructor takes an options hash and processes the following keys:
25
+ * `application_id` - If set, will raise a `AlexaSkillsRuby::InvalidApplicationId` if a request's application_id does not match
26
+ * `logger` - Will be available through the `logger` method in the handler; not otherwise used by the base class
27
+
28
+ ## Example Sinatra App Using this Library
29
+
30
+ ```ruby
31
+ require 'sinatra'
32
+ require 'alexa_skills_ruby'
33
+
34
+ class CustomHandler < AlexaSkillsRuby::Handler
35
+
36
+ on_intent("GetZodiacHoroscopeIntent") do
37
+ slots = request.intent.slots
38
+ response.set_output_speech_text("Horiscope Text")
39
+ response.set_simple_card("title", "content")
40
+ logger.info 'GetZodiacHoroscopeIntent processed'
41
+ end
42
+
43
+ end
44
+
45
+ post '/' do
46
+ content_type :json
47
+
48
+ handler = CustomHandler.new(application_id: ENV['APPLICATION_ID'], logger: logger)
49
+
50
+ begin
51
+ handler.handle(request.body.read)
52
+ rescue AlexaSkillsRuby::InvalidApplicationId => e
53
+ logger.error e.to_s
54
+ 403
55
+ end
56
+
57
+ end
58
+ ```
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :test => :spec
7
+ task :default => :spec
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'alexa_skills_ruby/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "alexa_skills_ruby"
8
+ spec.version = AlexaSkillsRuby::VERSION
9
+ spec.authors = ["Dan Elbert"]
10
+ spec.email = ["dan.elbert@gmail.com"]
11
+ spec.homepage = 'https://github.com/DanElbert/alexa_skills_ruby'
12
+ spec.summary = %q{Simple library to interface with the Alexa Skills Kit}
13
+
14
+ spec.files = `git ls-files -z`.split("\x0")
15
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
+ spec.require_paths = ["lib"]
18
+
19
+ spec.add_runtime_dependency 'activesupport', '~> 4.2'
20
+ spec.add_runtime_dependency "multi_json", "~> 1.0"
21
+
22
+ spec.add_development_dependency "bundler"
23
+ spec.add_development_dependency "rake"
24
+ spec.add_development_dependency "rspec", "~> 3.4.0"
25
+ spec.add_development_dependency "oj", "~> 2.10.2"
26
+ end
@@ -0,0 +1,27 @@
1
+ require 'active_support/core_ext/class/attribute'
2
+ require 'active_support/callbacks'
3
+ require 'multi_json'
4
+
5
+ require 'alexa_skills_ruby/version'
6
+ require 'alexa_skills_ruby/json_object'
7
+ require 'alexa_skills_ruby/json_objects/application'
8
+ require 'alexa_skills_ruby/json_objects/user'
9
+ require 'alexa_skills_ruby/json_objects/session'
10
+ require 'alexa_skills_ruby/json_objects/intent'
11
+ require 'alexa_skills_ruby/json_objects/base_request'
12
+ require 'alexa_skills_ruby/json_objects/intent_request'
13
+ require 'alexa_skills_ruby/json_objects/launch_request'
14
+ require 'alexa_skills_ruby/json_objects/session_ended_request'
15
+ require 'alexa_skills_ruby/json_objects/image'
16
+ require 'alexa_skills_ruby/json_objects/card'
17
+ require 'alexa_skills_ruby/json_objects/output_speech'
18
+ require 'alexa_skills_ruby/json_objects/reprompt'
19
+ require 'alexa_skills_ruby/json_objects/response'
20
+ require 'alexa_skills_ruby/json_objects/skills_request'
21
+ require 'alexa_skills_ruby/json_objects/skills_response'
22
+ require 'alexa_skills_ruby/errors'
23
+ require 'alexa_skills_ruby/handler'
24
+
25
+ module AlexaSkillsRuby
26
+
27
+ end
@@ -0,0 +1,4 @@
1
+ module AlexaSkillsRuby
2
+ class InvalidApplicationId < StandardError
3
+ end
4
+ end
@@ -0,0 +1,87 @@
1
+ module AlexaSkillsRuby
2
+ class Handler
3
+ include ActiveSupport::Callbacks
4
+ define_callbacks :authenticate, :session_start, :launch, :intent, :session_end
5
+
6
+ attr_reader :request, :session, :response
7
+ attr_accessor :application_id, :logger
8
+
9
+ def initialize(opts = {})
10
+ if opts[:application_id]
11
+ @application_id = opts[:application_id]
12
+ end
13
+
14
+ if opts[:logger]
15
+ @logger = opts[:logger]
16
+ end
17
+ end
18
+
19
+ def session_attributes
20
+ @session.attributes ||= {}
21
+ end
22
+
23
+ def handle(request_json)
24
+ @skill_request = JsonObjects::SkillsRequest.new(MultiJson.load(request_json))
25
+ @skill_response = JsonObjects::SkillsResponse.new
26
+
27
+ @session = @skill_request.session
28
+ @request = @skill_request.request
29
+ @response = @skill_response.response
30
+
31
+
32
+ run_callbacks :authenticate do
33
+ if @application_id
34
+ if @application_id != session.application.application_id
35
+ raise InvalidApplicationId, "Invalid: [#{session.application.application_id}]"
36
+ end
37
+ end
38
+ end
39
+
40
+ if session.new
41
+ run_callbacks :session_start
42
+ end
43
+
44
+ case request
45
+ when JsonObjects::LaunchRequest
46
+ run_callbacks :launch
47
+ when JsonObjects::IntentRequest
48
+ run_callbacks :intent
49
+ when JsonObjects::SessionEndedRequest
50
+ run_callbacks :session_end
51
+ end
52
+
53
+ if response.should_end_session
54
+ @skill_response.session_attributes = {}
55
+ else
56
+ @skill_response.session_attributes = session_attributes
57
+ end
58
+
59
+ MultiJson.dump(@skill_response.as_json)
60
+ end
61
+
62
+ def self.on_authenticate(&block)
63
+ set_callback :authenticate, :before, block
64
+ end
65
+
66
+ def self.on_session_start(&block)
67
+ set_callback :session_start, :before, block
68
+ end
69
+
70
+ def self.on_launch(&block)
71
+ set_callback :launch, :before, block
72
+ end
73
+
74
+ def self.on_session_end(&block)
75
+ set_callback :session_end, :before, block
76
+ end
77
+
78
+ def self.on_intent(intent_name = nil, &block)
79
+ opts = {}
80
+ if intent_name
81
+ opts[:if] = -> { request.intent_name == intent_name }
82
+ end
83
+ set_callback :intent, :before, block, opts
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,122 @@
1
+ module AlexaSkillsRuby
2
+ class JsonObject
3
+
4
+ class_attribute :_json_object_properties
5
+ class_attribute :_properties
6
+ self._json_object_properties = {}
7
+ self._properties = []
8
+
9
+ def self.json_object_attribute(*args)
10
+ klass = args.pop
11
+ raise "Invalid arguments" unless klass && klass.is_a?(Class) && args.length > 0
12
+
13
+ args.compact.map { |a| a.to_sym }.each do |a|
14
+ attr_accessor a
15
+ self._json_object_properties[a.to_sym] = klass
16
+ end
17
+ end
18
+
19
+ def self.attribute(*attrs)
20
+ attrs.compact.map { |a| a.to_sym }.each do |a|
21
+ attr_accessor a
22
+ self._properties << a
23
+ end
24
+ end
25
+
26
+ class << self
27
+ alias_method :json_object_attributes, :json_object_attribute
28
+ alias_method :attributes, :attribute
29
+ end
30
+
31
+ # Copy properties on inheritance.
32
+ def self.inherited(subclass)
33
+ entities = _json_object_properties.dup
34
+ attrs = _properties.dup
35
+ subclass._json_object_properties = entities.each { |k, v| entities[k] = v.dup }
36
+ subclass._properties = attrs
37
+ super
38
+ end
39
+
40
+ def initialize(attrs = {})
41
+ populate_from_json(attrs)
42
+ end
43
+
44
+ def as_json(options = nil)
45
+ serialize_attributes(_properties + _json_object_properties.keys)
46
+ end
47
+
48
+ def to_json(options = nil)
49
+ MultiJson.dump(as_json(options))
50
+ end
51
+
52
+ def populate_from_json(attrs)
53
+ return unless attrs
54
+
55
+ attrs.each do |k, v|
56
+ meth = "#{get_method_name(k)}=".to_sym
57
+ if self.respond_to? meth
58
+ if json_object_class = _json_object_properties[k.to_sym]
59
+ assign_json_object(meth, v, json_object_class)
60
+ else
61
+ self.send(meth, v)
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ def serialize_attributes(attrs)
68
+ json = {}
69
+ attrs.each do |k|
70
+ meth = k.to_sym
71
+ if self.respond_to?(meth)
72
+ if _json_object_properties[k.to_sym]
73
+ val = serialize_json_object(meth)
74
+ else
75
+ val = self.send(meth)
76
+ end
77
+ unless val.nil?
78
+ json[get_attribute_name(k)] = val
79
+ end
80
+ end
81
+ end
82
+ json
83
+ end
84
+
85
+ protected
86
+
87
+ def assign_json_object(assignment_method, value, klass)
88
+
89
+ if value.is_a? Array
90
+ data = value.map { |v| hydrate_entity(v, klass) }
91
+ else
92
+ data = hydrate_entity(value, klass)
93
+ end
94
+
95
+ self.send(assignment_method, data)
96
+ end
97
+
98
+ def hydrate_entity(json, klass)
99
+ klass.new(json)
100
+ end
101
+
102
+ def get_method_name(attr_name)
103
+ attr_name.to_s.gsub(/([a-z])([A-Z])([a-z]|^)/) { |m| "#{m[0]}_#{m[1].downcase}#{m[2]}" }
104
+ end
105
+
106
+ def get_attribute_name(meth_name)
107
+ meth_name.to_s.gsub(/([a-z])(_)([a-z])/) { |m| m[0] + m[2].upcase }
108
+ end
109
+
110
+ def serialize_json_object(method)
111
+ value = self.send(method)
112
+ case value
113
+ when nil
114
+ nil
115
+ when Array
116
+ value.map { |v| v.as_json }
117
+ else
118
+ value.as_json
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,7 @@
1
+ module AlexaSkillsRuby
2
+ module JsonObjects
3
+ class Application < JsonObject
4
+ attributes :application_id
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,40 @@
1
+ module AlexaSkillsRuby
2
+ module JsonObjects
3
+ class BaseRequest < JsonObject
4
+ attributes :type, :request_id, :timestamp
5
+
6
+ def self.new(*args, &block)
7
+ json = args.first
8
+ subclass = case
9
+ when self != BaseRequest
10
+ nil
11
+ when json.nil?
12
+ nil
13
+ when json['type'] == 'LaunchRequest'
14
+ LaunchRequest
15
+ when json['type'] == 'IntentRequest'
16
+ IntentRequest
17
+ when json['type'] == 'SessionEndedRequest'
18
+ SessionEndedRequest
19
+ else
20
+ nil
21
+ end
22
+
23
+ if subclass
24
+ subclass.new(*args, &block)
25
+ else
26
+ super
27
+ end
28
+ end
29
+
30
+ def intent_name
31
+ if self.is_a? IntentRequest
32
+ self.intent.name
33
+ else
34
+ nil
35
+ end
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,16 @@
1
+ module AlexaSkillsRuby
2
+ module JsonObjects
3
+ class Card < JsonObject
4
+ attributes :type, :title, :content, :text
5
+ json_object_attributes :image, Image
6
+
7
+ def self.simple(title, content)
8
+ card = new
9
+ card.type = "Simple"
10
+ card.title = title
11
+ card.content = content
12
+ card
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,7 @@
1
+ module AlexaSkillsRuby
2
+ module JsonObjects
3
+ class Image < JsonObject
4
+ attributes :small_image_url, :large_image_url
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module AlexaSkillsRuby
2
+ module JsonObjects
3
+ class Intent < JsonObject
4
+ attributes :name, :slots
5
+
6
+ def slots=(val)
7
+ @slots = Hash[(val || {}).map { |k, v| [k, v['value']] }]
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ module AlexaSkillsRuby
2
+ module JsonObjects
3
+ class IntentRequest < BaseRequest
4
+ json_object_attribute :intent, Intent
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module AlexaSkillsRuby
2
+ module JsonObjects
3
+ class LaunchRequest < BaseRequest
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,21 @@
1
+ module AlexaSkillsRuby
2
+ module JsonObjects
3
+ class OutputSpeech < JsonObject
4
+ attributes :type, :text, :ssml
5
+
6
+ def self.text(text)
7
+ os = new
8
+ os.text = text
9
+ os.type = 'PlainText'
10
+ os
11
+ end
12
+
13
+ def self.ssml(ssml)
14
+ os = new
15
+ os.ssml = ssml
16
+ os.type = 'SSML'
17
+ os
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ module AlexaSkillsRuby
2
+ module JsonObjects
3
+ class Reprompt < JsonObject
4
+ json_object_attributes :output_speech, OutputSpeech
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,29 @@
1
+ module AlexaSkillsRuby
2
+ module JsonObjects
3
+ class Response < JsonObject
4
+ attributes :should_end_session
5
+ json_object_attribute :output_speech, OutputSpeech
6
+ json_object_attribute :card, Card
7
+ json_object_attribute :reprompt, Reprompt
8
+
9
+ def initialize
10
+ self.should_end_session = true
11
+ end
12
+
13
+ def set_output_speech_text(text)
14
+ self.output_speech = OutputSpeech.text(text)
15
+ end
16
+
17
+ def set_simple_card(title, content)
18
+ self.card = Card.simple(title, content)
19
+ end
20
+
21
+ def set_reprompt_speech_text(text)
22
+ os = OutputSpeech.text(text)
23
+ rp = Reprompt.new
24
+ rp.output_speech = os
25
+ self.reprompt = rp
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,9 @@
1
+ module AlexaSkillsRuby
2
+ module JsonObjects
3
+ class Session < JsonObject
4
+ attributes :new, :session_id, :attributes
5
+ json_object_attribute :application, Application
6
+ json_object_attribute :user, User
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module AlexaSkillsRuby
2
+ module JsonObjects
3
+ class SessionEndedRequest < BaseRequest
4
+ attribute :reason
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ module AlexaSkillsRuby
2
+ module JsonObjects
3
+ class SkillsRequest < JsonObject
4
+ attribute :version
5
+ json_object_attribute :session, Session
6
+ json_object_attribute :request, BaseRequest
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ module AlexaSkillsRuby
2
+ module JsonObjects
3
+ class SkillsResponse < JsonObject
4
+ attributes :version, :session_attributes
5
+ json_object_attribute :response, Response
6
+
7
+ def initialize
8
+ self.version = '1.0'
9
+ self.session_attributes = {}
10
+ self.response = JsonObjects::Response.new
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ module AlexaSkillsRuby
2
+ module JsonObjects
3
+ class User < JsonObject
4
+ attributes :user_id, :access_token
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module AlexaSkillsRuby
2
+ VERSION = '0.0.5'
3
+ end
@@ -0,0 +1,34 @@
1
+ {
2
+ "version": "1.0",
3
+ "session": {
4
+ "new": false,
5
+ "sessionId": "amzn1.echo-api.session.0000000-0000-0000-0000-00000000000",
6
+ "application": {
7
+ "applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe"
8
+ },
9
+ "attributes": {
10
+ "supportedHoroscopePeriods": {
11
+ "daily": true,
12
+ "weekly": false,
13
+ "monthly": false
14
+ }
15
+ },
16
+ "user": {
17
+ "userId": "amzn1.account.AM3B00000000000000000000000"
18
+ }
19
+ },
20
+ "request": {
21
+ "type": "IntentRequest",
22
+ "requestId": "amzn1.echo-api.request.0000000-0000-0000-0000-00000000000",
23
+ "timestamp": "2015-05-13T12:34:56Z",
24
+ "intent": {
25
+ "name": "GetZodiacHoroscopeIntent",
26
+ "slots": {
27
+ "ZodiacSign": {
28
+ "name": "ZodiacSign",
29
+ "value": "virgo"
30
+ }
31
+ }
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "version": "1.0",
3
+ "session": {
4
+ "new": true,
5
+ "sessionId": "amzn1.echo-api.session.0000000-0000-0000-0000-00000000000",
6
+ "application": {
7
+ "applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe"
8
+ },
9
+ "attributes": {},
10
+ "user": {
11
+ "userId": "amzn1.account.AM3B00000000000000000000000"
12
+ }
13
+ },
14
+ "request": {
15
+ "type": "LaunchRequest",
16
+ "requestId": "amzn1.echo-api.request.0000000-0000-0000-0000-00000000000",
17
+ "timestamp": "2015-05-13T12:34:56Z"
18
+ }
19
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "version": "1.0",
3
+ "sessionAttributes": {
4
+ "supportedHoriscopePeriods": {
5
+ "daily": true,
6
+ "weekly": false,
7
+ "monthly": false
8
+ }
9
+ },
10
+ "response": {
11
+ "outputSpeech": {
12
+ "type": "PlainText",
13
+ "text": "Today will provide you a new learning opportunity. Stick with it and the possibilities will be endless. Can I help you with anything else?"
14
+ },
15
+ "card": {
16
+ "type": "Simple",
17
+ "title": "Horoscope",
18
+ "content": "Today will provide you a new learning opportunity. Stick with it and the possibilities will be endless."
19
+ },
20
+ "reprompt": {
21
+ "outputSpeech": {
22
+ "type": "PlainText",
23
+ "text": "Can I help you with anything else?"
24
+ }
25
+ },
26
+ "shouldEndSession": false
27
+ }
28
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "version": "1.0",
3
+ "session": {
4
+ "new": false,
5
+ "sessionId": "amzn1.echo-api.session.0000000-0000-0000-0000-00000000000",
6
+ "application": {
7
+ "applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe"
8
+ },
9
+ "attributes": {
10
+ "supportedHoroscopePeriods": {
11
+ "daily": true,
12
+ "weekly": false,
13
+ "monthly": false
14
+ }
15
+ },
16
+ "user": {
17
+ "userId": "amzn1.account.AM3B00000000000000000000000"
18
+ }
19
+ },
20
+ "request": {
21
+ "type": "SessionEndedRequest",
22
+ "requestId": "amzn1.echo-api.request.0000000-0000-0000-0000-00000000000",
23
+ "timestamp": "2015-05-13T12:34:56Z",
24
+ "reason": "USER_INITIATED"
25
+ }
26
+ }
@@ -0,0 +1,15 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'oj'
4
+ require 'rspec'
5
+ require File.expand_path('../../lib/alexa_skills_ruby', __FILE__)
6
+
7
+ MultiJson.use :oj
8
+
9
+ Dir[File.expand_path('../support/**/*', __FILE__)].each { |f| require f }
10
+
11
+ RSpec.configure do |config|
12
+
13
+ config.include FixtureSupport
14
+
15
+ end
@@ -0,0 +1,11 @@
1
+ module FixtureSupport
2
+
3
+ def load_json(name)
4
+ File.read(File.expand_path(File.dirname(__FILE__) + '/../fixtures/' + name))
5
+ end
6
+
7
+ def load_fixture(name)
8
+ Oj.load(load_json(name))
9
+ end
10
+
11
+ end
@@ -0,0 +1,142 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe AlexaSkillsRuby::Handler do
4
+
5
+ class TestHandler < AlexaSkillsRuby::Handler
6
+
7
+ attr_reader :auths, :intents, :launch, :ends, :starts
8
+
9
+ def initialize(*args)
10
+ super
11
+ @auths = []
12
+ @intents = []
13
+ @launch = []
14
+ @ends = []
15
+ @starts = []
16
+ end
17
+
18
+ on_session_start do
19
+ @starts << request
20
+ end
21
+
22
+ on_authenticate do
23
+ @auths << request
24
+ end
25
+
26
+ on_launch do
27
+ @launch << request
28
+ end
29
+
30
+ on_intent do
31
+ @intents << request
32
+ end
33
+
34
+ on_intent('special') do
35
+ @intents << request
36
+ end
37
+
38
+ on_session_end do
39
+ @ends << request
40
+ end
41
+ end
42
+
43
+ let(:handler) { TestHandler.new }
44
+
45
+ describe 'with a launch request' do
46
+ let(:request_json) { load_json 'example_launch.json' }
47
+
48
+ it 'fires the handlers' do
49
+ handler.handle(request_json)
50
+
51
+ expect(handler.auths.count).to eq 1
52
+ expect(handler.intents.count).to eq 0
53
+ expect(handler.ends.count).to eq 0
54
+ expect(handler.starts.count).to eq 1
55
+ expect(handler.launch.count).to eq 1
56
+ end
57
+ end
58
+
59
+ describe 'with an intent request' do
60
+ let(:request_json) { load_json 'example_intent.json' }
61
+
62
+ it 'fires the handlers' do
63
+ handler.handle(request_json)
64
+
65
+ expect(handler.auths.count).to eq 1
66
+ expect(handler.intents.count).to eq 1
67
+ expect(handler.ends.count).to eq 0
68
+ expect(handler.starts.count).to eq 0
69
+ expect(handler.launch.count).to eq 0
70
+ end
71
+ end
72
+
73
+ describe 'with a special intent request' do
74
+ let(:request_json) do
75
+ json = load_fixture 'example_intent.json'
76
+ json['request']['intent']['name'] = 'special'
77
+ Oj.dump(json)
78
+ end
79
+
80
+ it 'fires the handlers' do
81
+ handler.handle(request_json)
82
+
83
+ expect(handler.auths.count).to eq 1
84
+ expect(handler.intents.count).to eq 2
85
+ expect(handler.ends.count).to eq 0
86
+ expect(handler.starts.count).to eq 0
87
+ expect(handler.launch.count).to eq 0
88
+ end
89
+ end
90
+
91
+ describe 'with a session ended request' do
92
+ let(:request_json) { load_json 'example_session_ended.json' }
93
+
94
+ it 'fires the handlers' do
95
+ handler.handle(request_json)
96
+
97
+ expect(handler.auths.count).to eq 1
98
+ expect(handler.intents.count).to eq 0
99
+ expect(handler.ends.count).to eq 1
100
+ expect(handler.starts.count).to eq 0
101
+ expect(handler.launch.count).to eq 0
102
+ end
103
+ end
104
+
105
+ describe 'with an application_id set' do
106
+
107
+ let(:handler) { TestHandler.new({application_id: 'amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe'}) }
108
+
109
+ describe 'with a valid app id in request' do
110
+ let(:request_json) { load_json 'example_session_ended.json' }
111
+
112
+ it 'fires the handlers' do
113
+ handler.handle(request_json)
114
+
115
+ expect(handler.auths.count).to eq 1
116
+ expect(handler.intents.count).to eq 0
117
+ expect(handler.ends.count).to eq 1
118
+ expect(handler.starts.count).to eq 0
119
+ expect(handler.launch.count).to eq 0
120
+ end
121
+ end
122
+
123
+ describe 'with an invalid app id in request' do
124
+ let(:request_json) do
125
+ json = load_fixture 'example_intent.json'
126
+ json['session']['application']['applicationId'] = 'broke'
127
+ Oj.dump(json)
128
+ end
129
+
130
+ it 'fires the handlers' do
131
+ expect { handler.handle(request_json) }.to raise_error AlexaSkillsRuby::InvalidApplicationId
132
+
133
+ expect(handler.auths.count).to eq 1
134
+ expect(handler.intents.count).to eq 0
135
+ expect(handler.ends.count).to eq 0
136
+ expect(handler.starts.count).to eq 0
137
+ expect(handler.launch.count).to eq 0
138
+ end
139
+ end
140
+ end
141
+
142
+ end
@@ -0,0 +1,59 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe AlexaSkillsRuby::JsonObject do
4
+
5
+ class TestJsonObject < AlexaSkillsRuby::JsonObject
6
+ attributes :name, :id
7
+ json_object_attribute :attr1, AlexaSkillsRuby::JsonObjects::User
8
+ end
9
+
10
+ class ChildTestJsonObject < TestJsonObject
11
+ attribute :another_field
12
+ json_object_attribute :attr2, AlexaSkillsRuby::JsonObjects::User
13
+ end
14
+
15
+ class OtherTestJsonObject < AlexaSkillsRuby::JsonObject
16
+ json_object_attribute :attr3, AlexaSkillsRuby::JsonObjects::User
17
+ end
18
+
19
+ describe '.json_object_attribute' do
20
+
21
+ it 'should keep separate lists for each type' do
22
+ expect(TestJsonObject._json_object_properties.keys).to contain_exactly :attr1
23
+ expect(ChildTestJsonObject._json_object_properties.keys).to contain_exactly :attr1, :attr2
24
+ expect(OtherTestJsonObject._json_object_properties.keys).to contain_exactly :attr3
25
+
26
+ expect(TestJsonObject._properties).to contain_exactly :name, :id
27
+ expect(ChildTestJsonObject._properties).to contain_exactly :name, :id, :another_field
28
+ expect(OtherTestJsonObject._properties.length).to eq 0
29
+ end
30
+
31
+ end
32
+
33
+ describe 'serialization' do
34
+
35
+ it 'serializes entities' do
36
+ json_object = TestJsonObject.new
37
+ json_object.attr1 = AlexaSkillsRuby::JsonObjects::User.new
38
+ json_object.attr1.user_id = 5
39
+ json_object.attr1.access_token = 'token'
40
+ json_object.name = 'name'
41
+ json_object.id = 1
42
+
43
+ json = json_object.as_json
44
+ expect(json).to eq({ 'attr1' => { 'userId' => 5, 'accessToken' => 'token'}, 'name' => 'name', 'id' => 1 })
45
+ end
46
+
47
+ end
48
+
49
+ describe 'deserialization' do
50
+ it 'populates the objects' do
51
+ json = { attr1: { id: 5, access_token: 'token'}, name: 'name', id: 1 }
52
+ json_object = TestJsonObject.new(json)
53
+ expect(json_object.name).to eq 'name'
54
+ expect(json_object.attr1).to be_a AlexaSkillsRuby::JsonObjects::User
55
+ expect(json_object.attr1.access_token).to eq 'token'
56
+ end
57
+ end
58
+
59
+ end
@@ -0,0 +1,56 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe AlexaSkillsRuby::JsonObjects::SkillsRequest do
4
+
5
+ let(:launch_request_json) do
6
+ load_fixture 'example_launch.json'
7
+ end
8
+
9
+ let(:intent_request_json) do
10
+ load_fixture 'example_intent.json'
11
+ end
12
+
13
+ let(:session_ended_request_json) do
14
+ load_fixture 'example_session_ended.json'
15
+ end
16
+
17
+ it 'constructs a launch request' do
18
+ r = AlexaSkillsRuby::JsonObjects::SkillsRequest.new(launch_request_json)
19
+ expect(r).not_to be_nil
20
+ expect(r.version).to eq '1.0'
21
+ expect(r.session).to be_a AlexaSkillsRuby::JsonObjects::Session
22
+ expect(r.session.new).to eq true
23
+ expect(r.session.session_id).to eq 'amzn1.echo-api.session.0000000-0000-0000-0000-00000000000'
24
+ expect(r.session.application).to be_a AlexaSkillsRuby::JsonObjects::Application
25
+ expect(r.session.application.application_id).to eq 'amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe'
26
+ expect(r.session.attributes).to eq({})
27
+ expect(r.session.user).to be_a AlexaSkillsRuby::JsonObjects::User
28
+ expect(r.session.user.user_id).to eq 'amzn1.account.AM3B00000000000000000000000'
29
+ expect(r.request).to be_a AlexaSkillsRuby::JsonObjects::LaunchRequest
30
+ expect(r.request.type).to eq 'LaunchRequest'
31
+ expect(r.request.request_id).to eq 'amzn1.echo-api.request.0000000-0000-0000-0000-00000000000'
32
+ expect(r.request.timestamp).to eq '2015-05-13T12:34:56Z'
33
+ end
34
+
35
+ it 'constructs an intent request' do
36
+ r = AlexaSkillsRuby::JsonObjects::SkillsRequest.new(intent_request_json)
37
+ expect(r.request).to be_a AlexaSkillsRuby::JsonObjects::IntentRequest
38
+ expect(r.request.type).to eq 'IntentRequest'
39
+ expect(r.request.request_id).to eq 'amzn1.echo-api.request.0000000-0000-0000-0000-00000000000'
40
+ expect(r.request.timestamp).to eq '2015-05-13T12:34:56Z'
41
+ expect(r.request.intent).to be_a AlexaSkillsRuby::JsonObjects::Intent
42
+ expect(r.request.intent.name).to eq 'GetZodiacHoroscopeIntent'
43
+ expect(r.request.intent.slots).to be_a Hash
44
+ expect(r.request.intent.slots['ZodiacSign']).to eq('virgo')
45
+ end
46
+
47
+ it 'constructs a session ended request' do
48
+ r = AlexaSkillsRuby::JsonObjects::SkillsRequest.new(session_ended_request_json)
49
+ expect(r.request).to be_a AlexaSkillsRuby::JsonObjects::SessionEndedRequest
50
+ expect(r.request.type).to eq 'SessionEndedRequest'
51
+ expect(r.request.request_id).to eq 'amzn1.echo-api.request.0000000-0000-0000-0000-00000000000'
52
+ expect(r.request.timestamp).to eq '2015-05-13T12:34:56Z'
53
+ expect(r.request.reason).to eq 'USER_INITIATED'
54
+ end
55
+
56
+ end
@@ -0,0 +1,48 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe AlexaSkillsRuby::JsonObjects::SkillsResponse do
4
+
5
+ let(:response_json) do
6
+ {
7
+ 'version' => "1.0",
8
+ 'sessionAttributes' => {
9
+ 'supportedHoriscopePeriods' => {
10
+ 'daily' => true,
11
+ 'weekly' => false,
12
+ 'monthly' => false
13
+ }
14
+ },
15
+ 'response' => {
16
+ 'outputSpeech' => {
17
+ 'type' => "PlainText",
18
+ 'text' => "Today will provide you a new learning opportunity. Stick with it and the possibilities will be endless. Can I help you with anything else?"
19
+ },
20
+ 'card' => {
21
+ 'type' => "Simple",
22
+ 'title' => "Horoscope",
23
+ 'content' => "Today will provide you a new learning opportunity. Stick with it and the possibilities will be endless."
24
+ },
25
+ 'reprompt' => {
26
+ 'outputSpeech' => {
27
+ 'type' => "PlainText",
28
+ 'text' => "Can I help you with anything else?"
29
+ }
30
+ },
31
+ 'shouldEndSession' => false
32
+ }
33
+ }
34
+ end
35
+
36
+ it 'generates example json' do
37
+ sr = AlexaSkillsRuby::JsonObjects::SkillsResponse.new
38
+ r = sr.response
39
+ sr.session_attributes = {'supportedHoriscopePeriods' => {'daily' => true, 'weekly' => false, 'monthly' => false}}
40
+ r.set_output_speech_text("Today will provide you a new learning opportunity. Stick with it and the possibilities will be endless. Can I help you with anything else?")
41
+ r.set_simple_card('Horoscope', 'Today will provide you a new learning opportunity. Stick with it and the possibilities will be endless.')
42
+ r.set_reprompt_speech_text('Can I help you with anything else?')
43
+ r.should_end_session = false
44
+
45
+ expect(sr.as_json).to eq response_json
46
+ end
47
+
48
+ end
metadata ADDED
@@ -0,0 +1,173 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: alexa_skills_ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.5
5
+ platform: ruby
6
+ authors:
7
+ - Dan Elbert
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-04-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: multi_json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 3.4.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 3.4.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: oj
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 2.10.2
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 2.10.2
97
+ description:
98
+ email:
99
+ - dan.elbert@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - Gemfile
106
+ - LICENSE
107
+ - README.md
108
+ - Rakefile
109
+ - alexa_skills_ruby.gemspec
110
+ - lib/alexa_skills_ruby.rb
111
+ - lib/alexa_skills_ruby/errors.rb
112
+ - lib/alexa_skills_ruby/handler.rb
113
+ - lib/alexa_skills_ruby/json_object.rb
114
+ - lib/alexa_skills_ruby/json_objects/application.rb
115
+ - lib/alexa_skills_ruby/json_objects/base_request.rb
116
+ - lib/alexa_skills_ruby/json_objects/card.rb
117
+ - lib/alexa_skills_ruby/json_objects/image.rb
118
+ - lib/alexa_skills_ruby/json_objects/intent.rb
119
+ - lib/alexa_skills_ruby/json_objects/intent_request.rb
120
+ - lib/alexa_skills_ruby/json_objects/launch_request.rb
121
+ - lib/alexa_skills_ruby/json_objects/output_speech.rb
122
+ - lib/alexa_skills_ruby/json_objects/reprompt.rb
123
+ - lib/alexa_skills_ruby/json_objects/response.rb
124
+ - lib/alexa_skills_ruby/json_objects/session.rb
125
+ - lib/alexa_skills_ruby/json_objects/session_ended_request.rb
126
+ - lib/alexa_skills_ruby/json_objects/skills_request.rb
127
+ - lib/alexa_skills_ruby/json_objects/skills_response.rb
128
+ - lib/alexa_skills_ruby/json_objects/user.rb
129
+ - lib/alexa_skills_ruby/version.rb
130
+ - spec/fixtures/example_intent.json
131
+ - spec/fixtures/example_launch.json
132
+ - spec/fixtures/example_response.json
133
+ - spec/fixtures/example_session_ended.json
134
+ - spec/spec_helper.rb
135
+ - spec/support/fixture_support.rb
136
+ - spec/unit/handler_spec.rb
137
+ - spec/unit/json_object_spec.rb
138
+ - spec/unit/json_objects/skills_request_spec.rb
139
+ - spec/unit/json_objects/skills_response_spec.rb
140
+ homepage: https://github.com/DanElbert/alexa_skills_ruby
141
+ licenses: []
142
+ metadata: {}
143
+ post_install_message:
144
+ rdoc_options: []
145
+ require_paths:
146
+ - lib
147
+ required_ruby_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ required_rubygems_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: '0'
157
+ requirements: []
158
+ rubyforge_project:
159
+ rubygems_version: 2.4.6
160
+ signing_key:
161
+ specification_version: 4
162
+ summary: Simple library to interface with the Alexa Skills Kit
163
+ test_files:
164
+ - spec/fixtures/example_intent.json
165
+ - spec/fixtures/example_launch.json
166
+ - spec/fixtures/example_response.json
167
+ - spec/fixtures/example_session_ended.json
168
+ - spec/spec_helper.rb
169
+ - spec/support/fixture_support.rb
170
+ - spec/unit/handler_spec.rb
171
+ - spec/unit/json_object_spec.rb
172
+ - spec/unit/json_objects/skills_request_spec.rb
173
+ - spec/unit/json_objects/skills_response_spec.rb