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.
- checksums.yaml +7 -0
- data/README.md +24 -0
- data/lib/clova.rb +14 -0
- data/lib/clova/client.rb +43 -0
- data/lib/clova/config.rb +10 -0
- data/lib/clova/event_request.rb +14 -0
- data/lib/clova/intent_request.rb +19 -0
- data/lib/clova/launch_request.rb +9 -0
- data/lib/clova/output_speech.rb +66 -0
- data/lib/clova/request.rb +15 -0
- data/lib/clova/response.rb +55 -0
- data/lib/clova/session.rb +22 -0
- data/lib/clova/session_ended_request.rb +10 -0
- data/lib/clova/skill.rb +25 -0
- data/lib/clova/validator.rb +56 -0
- data/lib/clova/version.rb +3 -0
- metadata +115 -0
checksums.yaml
ADDED
@@ -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
|
data/README.md
ADDED
@@ -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
|
+
|
data/lib/clova.rb
ADDED
@@ -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'
|
data/lib/clova/client.rb
ADDED
@@ -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
|
data/lib/clova/config.rb
ADDED
@@ -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,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
|
data/lib/clova/skill.rb
ADDED
@@ -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
|
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: []
|