clova-speech 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []