clova-speech 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5df3bd56f3d5eac180fd191333d75172f4044e6b758b0f04538c8aa15c2e8082
4
+ data.tar.gz: 0ab2b3ba5d794293edeafcabe8eaedb8a082c786f6ab5243faccaf2f044af1d0
5
+ SHA512:
6
+ metadata.gz: 3ad9d85dc5d8683fc42546a89ea3502b17ac58e37f9c98212b353b08286711c6bbad0ce3987e28b1c48bae129b0d6fdb0619f2084c5255bf5bee36b573309cd0
7
+ data.tar.gz: 46c0498da965b546003f57b58297d3eaed6478c05a559361473ec90b398f5c2b5d4ab9a4dbeeaabd3ecd8d941afe916151eb9583378f787f50d9b98a490b5b7e
@@ -0,0 +1,24 @@
1
+ # Clova-Speech
2
+
3
+ ## Installation
4
+
5
+ Add this line to your application's Gemfile:
6
+
7
+ ```ruby
8
+ gem 'clova-speech'
9
+ ```
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ ## Usage
16
+
17
+
18
+
19
+ ## Development
20
+
21
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
22
+
23
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
24
+
@@ -0,0 +1,14 @@
1
+ require 'clova/version'
2
+ require 'clova/config'
3
+ require 'clova/request'
4
+ require 'clova/response'
5
+ require 'clova/event_request'
6
+ require 'clova/intent_request'
7
+ require 'clova/output_speech'
8
+ require 'clova/validator'
9
+ require 'clova/session'
10
+ require 'clova/launch_request'
11
+ require 'clova/session_ended_request'
12
+ require 'clova/client'
13
+ require 'clova/skill'
14
+ require 'json'
@@ -0,0 +1,43 @@
1
+ module Clova
2
+ class Client
3
+
4
+ attr_accessor :config
5
+
6
+ def initialize
7
+ @config = Config.new
8
+ end
9
+
10
+ def configure
11
+ yield(config)
12
+ end
13
+
14
+ def build_response(raw_request)
15
+ request = self.build_request(raw_request)
16
+ skill = config.skill_class.new(request)
17
+ skill.call
18
+ end
19
+
20
+ #raw_request: the HTTPRequest that is recieved, including headers, body, before deserialization
21
+ def build_request(raw_request)
22
+ validator = Clova::Validator.new(app_id: config.application_id)
23
+ validator.validate(raw_request)
24
+
25
+ raw_request.body.rewind #allows for the request to be read a second time
26
+ request = JSON.parse(raw_request.body.read.to_s, :symbolize_names => true)
27
+ type = request[:request].fetch(:type)
28
+
29
+ case type
30
+ when 'IntentRequest'
31
+ IntentRequest.new(request)
32
+ when 'EventRequest'
33
+ EventRequest.new(request)
34
+ when 'LaunchRequest'
35
+ LaunchRequest.new(request)
36
+ when 'SessionEndedRequest'
37
+ SessionEndedRequest.new(request)
38
+ else
39
+ raise "Request Invalid (no matching type)"
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,10 @@
1
+ module Clova
2
+ class Config
3
+ attr_accessor :application_id, :skill_class
4
+
5
+ def initialize
6
+ @application_id = nil
7
+ @skill_class = nil
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ module Clova
2
+ class EventRequest < Request
3
+ attr_reader :namespace, :name, :payload, :event, :type
4
+ def initialize(session:, context:, request:, version:)
5
+ super
6
+ @event = request[:event]
7
+ @namespace = @event[:namespace]
8
+ @name = @event[:name]
9
+ @payload = @event[:payload]
10
+
11
+ @type = 'EventRequest'
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ module Clova
2
+ class IntentRequest < Request
3
+ attr_accessor :name, :slots, :intent, :type
4
+ def initialize(session:, context:, request:, version:)
5
+ super
6
+ @intent = request[:intent]
7
+ @name = @intent[:name]
8
+ @slots = @intent[:slots]
9
+
10
+ @type = 'IntentRequest'
11
+ end
12
+
13
+ def find_slot_value_by(slot_name)
14
+ name = slot_name
15
+ name = name.to_sym if name.is_a?(String)
16
+ @slots[name][:value]
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ module Clova
2
+ class LaunchRequest < Request
3
+ attr_reader :type
4
+ def initialize(session:, context:, request:, version:)
5
+ super
6
+ @type = 'LaunchRequest'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,66 @@
1
+ module Clova
2
+ class OutputSpeech
3
+ def initialize
4
+ @lang = 'ja'
5
+ @speech_info_objects = []
6
+ end
7
+
8
+ def as_json
9
+ {type: type, values: values}
10
+ end
11
+
12
+ def empty?
13
+ @speech_info_objects.empty?
14
+ end
15
+
16
+ def add_speech(value:, type:'PlainText', language: @lang)
17
+ case type
18
+ when 'PlainText'
19
+ lang = language
20
+ when 'URL'
21
+ lang = ''
22
+ end
23
+ @speech_info_objects << SpeechInfoObject.new(lang: lang, value:value, type:type)
24
+ end
25
+
26
+ def simple_speech?
27
+ @speech_info_objects.one?
28
+ end
29
+
30
+ def speech_list?
31
+ !simple_speech?
32
+ end
33
+
34
+ def type
35
+ case
36
+ when simple_speech? then 'SimpleSpeech'
37
+ when speech_list? then 'SpeechList'
38
+ else
39
+ raise 'Type not found'
40
+ end
41
+ end
42
+
43
+ def values
44
+ case
45
+ when simple_speech?
46
+ @speech_info_objects.first.as_json
47
+ when speech_list?
48
+ @speech_info_objects.map(&:as_json)
49
+ end
50
+ end
51
+
52
+ class SpeechInfoObject
53
+ attr_accessor :type, :lang, :value
54
+
55
+ def initialize(lang:, value:, type:)
56
+ @lang = lang
57
+ @type = type
58
+ @value = value
59
+ end
60
+
61
+ def as_json
62
+ {type: type, lang: lang, value: value}
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,15 @@
1
+ module Clova
2
+ class Request
3
+ attr_reader :session, :context, :version
4
+
5
+ def initialize(session:, context:, request:, version:)
6
+ @context = context
7
+ @session = Session.new(session)
8
+ @version = version
9
+ end
10
+
11
+ def session_attributes
12
+ @session.session_attributes
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,55 @@
1
+ module Clova
2
+ class Response
3
+ attr_accessor :type, :output_speech, :session_attributes, :should_end_session, :version, :card, :directives, :reprompt_speech, :default_language
4
+
5
+ def initialize
6
+ @default_language = 'ja'
7
+ @version = Clova::VERSION
8
+ @output_speech = OutputSpeech.new
9
+ @session_attributes = {}
10
+ @directives = []
11
+ @should_end_session = false
12
+ @card = {}
13
+ @reprompt_speech = OutputSpeech.new
14
+ end
15
+
16
+ def as_json
17
+ {
18
+ version: version,
19
+ sessionAttributes: session_attributes,
20
+ response: response
21
+ }
22
+ end
23
+
24
+ def add_session_attribute(key:, value:)
25
+ @session_attributes[key] = value
26
+ end
27
+
28
+ #type either is 'PlainText' or 'URL'
29
+ def add_speech(value, type:'PlainText', language: @default_language)
30
+ @output_speech.add_speech(value: value, type: type, language: language)
31
+ end
32
+
33
+ def add_reprompt(value, type: 'PlainText')
34
+ @reprompt_speech.add_speech(value: value, type: type)
35
+ end
36
+
37
+ def add_session_attribute(key:, value:)
38
+ @session_attributes[key] = value
39
+ end
40
+
41
+ def response
42
+ hash = {
43
+ outputSpeech: output_speech.as_json,
44
+ card: card,
45
+ directives: directives,
46
+ shouldEndSession: should_end_session
47
+ }
48
+
49
+ if !@reprompt_speech.empty?
50
+ hash[:reprompt] = {outputSpeech: reprompt_speech.as_json}
51
+ end
52
+ hash
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,22 @@
1
+ module Clova
2
+ class Session
3
+ attr_accessor :new, :session_attributes, :session_id, :user
4
+
5
+ def initialize(new:, sessionAttributes:, sessionId:, user:)
6
+ @new = new #boolean
7
+ @session_attributes = sessionAttributes #key-value pair object, stores data from previous ClovaResponse' session_attributes
8
+ @session_id = sessionId #String
9
+ @user = User.new(user)
10
+ end
11
+
12
+
13
+ class User
14
+ attr_accessor :user_id, :access_token
15
+
16
+ def initialize(userId:, accessToken:nil)
17
+ @user_id = userId
18
+ @access_token = accessToken
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,10 @@
1
+ module Clova
2
+ class SessionEndedRequest < Request
3
+ attr_accessor :type
4
+
5
+ def initialize(session:, context:, request:, version:)
6
+ super
7
+ @type = 'SessionEndedRequest'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,25 @@
1
+ module Clova
2
+ class Skill
3
+ attr_accessor :request, :response
4
+ def initialize(request)
5
+ @request = request
6
+ @response = response
7
+ end
8
+
9
+ def call
10
+ case request.type
11
+ when 'LaunchRequest' then launch_request
12
+ when 'IntentRequest' then intent_request
13
+ when 'EventRequest' then event_request
14
+ when 'SessionEndedRequest' then session_ended_request
15
+ end
16
+ @response.as_json.to_json
17
+ end
18
+
19
+ private
20
+
21
+ def response
22
+ @response ||= Clova::Response.new
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,56 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+
4
+ # Each request contains an application_id, which mirrors the extension_id the developer
5
+ # sets on the Clova development portal. Every request is also signed using RSA encryption
6
+
7
+ module Clova
8
+ class Validator
9
+ attr_accessor :app_id
10
+
11
+ # raw_request: is the body of the incoming request, in the form of String
12
+ # request_sign: is the signature, found in the header of the request under "SignatureCEK"
13
+ def initialize(app_id:)
14
+ @app_id = app_id
15
+ end
16
+
17
+ def validate(raw_request)
18
+ request_body_str = raw_request.body.read
19
+ request_sign = raw_request.env["HTTP_SIGNATURECEK"]
20
+
21
+ sign_result = valid_signature?(request_body_str, request_sign)
22
+ app_id_result = valid_app_id?(request_body_str)
23
+
24
+ raise 'Bad Request: invalid signature' unless sign_result
25
+ raise 'Bad Request: invalid application id' unless app_id_result
26
+
27
+ sign_result && app_id_result
28
+ end
29
+
30
+ def valid_signature?(request_body_str, request_sign)
31
+ key = OpenSSL::PKey::RSA.new clova_public_key
32
+ key.public_key.verify(OpenSSL::Digest::SHA256.new, Base64.decode64(request_sign), request_body_str)
33
+ end
34
+
35
+ def valid_app_id?(request_body_str)
36
+ request = JSON.parse(request_body_str)
37
+ request_app_id = request['context']['System']['application']['applicationId']
38
+ app_id == request_app_id
39
+ end
40
+
41
+ private
42
+ def clova_public_key
43
+ <<~PEM.chomp
44
+ -----BEGIN PUBLIC KEY-----
45
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwiMvQNKD/WQcX9KiWNMb
46
+ nSR+dJYTWL6TmqqwWFia69TyiobVIfGfxFSefxYyMTcFznoGCpg8aOCAkMxUH58N
47
+ 0/UtWWvfq0U5FQN9McE3zP+rVL3Qul9fbC2mxvazxpv5KT7HEp780Yew777cVPUv
48
+ 3+I73z2t0EHnkwMesmpUA/2Rp8fW8vZE4jfiTRm5vSVmW9F37GC5TEhPwaiIkIin
49
+ KCrH0rXbfe3jNWR7qKOvVDytcWgRHJqRUuWhwJuAnuuqLvqTyAawqEslhKZ5t+1Z
50
+ 0GN8b2zMENSuixa1M9K0ZKUw3unzHpvgBlYmXRGPTSuq/EaGYWyckYz8CBq5Lz2Q
51
+ UwIDAQAB
52
+ -----END PUBLIC KEY-----
53
+ PEM
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,3 @@
1
+ module Clova
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: clova-speech
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Isaiah Bush
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-09-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: openssl
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.16'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.16'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ description: Clova API - SDK of the LINE Clova Smart Speaker API for Ruby. Developed
70
+ as part of an internship at LINE Corp
71
+ email:
72
+ - itbush@cs.stanford.com, itbush2016@gmail.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - README.md
78
+ - lib/clova.rb
79
+ - lib/clova/client.rb
80
+ - lib/clova/config.rb
81
+ - lib/clova/event_request.rb
82
+ - lib/clova/intent_request.rb
83
+ - lib/clova/launch_request.rb
84
+ - lib/clova/output_speech.rb
85
+ - lib/clova/request.rb
86
+ - lib/clova/response.rb
87
+ - lib/clova/session.rb
88
+ - lib/clova/session_ended_request.rb
89
+ - lib/clova/skill.rb
90
+ - lib/clova/validator.rb
91
+ - lib/clova/version.rb
92
+ homepage: https://github.com/itbush/clova-speech
93
+ licenses: []
94
+ metadata: {}
95
+ post_install_message:
96
+ rdoc_options: []
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubyforge_project:
111
+ rubygems_version: 2.7.6
112
+ signing_key:
113
+ specification_version: 4
114
+ summary: SDK of the LINE Clova Smart Speaker
115
+ test_files: []