vonage 7.3.0 → 7.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +28 -1
- data/lib/vonage/version.rb +1 -1
- data/lib/vonage/voice/actions/connect.rb +199 -0
- data/lib/vonage/voice/actions/conversation.rb +107 -0
- data/lib/vonage/voice/actions/input.rb +119 -0
- data/lib/vonage/voice/actions/notify.rb +57 -0
- data/lib/vonage/voice/actions/record.rb +130 -0
- data/lib/vonage/voice/actions/stream.rb +72 -0
- data/lib/vonage/voice/actions/talk.rb +73 -0
- data/lib/vonage/voice/ncco.rb +42 -0
- data/vonage.gemspec +1 -0
- metadata +24 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a62ffd2c9e2a145badeb3e1e232b7959ca2b31c0d7355c1cf804db7e955cb96a
|
4
|
+
data.tar.gz: 6fa0cae4317a331b4218e3d479bff3b7031145c36386c7d19e94d59dfb9cc927
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fc3c593e76119e4c74094db945f74da19e0289766752c18f89f5de8491753b983f0d130e02af4be4c6bbb70c0ee0e73d0b626af96c676b86c15b0fdbd62803c2
|
7
|
+
data.tar.gz: 702152725c3a7a707965e971ba0b4599bac7e62dd842037ad9c4e784d68f0d645c90e203093fff806016cb6efc2f7e5269103f14ba2fe7a1fd255b265bd12ed9
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Vonage Server SDK for Ruby
|
2
2
|
|
3
|
-
[![Gem Version](https://badge.fury.io/rb/vonage.svg)](https://badge.fury.io/rb/vonage) ![Coverage Status](https://github.com/Vonage/vonage-ruby-sdk/workflows/CI/badge.svg) [![codecov](https://codecov.io/gh/Vonage/vonage-ruby-sdk/branch/
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/vonage.svg)](https://badge.fury.io/rb/vonage) ![Coverage Status](https://github.com/Vonage/vonage-ruby-sdk/workflows/CI/badge.svg) [![codecov](https://codecov.io/gh/Vonage/vonage-ruby-sdk/branch/7.x/graph/badge.svg?token=FKW6KL532P)](https://codecov.io/gh/Vonage/vonage-ruby-sdk)
|
4
4
|
|
5
5
|
|
6
6
|
<img src="https://developer.nexmo.com/assets/images/Vonage_Nexmo.svg" height="48px" alt="Nexmo is now known as Vonage" />
|
@@ -16,6 +16,7 @@ need a Vonage account. Sign up [for free at vonage.com][signup].
|
|
16
16
|
* [JWT authentication](#jwt-authentication)
|
17
17
|
* [Webhook signatures](#webhook-signatures)
|
18
18
|
* [Pagination](#pagination)
|
19
|
+
* [NCCO Builder](#ncco-builder)
|
19
20
|
* [Documentation](#documentation)
|
20
21
|
* [Frequently Asked Questions](#frequently-asked-questions)
|
21
22
|
* [Supported APIs](#supported-apis)
|
@@ -167,6 +168,32 @@ To modify the `auto_advance` behavior you can specify it in your method:
|
|
167
168
|
client.applications.list(auto_advance: false)
|
168
169
|
```
|
169
170
|
|
171
|
+
## NCCO Builder
|
172
|
+
|
173
|
+
The Vonage Voice API accepts instructions via JSON objects called NCCOs. Each NCCO can be made up multiple actions that are executed in the order they are written. The Vonage API Developer Portal contains an [NCCO Reference](https://developer.vonage.com/voice/voice-api/ncco-reference) with instructions and information on all the parameters possible.
|
174
|
+
|
175
|
+
The SDK includes an NCCO builder that you can use to build NCCOs for your Voice API methods.
|
176
|
+
|
177
|
+
For example, to build `talk` and `input` NCCO actions and then combine them into a single NCCO you would do the following:
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
talk = Vonage::Voice::Ncco.talk(text: 'Hello World!')
|
181
|
+
input = Vonage::Voice::Ncco.input(type: ['dtmf'], dtmf: { bargeIn: true })
|
182
|
+
ncco = Vonage::Voice::Ncco.build(talk, input)
|
183
|
+
|
184
|
+
# => [{:action=>"talk", :text=>"Hello World!"}, {:action=>"input", :type=>["dtmf"], :dtmf=>{:bargeIn=>true}}]
|
185
|
+
```
|
186
|
+
|
187
|
+
Once you have the constructed NCCO you can then use it in a Voice API request:
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
response = client.voice.create({
|
191
|
+
to: [{type: 'phone', number: '14843331234'}],
|
192
|
+
from: {type: 'phone', number: '14843335555'},
|
193
|
+
ncco: ncco
|
194
|
+
})
|
195
|
+
```
|
196
|
+
|
170
197
|
## Documentation
|
171
198
|
|
172
199
|
Vonage Ruby documentation: https://www.rubydoc.info/github/Vonage/vonage-ruby-sdk
|
data/lib/vonage/version.rb
CHANGED
@@ -0,0 +1,199 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
require 'phonelib'
|
4
|
+
|
5
|
+
module Vonage
|
6
|
+
class Voice::Actions::Connect
|
7
|
+
attr_accessor :endpoint, :from, :eventType, :timeout, :limit, :machineDetection, :eventUrl, :eventMethod, :ringbackTone
|
8
|
+
|
9
|
+
def initialize(attributes = {})
|
10
|
+
@endpoint = attributes.fetch(:endpoint)
|
11
|
+
@from = attributes.fetch(:from, nil)
|
12
|
+
@eventType = attributes.fetch(:eventType, nil)
|
13
|
+
@timeout = attributes.fetch(:timeout, nil)
|
14
|
+
@limit = attributes.fetch(:limit, nil)
|
15
|
+
@machineDetection = attributes.fetch(:machineDetection, nil)
|
16
|
+
@eventUrl = attributes.fetch(:eventUrl, nil)
|
17
|
+
@eventMethod = attributes.fetch(:eventMethod, nil)
|
18
|
+
@ringbackTone = attributes.fetch(:ringbackTone, nil)
|
19
|
+
|
20
|
+
after_initialize!
|
21
|
+
end
|
22
|
+
|
23
|
+
def after_initialize!
|
24
|
+
verify_endpoint
|
25
|
+
|
26
|
+
if self.from
|
27
|
+
verify_from
|
28
|
+
end
|
29
|
+
|
30
|
+
if self.eventType
|
31
|
+
verify_event_type
|
32
|
+
end
|
33
|
+
|
34
|
+
if self.limit
|
35
|
+
verify_limit
|
36
|
+
end
|
37
|
+
|
38
|
+
if self.machineDetection
|
39
|
+
verify_machine_detection
|
40
|
+
end
|
41
|
+
|
42
|
+
if self.eventUrl
|
43
|
+
verify_event_url
|
44
|
+
end
|
45
|
+
|
46
|
+
if self.eventMethod
|
47
|
+
verify_event_method
|
48
|
+
end
|
49
|
+
|
50
|
+
if self.ringbackTone
|
51
|
+
verify_ringback_tone
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def verify_endpoint
|
56
|
+
case self.endpoint[:type]
|
57
|
+
when 'phone'
|
58
|
+
raise ClientError.new("Expected 'number' value to be in E.164 format") unless Phonelib.parse(endpoint[:number].to_i).valid?
|
59
|
+
when 'app'
|
60
|
+
raise ClientError.new("'user' must be defined") unless endpoint[:user]
|
61
|
+
when 'websocket'
|
62
|
+
raise ClientError.new("Expected 'uri' value to be a valid URI") unless URI.parse(endpoint[:uri]).kind_of?(URI::Generic)
|
63
|
+
raise ClientError.new("Expected 'content-type' parameter to be either 'audio/116;rate=16000' or 'audio/116;rate=8000") unless endpoint[:'content-type'] == 'audio/116;rate=16000' || endpoint[:'content-type'] == 'audio/116;rate=8000'
|
64
|
+
when 'sip'
|
65
|
+
raise ClientError.new("Expected 'uri' value to be a valid URI") unless URI.parse(endpoint[:uri]).kind_of?(URI::Generic)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def verify_from
|
70
|
+
raise ClientError.new("Invalid 'from' value, must be in E.164 format") unless Phonelib.parse(self.from.to_i).valid?
|
71
|
+
end
|
72
|
+
|
73
|
+
def verify_event_type
|
74
|
+
raise ClientError.new("Invalid 'eventType' value, must be 'synchronous' if defined") unless self.eventType == 'synchronous'
|
75
|
+
end
|
76
|
+
|
77
|
+
def verify_limit
|
78
|
+
raise ClientError.new("Invalid 'limit' value, must be between 0 and 7200 seconds") unless self.limit.to_i >= 0 && self.limit.to_i <= 7200
|
79
|
+
end
|
80
|
+
|
81
|
+
def verify_machine_detection
|
82
|
+
raise ClientError.new("Invalid 'machineDetection' value, must be either: 'continue' or 'hangup' if defined") unless self.machineDetection == 'continue' || self.machineDetection == 'hangup'
|
83
|
+
end
|
84
|
+
|
85
|
+
def verify_event_url
|
86
|
+
uri = URI.parse(self.eventUrl)
|
87
|
+
|
88
|
+
raise ClientError.new("Invalid 'eventUrl' value, must be a valid URL") unless uri.kind_of?(URI::HTTP) || uri.kind_of?(URI::HTTPS)
|
89
|
+
|
90
|
+
self.eventUrl
|
91
|
+
end
|
92
|
+
|
93
|
+
def verify_event_method
|
94
|
+
valid_methods = ['GET', 'POST']
|
95
|
+
|
96
|
+
raise ClientError.new("Invalid 'eventMethod' value. must be either: 'GET' or 'POST'") unless valid_methods.include?(self.eventMethod.upcase)
|
97
|
+
end
|
98
|
+
|
99
|
+
def verify_ringback_tone
|
100
|
+
uri = URI.parse(self.ringbackTone)
|
101
|
+
|
102
|
+
raise ClientError.new("Invalid 'ringbackTone' value, must be a valid URL") unless uri.kind_of?(URI::HTTP) || uri.kind_of?(URI::HTTPS)
|
103
|
+
|
104
|
+
self.ringbackTone
|
105
|
+
end
|
106
|
+
|
107
|
+
def action
|
108
|
+
create_connect!(self)
|
109
|
+
end
|
110
|
+
|
111
|
+
def create_connect!(builder)
|
112
|
+
ncco = [
|
113
|
+
{
|
114
|
+
action: 'connect',
|
115
|
+
endpoint: [
|
116
|
+
create_endpoint(builder)
|
117
|
+
]
|
118
|
+
}
|
119
|
+
]
|
120
|
+
|
121
|
+
ncco[0].merge!(from: builder.from) if builder.from
|
122
|
+
ncco[0].merge!(eventType: builder.eventType) if builder.eventType
|
123
|
+
ncco[0].merge!(timeout: builder.timeout) if builder.timeout
|
124
|
+
ncco[0].merge!(limit: builder.limit) if builder.limit
|
125
|
+
ncco[0].merge!(machineDetection: builder.machineDetection) if builder.machineDetection
|
126
|
+
ncco[0].merge!(eventUrl: builder.eventUrl) if builder.eventUrl
|
127
|
+
ncco[0].merge!(eventMethod: builder.eventMethod) if builder.eventMethod
|
128
|
+
ncco[0].merge!(ringbackTone: builder.ringbackTone) if builder.ringbackTone
|
129
|
+
|
130
|
+
ncco
|
131
|
+
end
|
132
|
+
|
133
|
+
def create_endpoint(builder)
|
134
|
+
case builder.endpoint[:type]
|
135
|
+
when 'phone'
|
136
|
+
phone_endpoint(builder.endpoint)
|
137
|
+
when 'app'
|
138
|
+
app_endpoint(builder.endpoint)
|
139
|
+
when 'websocket'
|
140
|
+
websocket_endpoint(builder.endpoint)
|
141
|
+
when 'sip'
|
142
|
+
sip_endpoint(builder.endpoint)
|
143
|
+
when 'vbc'
|
144
|
+
vbc_endpoint(builder.endpoint)
|
145
|
+
else
|
146
|
+
raise ClientError.new("Invalid value for 'endpoint', please refer to the Vonage API Developer Portal https://developer.nexmo.com/voice/voice-api/ncco-reference#endpoint-types-and-values for a list of possible values")
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def phone_endpoint(endpoint_attrs)
|
151
|
+
hash = {
|
152
|
+
type: 'phone',
|
153
|
+
number: endpoint_attrs[:number]
|
154
|
+
}
|
155
|
+
|
156
|
+
hash.merge!(dtmfAnswer: endpoint_attrs[:dtmfAnswer]) if endpoint_attrs[:dtmfAnswer]
|
157
|
+
hash.merge!(onAnswer: endpoint_attrs[:onAnswer]) if endpoint_attrs[:onAnswer]
|
158
|
+
|
159
|
+
hash
|
160
|
+
end
|
161
|
+
|
162
|
+
def app_endpoint(endpoint_attrs)
|
163
|
+
{
|
164
|
+
type: 'app',
|
165
|
+
user: endpoint_attrs[:user]
|
166
|
+
}
|
167
|
+
end
|
168
|
+
|
169
|
+
def websocket_endpoint(endpoint_attrs)
|
170
|
+
hash = {
|
171
|
+
type: 'websocket',
|
172
|
+
uri: endpoint_attrs[:uri],
|
173
|
+
:'content-type' => endpoint_attrs[:'content-type']
|
174
|
+
}
|
175
|
+
|
176
|
+
hash.merge!(headers: endpoint_attrs[:headers]) if endpoint_attrs[:headers]
|
177
|
+
|
178
|
+
hash
|
179
|
+
end
|
180
|
+
|
181
|
+
def sip_endpoint(endpoint_attrs)
|
182
|
+
hash = {
|
183
|
+
type: 'sip',
|
184
|
+
uri: endpoint_attrs[:uri]
|
185
|
+
}
|
186
|
+
|
187
|
+
hash.merge!(headers: endpoint_attrs[:headers]) if endpoint_attrs[:headers]
|
188
|
+
|
189
|
+
hash
|
190
|
+
end
|
191
|
+
|
192
|
+
def vbc_endpoint(endpoint_attrs)
|
193
|
+
{
|
194
|
+
type: 'vbc',
|
195
|
+
extension: endpoint_attrs[:extension]
|
196
|
+
}
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Vonage
|
5
|
+
class Voice::Actions::Conversation
|
6
|
+
attr_accessor :name, :musicOnHoldUrl, :startOnEnter, :endOnExit, :record, :canSpeak, :canHear, :mute
|
7
|
+
|
8
|
+
def initialize(attributes = {})
|
9
|
+
@name = attributes.fetch(:name)
|
10
|
+
@musicOnHoldUrl = attributes.fetch(:musicOnHoldUrl, nil)
|
11
|
+
@startOnEnter = attributes.fetch(:startOnEnter, nil)
|
12
|
+
@endOnExit = attributes.fetch(:endOnExit, nil)
|
13
|
+
@record = attributes.fetch(:record, nil)
|
14
|
+
@canSpeak = attributes.fetch(:canSpeak, nil)
|
15
|
+
@canHear = attributes.fetch(:canHear, nil)
|
16
|
+
@mute = attributes.fetch(:mute, nil)
|
17
|
+
|
18
|
+
after_initialize!
|
19
|
+
end
|
20
|
+
|
21
|
+
def after_initialize!
|
22
|
+
if self.musicOnHoldUrl
|
23
|
+
verify_music_on_hold_url
|
24
|
+
end
|
25
|
+
|
26
|
+
if self.startOnEnter
|
27
|
+
verify_start_on_enter
|
28
|
+
end
|
29
|
+
|
30
|
+
if self.endOnExit
|
31
|
+
verify_end_on_exit
|
32
|
+
end
|
33
|
+
|
34
|
+
if self.record
|
35
|
+
verify_record
|
36
|
+
end
|
37
|
+
|
38
|
+
if self.canSpeak
|
39
|
+
verify_can_speak
|
40
|
+
end
|
41
|
+
|
42
|
+
if self.canHear
|
43
|
+
verify_can_hear
|
44
|
+
end
|
45
|
+
|
46
|
+
if self.mute
|
47
|
+
verify_mute
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def verify_music_on_hold_url
|
52
|
+
uri = URI.parse(self.musicOnHoldUrl)
|
53
|
+
|
54
|
+
raise ClientError.new("Invalid 'musicOnHoldUrl' value, must be a valid URL") unless uri.kind_of?(URI::HTTP) || uri.kind_of?(URI::HTTPS)
|
55
|
+
|
56
|
+
self.musicOnHoldUrl
|
57
|
+
end
|
58
|
+
|
59
|
+
def verify_start_on_enter
|
60
|
+
raise ClientError.new("Expected 'startOnEnter' value to be a Boolean") unless self.startOnEnter == true || self.startOnEnter == false
|
61
|
+
end
|
62
|
+
|
63
|
+
def verify_end_on_exit
|
64
|
+
raise ClientError.new("Expected 'endOnExit' value to be a Boolean") unless self.endOnExit == true || self.endOnExit == false
|
65
|
+
end
|
66
|
+
|
67
|
+
def verify_record
|
68
|
+
raise ClientError.new("Expected 'record' value to be a Boolean") unless self.record == true || self.record == false
|
69
|
+
end
|
70
|
+
|
71
|
+
def verify_can_speak
|
72
|
+
raise ClientError.new("Expected 'canSpeak' value to be an Array of leg UUIDs") unless self.canSpeak.is_a?(Array)
|
73
|
+
end
|
74
|
+
|
75
|
+
def verify_can_hear
|
76
|
+
raise ClientError.new("Expected 'canHear' value to be an Array of leg UUIDs") unless self.canHear.is_a?(Array)
|
77
|
+
end
|
78
|
+
|
79
|
+
def verify_mute
|
80
|
+
raise ClientError.new("Expected 'mute' value to be a Boolean") unless self.mute == true || self.mute == false
|
81
|
+
raise ClientError.new("The 'mute' value is not supported if the 'canSpeak' option is defined") if self.canSpeak
|
82
|
+
end
|
83
|
+
|
84
|
+
def action
|
85
|
+
create_conversation!(self)
|
86
|
+
end
|
87
|
+
|
88
|
+
def create_conversation!(builder)
|
89
|
+
ncco = [
|
90
|
+
{
|
91
|
+
action: 'conversation',
|
92
|
+
name: builder.name
|
93
|
+
}
|
94
|
+
]
|
95
|
+
|
96
|
+
ncco[0].merge!(musicOnHoldUrl: builder.musicOnHoldUrl) if (builder.musicOnHoldUrl || builder.musicOnHoldUrl == false)
|
97
|
+
ncco[0].merge!(startOnEnter: builder.startOnEnter) if (builder.startOnEnter || builder.startOnEnter == false)
|
98
|
+
ncco[0].merge!(endOnExit: builder.endOnExit) if (builder.endOnExit || builder.endOnExit == false)
|
99
|
+
ncco[0].merge!(record: builder.record) if builder.record
|
100
|
+
ncco[0].merge!(canSpeak: builder.canSpeak) if builder.canSpeak
|
101
|
+
ncco[0].merge!(canHear: builder.canHear) if builder.canHear
|
102
|
+
ncco[0].merge!(mute: builder.mute) if builder.mute
|
103
|
+
|
104
|
+
ncco
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Vonage
|
5
|
+
class Voice::Actions::Input
|
6
|
+
attr_accessor :type, :dtmf, :speech, :eventUrl, :eventMethod
|
7
|
+
|
8
|
+
def initialize(attributes = {})
|
9
|
+
@type = attributes.fetch(:type)
|
10
|
+
@dtmf = attributes.fetch(:dtmf, nil)
|
11
|
+
@speech = attributes.fetch(:speech, nil)
|
12
|
+
@eventUrl = attributes.fetch(:eventUrl, nil)
|
13
|
+
@eventMethod = attributes.fetch(:eventMethod, nil)
|
14
|
+
|
15
|
+
after_initialize!
|
16
|
+
end
|
17
|
+
|
18
|
+
def after_initialize!
|
19
|
+
validate_type
|
20
|
+
|
21
|
+
if self.dtmf
|
22
|
+
validate_dtmf
|
23
|
+
end
|
24
|
+
|
25
|
+
if self.speech
|
26
|
+
validate_speech
|
27
|
+
end
|
28
|
+
|
29
|
+
if self.eventUrl
|
30
|
+
validate_event_url
|
31
|
+
end
|
32
|
+
|
33
|
+
if self.eventMethod
|
34
|
+
validate_event_method
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def validate_type
|
39
|
+
valid_types = ['dtmf', 'speech']
|
40
|
+
|
41
|
+
raise ClientError.new("Invalid 'type', must be an Array of at least one String") unless self.type.is_a?(Array)
|
42
|
+
raise ClientError.new("Invalid 'type' value, must be 'dtmf', 'speech' or both 'dtmf' and 'speech'") if (valid_types & self.type).empty?
|
43
|
+
end
|
44
|
+
|
45
|
+
def validate_dtmf
|
46
|
+
raise ClientError.new("Expected 'dtmf' to be included in 'type' parameter if 'dtmf' options specified") unless self.type.include?('dtmf')
|
47
|
+
|
48
|
+
if self.dtmf[:timeOut]
|
49
|
+
raise ClientError.new("Expected 'timeOut' to not be more than 10 seconds") if self.dtmf[:timeOut] > 10
|
50
|
+
end
|
51
|
+
|
52
|
+
if self.dtmf[:maxDigits]
|
53
|
+
raise ClientError.new("Expected 'maxDigits' to not be more than 22") if self.dtmf[:maxDigits] > 22
|
54
|
+
end
|
55
|
+
|
56
|
+
if self.dtmf[:submitOnHash]
|
57
|
+
raise ClientError.new("Invalid 'submitOnHash' value, must be a Boolean") unless self.dtmf[:submitOnHash] == true || self.dtmf[:submitOnHash] == false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def validate_speech
|
62
|
+
raise ClientError.new("Expected 'speech' to be included in 'type' parameter if 'speech' options specified") unless self.type.include?('speech')
|
63
|
+
|
64
|
+
if self.speech[:uuid]
|
65
|
+
raise ClientError.new("Invalid 'uuid' value, must be an Array containing a single call leg ID element") unless self.speech[:uuid].is_a?(Array)
|
66
|
+
end
|
67
|
+
|
68
|
+
if self.speech[:endOnSilence]
|
69
|
+
raise ClientError.new("Expected 'endOnSilence' to not be more than 10 seconds") unless self.speech[:endOnSilence] <= 10 && self.speech[:endOnSilence] >= 0
|
70
|
+
end
|
71
|
+
|
72
|
+
if self.speech[:context]
|
73
|
+
raise ClientError.new("Expected 'context' to be an Array of strings") unless self.speech[:context].is_a?(Array)
|
74
|
+
end
|
75
|
+
|
76
|
+
if self.speech[:startTimeout]
|
77
|
+
raise ClientError.new("Expected 'startTimeout' to not be more than 10 seconds") unless self.speech[:startTimeout] <= 10 && self.speech[:startTimeout] >= 0
|
78
|
+
end
|
79
|
+
|
80
|
+
if self.speech[:maxDuration]
|
81
|
+
raise ClientError.new("Expected 'maxDuration' to not be more than 60 seconds") unless self.speech[:maxDuration] <= 60 && self.speech[:maxDuration] >= 0
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def validate_event_url
|
86
|
+
uri = URI.parse(self.eventUrl)
|
87
|
+
|
88
|
+
raise ClientError.new("Invalid 'eventUrl' value, must be a valid URL") unless uri.kind_of?(URI::HTTP) || uri.kind_of?(URI::HTTPS)
|
89
|
+
|
90
|
+
self.eventUrl
|
91
|
+
end
|
92
|
+
|
93
|
+
def validate_event_method
|
94
|
+
valid_methods = ['GET', 'POST']
|
95
|
+
|
96
|
+
raise ClientError.new("Invalid 'eventMethod' value. must be either: 'GET' or 'POST'") unless valid_methods.include?(self.eventMethod.upcase)
|
97
|
+
end
|
98
|
+
|
99
|
+
def action
|
100
|
+
create_input!(self)
|
101
|
+
end
|
102
|
+
|
103
|
+
def create_input!(builder)
|
104
|
+
ncco = [
|
105
|
+
{
|
106
|
+
action: 'input',
|
107
|
+
type: builder.type
|
108
|
+
}
|
109
|
+
]
|
110
|
+
|
111
|
+
ncco[0].merge!(dtmf: builder.dtmf) if builder.dtmf
|
112
|
+
ncco[0].merge!(speech: builder.speech) if builder.speech
|
113
|
+
ncco[0].merge!(eventUrl: builder.eventUrl) if builder.eventUrl
|
114
|
+
ncco[0].merge!(eventMethod: builder.eventMethod) if builder.eventMethod
|
115
|
+
|
116
|
+
ncco
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Vonage
|
5
|
+
class Voice::Actions::Notify
|
6
|
+
attr_accessor :payload, :eventUrl, :eventMethod
|
7
|
+
|
8
|
+
def initialize(attributes = {})
|
9
|
+
@payload = attributes.fetch(:payload)
|
10
|
+
@eventUrl = attributes.fetch(:eventUrl)
|
11
|
+
@eventMethod = attributes.fetch(:eventMethod, nil)
|
12
|
+
|
13
|
+
after_initialize!
|
14
|
+
end
|
15
|
+
|
16
|
+
def after_initialize!
|
17
|
+
validate_event_url
|
18
|
+
|
19
|
+
if self.eventMethod
|
20
|
+
validate_event_method
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate_event_url
|
25
|
+
uri = URI.parse(self.eventUrl[0])
|
26
|
+
|
27
|
+
raise ClientError.new("Expected 'eventUrl' value to be an Array with a single string") unless self.eventUrl.is_a?(Array)
|
28
|
+
raise ClientError.new("Invalid 'eventUrl' value, must be a valid URL") unless uri.kind_of?(URI::HTTP) || uri.kind_of?(URI::HTTPS)
|
29
|
+
|
30
|
+
self.eventUrl
|
31
|
+
end
|
32
|
+
|
33
|
+
def validate_event_method
|
34
|
+
valid_methods = ['GET', 'POST']
|
35
|
+
|
36
|
+
raise ClientError.new("Invalid 'eventMethod' value. must be either: 'GET' or 'POST'") unless valid_methods.include?(self.eventMethod.upcase)
|
37
|
+
end
|
38
|
+
|
39
|
+
def action
|
40
|
+
create_notify!(self)
|
41
|
+
end
|
42
|
+
|
43
|
+
def create_notify!(builder)
|
44
|
+
ncco = [
|
45
|
+
{
|
46
|
+
action: 'notify',
|
47
|
+
payload: builder.payload,
|
48
|
+
eventUrl: builder.eventUrl
|
49
|
+
}
|
50
|
+
]
|
51
|
+
|
52
|
+
ncco[0].merge!(eventMethod: builder.eventMethod) if builder.eventMethod
|
53
|
+
|
54
|
+
ncco
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Vonage
|
5
|
+
class Voice::Actions::Record
|
6
|
+
attr_accessor :format, :split, :channels, :endOnSilence, :endOnKey, :timeOut, :beepStart, :eventUrl, :eventMethod
|
7
|
+
|
8
|
+
def initialize(attributes = {})
|
9
|
+
@format = attributes.fetch(:format, nil)
|
10
|
+
@split = attributes.fetch(:split, nil)
|
11
|
+
@channels = attributes.fetch(:channels, nil)
|
12
|
+
@endOnSilence = attributes.fetch(:endOnSilence, nil)
|
13
|
+
@endOnKey = attributes.fetch(:endOnKey, nil)
|
14
|
+
@timeOut = attributes.fetch(:timeOut, nil)
|
15
|
+
@beepStart = attributes.fetch(:beepStart, nil)
|
16
|
+
@eventUrl = attributes.fetch(:eventUrl, nil)
|
17
|
+
@eventMethod = attributes.fetch(:eventMethod, nil)
|
18
|
+
|
19
|
+
after_initialize!
|
20
|
+
end
|
21
|
+
|
22
|
+
def after_initialize!
|
23
|
+
if self.format
|
24
|
+
validate_format
|
25
|
+
end
|
26
|
+
|
27
|
+
if self.split
|
28
|
+
validate_split
|
29
|
+
end
|
30
|
+
|
31
|
+
if self.channels
|
32
|
+
validate_channels
|
33
|
+
end
|
34
|
+
|
35
|
+
if self.endOnSilence
|
36
|
+
validate_end_on_silence
|
37
|
+
end
|
38
|
+
|
39
|
+
if self.endOnKey
|
40
|
+
validate_end_on_key
|
41
|
+
end
|
42
|
+
|
43
|
+
if self.timeOut
|
44
|
+
validate_time_out
|
45
|
+
end
|
46
|
+
|
47
|
+
if self.beepStart
|
48
|
+
validate_beep_start
|
49
|
+
end
|
50
|
+
|
51
|
+
if self.eventUrl
|
52
|
+
validate_event_url
|
53
|
+
end
|
54
|
+
|
55
|
+
if self.eventMethod
|
56
|
+
validate_event_method
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def validate_format
|
61
|
+
valid_formats = ['mp3', 'wav', 'ogg']
|
62
|
+
|
63
|
+
raise ClientError.new("Invalid format, must be one of: 'mp3', 'wav', 'ogg'") unless valid_formats.include?(self.format)
|
64
|
+
end
|
65
|
+
|
66
|
+
def validate_split
|
67
|
+
raise ClientError.new("Expected 'split' value to be 'conversation' if defined") unless self.split == 'conversation'
|
68
|
+
end
|
69
|
+
|
70
|
+
def validate_channels
|
71
|
+
raise ClientError.new("The 'split' parameter must be defined to 'conversation' to also define 'channels'") unless self.split
|
72
|
+
|
73
|
+
raise ClientError.new("Expected 'split' parameter to be equal to or less than 32") unless self.channels <= 32
|
74
|
+
end
|
75
|
+
|
76
|
+
def validate_end_on_silence
|
77
|
+
raise ClientError.new("Expected 'endOnSilence' value to be between 3 and 10") unless self.endOnSilence <= 10 && self.endOnSilence >= 3
|
78
|
+
end
|
79
|
+
|
80
|
+
def validate_end_on_key
|
81
|
+
raise ClientError.new("Expected 'endOnKey' value to be a one of the following: a single digit between 1-9, '*' or '#'") unless self.endOnKey.match(/^(\*|[1-9]|\#)$/)
|
82
|
+
end
|
83
|
+
|
84
|
+
def validate_time_out
|
85
|
+
raise ClientError.new("Expected 'timeOut' value to be between 3 and 7200 seconds") unless self.timeOut <= 7200 && self.timeOut >= 3
|
86
|
+
end
|
87
|
+
|
88
|
+
def validate_beep_start
|
89
|
+
raise ClientError.new("Expected 'beepStart' value to be a Boolean") unless self.beepStart == true || self.beepStart == false
|
90
|
+
end
|
91
|
+
|
92
|
+
def validate_event_url
|
93
|
+
uri = URI.parse(self.eventUrl)
|
94
|
+
|
95
|
+
raise ClientError.new("Invalid 'eventUrl' value, must be a valid URL") unless uri.kind_of?(URI::HTTP) || uri.kind_of?(URI::HTTPS)
|
96
|
+
|
97
|
+
self.eventUrl
|
98
|
+
end
|
99
|
+
|
100
|
+
def validate_event_method
|
101
|
+
valid_methods = ['GET', 'POST']
|
102
|
+
|
103
|
+
raise ClientError.new("Invalid 'eventMethod' value. must be either: 'GET' or 'POST'") unless valid_methods.include?(self.eventMethod.upcase)
|
104
|
+
end
|
105
|
+
|
106
|
+
def action
|
107
|
+
create_record!(self)
|
108
|
+
end
|
109
|
+
|
110
|
+
def create_record!(builder)
|
111
|
+
ncco = [
|
112
|
+
{
|
113
|
+
action: 'record'
|
114
|
+
}
|
115
|
+
]
|
116
|
+
|
117
|
+
ncco[0].merge!(format: builder.format) if builder.format
|
118
|
+
ncco[0].merge!(split: builder.split) if builder.split
|
119
|
+
ncco[0].merge!(channels: builder.channels) if builder.channels
|
120
|
+
ncco[0].merge!(endOnSilence: builder.endOnSilence) if builder.endOnSilence
|
121
|
+
ncco[0].merge!(endOnKey: builder.endOnKey) if builder.endOnKey
|
122
|
+
ncco[0].merge!(timeOut: builder.timeOut) if builder.timeOut
|
123
|
+
ncco[0].merge!(beepStart: builder.beepStart) if builder.beepStart
|
124
|
+
ncco[0].merge!(eventUrl: builder.eventUrl) if builder.eventUrl
|
125
|
+
ncco[0].merge!(eventMethod: builder.eventMethod) if builder.eventMethod
|
126
|
+
|
127
|
+
ncco
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Vonage
|
5
|
+
class Voice::Actions::Stream
|
6
|
+
attr_accessor :streamUrl, :level, :bargeIn, :loop
|
7
|
+
|
8
|
+
def initialize(attributes = {})
|
9
|
+
@streamUrl = attributes.fetch(:streamUrl)
|
10
|
+
@level = attributes.fetch(:level, nil)
|
11
|
+
@bargeIn = attributes.fetch(:bargeIn, nil)
|
12
|
+
@loop = attributes.fetch(:loop, nil)
|
13
|
+
|
14
|
+
after_initialize!
|
15
|
+
end
|
16
|
+
|
17
|
+
def after_initialize!
|
18
|
+
verify_stream_url
|
19
|
+
|
20
|
+
if self.level
|
21
|
+
verify_level
|
22
|
+
end
|
23
|
+
|
24
|
+
if self.bargeIn
|
25
|
+
verify_barge_in
|
26
|
+
end
|
27
|
+
|
28
|
+
if self.loop
|
29
|
+
verify_loop
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def verify_stream_url
|
34
|
+
raise ClientError.new("Expected 'streamUrl' parameter to be an Array containing a single string item") unless self.streamUrl.is_a?(Array)
|
35
|
+
|
36
|
+
uri = URI.parse(self.streamUrl[0])
|
37
|
+
|
38
|
+
raise ClientError.new("Invalid 'streamUrl' value, must be a valid URL") unless uri.kind_of?(URI::HTTP) || uri.kind_of?(URI::HTTPS)
|
39
|
+
end
|
40
|
+
|
41
|
+
def verify_level
|
42
|
+
raise ClientError.new("Expected 'level' value to be a number between -1 and 1") unless self.level.between?(-1, 1)
|
43
|
+
end
|
44
|
+
|
45
|
+
def verify_barge_in
|
46
|
+
raise ClientError.new("Expected 'bargeIn' value to be a Boolean") unless self.bargeIn == true || self.bargeIn == false
|
47
|
+
end
|
48
|
+
|
49
|
+
def verify_loop
|
50
|
+
raise ClientError.new("Expected 'loop' value to be either 1 or 0") unless self.loop == 1 || self.loop == 0
|
51
|
+
end
|
52
|
+
|
53
|
+
def action
|
54
|
+
create_stream!(self)
|
55
|
+
end
|
56
|
+
|
57
|
+
def create_stream!(builder)
|
58
|
+
ncco = [
|
59
|
+
{
|
60
|
+
action: 'stream',
|
61
|
+
streamUrl: builder.streamUrl
|
62
|
+
}
|
63
|
+
]
|
64
|
+
|
65
|
+
ncco[0].merge!(level: builder.level) if builder.level
|
66
|
+
ncco[0].merge!(bargeIn: builder.bargeIn) if (builder.bargeIn || builder.bargeIn == false)
|
67
|
+
ncco[0].merge!(loop: builder.loop) if builder.loop
|
68
|
+
|
69
|
+
ncco
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
module Vonage
|
4
|
+
class Voice::Actions::Talk
|
5
|
+
attr_accessor :text, :bargeIn, :loop, :level, :language, :style
|
6
|
+
|
7
|
+
def initialize(attributes= {})
|
8
|
+
@text = attributes.fetch(:text)
|
9
|
+
@bargeIn = attributes.fetch(:bargeIn, nil)
|
10
|
+
@loop = attributes.fetch(:loop, nil)
|
11
|
+
@level = attributes.fetch(:level, nil)
|
12
|
+
@language = attributes.fetch(:language, nil)
|
13
|
+
@style = attributes.fetch(:style, nil)
|
14
|
+
|
15
|
+
after_initialize!
|
16
|
+
end
|
17
|
+
|
18
|
+
def after_initialize!
|
19
|
+
if self.bargeIn
|
20
|
+
verify_barge_in
|
21
|
+
end
|
22
|
+
|
23
|
+
if self.loop
|
24
|
+
verify_loop
|
25
|
+
end
|
26
|
+
|
27
|
+
if self.level
|
28
|
+
verify_level
|
29
|
+
end
|
30
|
+
|
31
|
+
if self.style
|
32
|
+
verify_style
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def verify_barge_in
|
37
|
+
raise ClientError.new("Expected 'bargeIn' value to be a Boolean") unless self.bargeIn == true || self.bargeIn == false
|
38
|
+
end
|
39
|
+
|
40
|
+
def verify_loop
|
41
|
+
raise ClientError.new("Expected 'loop' value to be either 1 or 0") unless self.loop == 1 || self.loop == 0
|
42
|
+
end
|
43
|
+
|
44
|
+
def verify_level
|
45
|
+
raise ClientError.new("Expected 'level' value to be a number between -1 and 1") unless self.level.between?(-1, 1)
|
46
|
+
end
|
47
|
+
|
48
|
+
def verify_style
|
49
|
+
raise ClientError.new("Expected 'style' value to be an Integer") unless self.style.is_a?(Integer)
|
50
|
+
end
|
51
|
+
|
52
|
+
def action
|
53
|
+
create_talk!(self)
|
54
|
+
end
|
55
|
+
|
56
|
+
def create_talk!(builder)
|
57
|
+
ncco = [
|
58
|
+
{
|
59
|
+
action: 'talk',
|
60
|
+
text: builder.text
|
61
|
+
}
|
62
|
+
]
|
63
|
+
|
64
|
+
ncco[0].merge!(bargeIn: builder.bargeIn) if (builder.bargeIn || builder.bargeIn == false)
|
65
|
+
ncco[0].merge!(loop: builder.loop) if builder.loop
|
66
|
+
ncco[0].merge!(level: builder.level) if builder.level
|
67
|
+
ncco[0].merge!(language: builder.language) if builder.language
|
68
|
+
ncco[0].merge!(style: builder.style) if builder.style
|
69
|
+
|
70
|
+
ncco
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Vonage
|
5
|
+
class Voice::Ncco
|
6
|
+
ACTIONS = {
|
7
|
+
connect: Vonage::Voice::Actions::Connect,
|
8
|
+
conversation: Vonage::Voice::Actions::Conversation,
|
9
|
+
input: Vonage::Voice::Actions::Input,
|
10
|
+
notify: Vonage::Voice::Actions::Notify,
|
11
|
+
record: Vonage::Voice::Actions::Record,
|
12
|
+
stream: Vonage::Voice::Actions::Stream,
|
13
|
+
talk: Vonage::Voice::Actions::Talk
|
14
|
+
}
|
15
|
+
|
16
|
+
ACTIONS.keys.each do |method|
|
17
|
+
self.class.send :define_method, method do |attributes|
|
18
|
+
ACTIONS[method].new(**attributes).action
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.method_missing(method)
|
23
|
+
raise ClientError.new("NCCO action must be one of the valid options. Please refer to https://developer.nexmo.com/voice/voice-api/ncco-reference#ncco-actions for a complete list.")
|
24
|
+
end
|
25
|
+
|
26
|
+
# Create an NCCO
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# talk = Vonage::Voice::Ncco.talk(text: 'This is sample text')
|
30
|
+
# input = Vonage::Voice::Ncco.input(type: ['dtmf'])
|
31
|
+
# ncco = Vonage::Voice::Ncco.create(talk, input)
|
32
|
+
#
|
33
|
+
# @option actions [Vonage::Voice::Ncco]
|
34
|
+
#
|
35
|
+
# @return [Array]
|
36
|
+
#
|
37
|
+
# @see https://developer.nexmo.com/voice/voice-api/ncco-reference
|
38
|
+
def self.create(*actions)
|
39
|
+
actions.flatten!
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/vonage.gemspec
CHANGED
@@ -16,6 +16,7 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.add_dependency('zeitwerk', '~> 2', '>= 2.2')
|
17
17
|
s.add_dependency('sorbet-runtime', '~> 0.5')
|
18
18
|
s.add_runtime_dependency('rexml')
|
19
|
+
s.add_runtime_dependency('phonelib')
|
19
20
|
s.require_path = 'lib'
|
20
21
|
s.metadata = {
|
21
22
|
'homepage' => 'https://github.com/Vonage/vonage-ruby-sdk',
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vonage
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 7.
|
4
|
+
version: 7.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vonage
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-03-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nexmo-jwt
|
@@ -72,6 +72,20 @@ dependencies:
|
|
72
72
|
- - ">="
|
73
73
|
- !ruby/object:Gem::Version
|
74
74
|
version: '0'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: phonelib
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
type: :runtime
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
75
89
|
description: Vonage Server SDK for Ruby
|
76
90
|
email:
|
77
91
|
- devrel@vonage.com
|
@@ -132,8 +146,16 @@ files:
|
|
132
146
|
- lib/vonage/verify.rb
|
133
147
|
- lib/vonage/version.rb
|
134
148
|
- lib/vonage/voice.rb
|
149
|
+
- lib/vonage/voice/actions/connect.rb
|
150
|
+
- lib/vonage/voice/actions/conversation.rb
|
151
|
+
- lib/vonage/voice/actions/input.rb
|
152
|
+
- lib/vonage/voice/actions/notify.rb
|
153
|
+
- lib/vonage/voice/actions/record.rb
|
154
|
+
- lib/vonage/voice/actions/stream.rb
|
155
|
+
- lib/vonage/voice/actions/talk.rb
|
135
156
|
- lib/vonage/voice/dtmf.rb
|
136
157
|
- lib/vonage/voice/list_response.rb
|
158
|
+
- lib/vonage/voice/ncco.rb
|
137
159
|
- lib/vonage/voice/stream.rb
|
138
160
|
- lib/vonage/voice/talk.rb
|
139
161
|
- vonage.gemspec
|