voiceml 0.7.1.1
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/LICENSE +21 -0
- data/README.md +265 -0
- data/lib/voiceml/client.rb +83 -0
- data/lib/voiceml/errors.rb +75 -0
- data/lib/voiceml/models/applications.rb +46 -0
- data/lib/voiceml/models/calls.rb +48 -0
- data/lib/voiceml/models/common.rb +64 -0
- data/lib/voiceml/models/conferences.rb +85 -0
- data/lib/voiceml/models/diagnostics.rb +56 -0
- data/lib/voiceml/models/incoming_phone_numbers.rb +59 -0
- data/lib/voiceml/models/messages.rb +56 -0
- data/lib/voiceml/models/payments.rb +96 -0
- data/lib/voiceml/models/queues.rb +82 -0
- data/lib/voiceml/models/recordings.rb +59 -0
- data/lib/voiceml/models/siprec.rb +44 -0
- data/lib/voiceml/models/streams.rb +44 -0
- data/lib/voiceml/models/transcriptions.rb +44 -0
- data/lib/voiceml/resources/applications.rb +62 -0
- data/lib/voiceml/resources/base.rb +38 -0
- data/lib/voiceml/resources/calls.rb +414 -0
- data/lib/voiceml/resources/conferences.rb +178 -0
- data/lib/voiceml/resources/diagnostics.rb +64 -0
- data/lib/voiceml/resources/incoming_phone_numbers.rb +162 -0
- data/lib/voiceml/resources/messages.rb +101 -0
- data/lib/voiceml/resources/notifications.rb +32 -0
- data/lib/voiceml/resources/queues.rb +120 -0
- data/lib/voiceml/resources/recordings.rb +89 -0
- data/lib/voiceml/transport.rb +296 -0
- data/lib/voiceml/version.rb +5 -0
- data/lib/voiceml.rb +27 -0
- metadata +118 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module VoiceML
|
|
4
|
+
# @api private
|
|
5
|
+
# Mixin holding a `Transport` reference and helpers for AccountSid-scoped pathing.
|
|
6
|
+
class BaseResource
|
|
7
|
+
def initialize(transport)
|
|
8
|
+
@transport = transport
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
# Build a URL under `/2010-04-01/Accounts/{AccountSid}/...`. Caller passes path segments
|
|
14
|
+
# (e.g. `"Calls"`, sid, `"Recordings"`). Empty segments are skipped; nothing is
|
|
15
|
+
# URL-encoded — sids and slugs never need escaping.
|
|
16
|
+
#
|
|
17
|
+
# As of v0.5.0 every REST endpoint resolves under its `.json` form (Twilio drop-in
|
|
18
|
+
# compatibility). Pass `suffix: ''` to opt out — used by `.wav` audio fetches and any
|
|
19
|
+
# caller that needs to append a different extension.
|
|
20
|
+
def path(*parts, suffix: '.json')
|
|
21
|
+
tail = parts.compact.reject { |p| p.to_s.empty? }.join('/')
|
|
22
|
+
"/2010-04-01/Accounts/#{@transport.account_sid}/#{tail}#{suffix}"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Translate snake_case Ruby kwargs to the form/query field names the server expects.
|
|
26
|
+
# `nil` values are dropped; booleans become "true"/"false" inside Transport.
|
|
27
|
+
def form_params(map, kwargs)
|
|
28
|
+
out = {}
|
|
29
|
+
map.each do |wire_name, ruby_key|
|
|
30
|
+
value = kwargs[ruby_key]
|
|
31
|
+
next if value.nil?
|
|
32
|
+
|
|
33
|
+
out[wire_name] = value
|
|
34
|
+
end
|
|
35
|
+
out
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
require_relative '../models/calls'
|
|
5
|
+
require_relative '../models/recordings'
|
|
6
|
+
require_relative '../models/streams'
|
|
7
|
+
require_relative '../models/siprec'
|
|
8
|
+
require_relative '../models/transcriptions'
|
|
9
|
+
require_relative '../models/diagnostics'
|
|
10
|
+
require_relative '../models/payments'
|
|
11
|
+
|
|
12
|
+
module VoiceML
|
|
13
|
+
# Operations on `/Calls` and call-scoped sub-resources (Recordings, Streams, Siprec,
|
|
14
|
+
# Transcriptions, Notifications, Events, UserDefinedMessages).
|
|
15
|
+
#
|
|
16
|
+
# All methods accept idiomatic snake_case keyword arguments — they're translated to
|
|
17
|
+
# Twilio's PascalCase wire names internally.
|
|
18
|
+
class CallsResource < BaseResource
|
|
19
|
+
# Create-call form-field map (snake_case kwarg -> Twilio wire name).
|
|
20
|
+
CREATE_FIELDS = {
|
|
21
|
+
'To' => :to,
|
|
22
|
+
'From' => :from,
|
|
23
|
+
'Url' => :url,
|
|
24
|
+
'Method' => :method,
|
|
25
|
+
'Twiml' => :twiml,
|
|
26
|
+
'ApplicationSid' => :application_sid,
|
|
27
|
+
'FallbackUrl' => :fallback_url,
|
|
28
|
+
'FallbackMethod' => :fallback_method,
|
|
29
|
+
'StatusCallback' => :status_callback,
|
|
30
|
+
'StatusCallbackMethod' => :status_callback_method,
|
|
31
|
+
'StatusCallbackEvent' => :status_callback_event,
|
|
32
|
+
'MachineDetection' => :machine_detection,
|
|
33
|
+
'MachineDetectionTimeout' => :machine_detection_timeout,
|
|
34
|
+
'MachineDetectionSpeechThreshold' => :machine_detection_speech_threshold,
|
|
35
|
+
'MachineDetectionSpeechEndThreshold' => :machine_detection_speech_end_threshold,
|
|
36
|
+
'MachineDetectionSilenceTimeout' => :machine_detection_silence_timeout,
|
|
37
|
+
'AsyncAmdStatusCallback' => :async_amd_status_callback,
|
|
38
|
+
'AsyncAmdStatusCallbackMethod' => :async_amd_status_callback_method,
|
|
39
|
+
'Record' => :record,
|
|
40
|
+
'RecordingStatusCallback' => :recording_status_callback,
|
|
41
|
+
'RecordingStatusCallbackMethod' => :recording_status_callback_method,
|
|
42
|
+
'RecordingStatusCallbackEvent' => :recording_status_callback_event,
|
|
43
|
+
'RecordingChannels' => :recording_channels,
|
|
44
|
+
'RecordingTrack' => :recording_track,
|
|
45
|
+
'Trim' => :trim,
|
|
46
|
+
'Timeout' => :timeout,
|
|
47
|
+
'SendDigits' => :send_digits,
|
|
48
|
+
'CallerId' => :caller_id,
|
|
49
|
+
'CallReason' => :call_reason,
|
|
50
|
+
'SipAuthUsername' => :sip_auth_username,
|
|
51
|
+
'SipAuthPassword' => :sip_auth_password,
|
|
52
|
+
'Byoc' => :byoc,
|
|
53
|
+
'AsyncAmd' => :async_amd,
|
|
54
|
+
'CallToken' => :call_token
|
|
55
|
+
}.freeze
|
|
56
|
+
|
|
57
|
+
UPDATE_FIELDS = {
|
|
58
|
+
'Status' => :status,
|
|
59
|
+
'Twiml' => :twiml,
|
|
60
|
+
'Url' => :url,
|
|
61
|
+
'Method' => :method,
|
|
62
|
+
'FallbackUrl' => :fallback_url,
|
|
63
|
+
'FallbackMethod' => :fallback_method,
|
|
64
|
+
'StatusCallback' => :status_callback,
|
|
65
|
+
'StatusCallbackMethod' => :status_callback_method,
|
|
66
|
+
'StatusCallbackEvent' => :status_callback_event
|
|
67
|
+
}.freeze
|
|
68
|
+
|
|
69
|
+
LIST_FIELDS = {
|
|
70
|
+
'To' => :to,
|
|
71
|
+
'From' => :from,
|
|
72
|
+
'Status' => :status,
|
|
73
|
+
'ParentCallSid' => :parent_call_sid,
|
|
74
|
+
'StartTime' => :start_time,
|
|
75
|
+
'StartTime<' => :start_time_lt,
|
|
76
|
+
'StartTime>' => :start_time_gt,
|
|
77
|
+
# Note: spec defines `StartTime>=` and `StartTime<=` as the literal query names.
|
|
78
|
+
'StartTime>=' => :start_time_gte,
|
|
79
|
+
'StartTime<=' => :start_time_lte,
|
|
80
|
+
'EndTime' => :end_time,
|
|
81
|
+
'EndTime<' => :end_time_lt,
|
|
82
|
+
'EndTime>' => :end_time_gt,
|
|
83
|
+
'Page' => :page,
|
|
84
|
+
'PageSize' => :page_size,
|
|
85
|
+
'PageToken' => :page_token
|
|
86
|
+
}.freeze
|
|
87
|
+
|
|
88
|
+
LIST_RECORDINGS_FIELDS = {
|
|
89
|
+
'DateCreated' => :date_created,
|
|
90
|
+
'DateCreated<' => :date_created_lt,
|
|
91
|
+
'DateCreated>' => :date_created_gt,
|
|
92
|
+
'Page' => :page,
|
|
93
|
+
'PageSize' => :page_size,
|
|
94
|
+
'PageToken' => :page_token
|
|
95
|
+
}.freeze
|
|
96
|
+
|
|
97
|
+
LIST_STUB_PAGE_FIELDS = {
|
|
98
|
+
'Page' => :page,
|
|
99
|
+
'PageSize' => :page_size,
|
|
100
|
+
'PageToken' => :page_token
|
|
101
|
+
}.freeze
|
|
102
|
+
|
|
103
|
+
LIST_NOTIFICATIONS_FIELDS = {
|
|
104
|
+
'Page' => :page,
|
|
105
|
+
'PageSize' => :page_size,
|
|
106
|
+
'PageToken' => :page_token,
|
|
107
|
+
'Log' => :log,
|
|
108
|
+
'MessageDate' => :message_date,
|
|
109
|
+
'MessageDate<' => :message_date_lt,
|
|
110
|
+
'MessageDate>' => :message_date_gt
|
|
111
|
+
}.freeze
|
|
112
|
+
|
|
113
|
+
START_RECORDING_FIELDS = {
|
|
114
|
+
'RecordingMaxDuration' => :recording_max_duration,
|
|
115
|
+
'RecordingChannels' => :recording_channels,
|
|
116
|
+
'PlayBeep' => :play_beep,
|
|
117
|
+
'RecordingStatusCallback' => :recording_status_callback,
|
|
118
|
+
'RecordingStatusCallbackMethod' => :recording_status_callback_method,
|
|
119
|
+
'RecordingStatusCallbackEvent' => :recording_status_callback_event
|
|
120
|
+
}.freeze
|
|
121
|
+
|
|
122
|
+
START_STREAM_FIELDS = {
|
|
123
|
+
'Url' => :url,
|
|
124
|
+
'Track' => :track,
|
|
125
|
+
'Name' => :name,
|
|
126
|
+
'StatusCallback' => :status_callback,
|
|
127
|
+
'StatusCallbackMethod' => :status_callback_method
|
|
128
|
+
}.freeze
|
|
129
|
+
|
|
130
|
+
START_SIPREC_FIELDS = {
|
|
131
|
+
'Name' => :name,
|
|
132
|
+
'ConnectorName' => :connector_name,
|
|
133
|
+
'Track' => :track,
|
|
134
|
+
'StatusCallback' => :status_callback,
|
|
135
|
+
'StatusCallbackMethod' => :status_callback_method
|
|
136
|
+
}.freeze
|
|
137
|
+
|
|
138
|
+
START_TRANSCRIPTION_FIELDS = {
|
|
139
|
+
'Name' => :name,
|
|
140
|
+
'Track' => :track,
|
|
141
|
+
'LanguageCode' => :language_code,
|
|
142
|
+
'TranscriptionEngine' => :transcription_engine,
|
|
143
|
+
'ProfanityFilter' => :profanity_filter,
|
|
144
|
+
'PartialResults' => :partial_results,
|
|
145
|
+
'Hints' => :hints,
|
|
146
|
+
'StatusCallback' => :status_callback,
|
|
147
|
+
'StatusCallbackMethod' => :status_callback_method,
|
|
148
|
+
'StatusCallbackEvents' => :status_callback_events
|
|
149
|
+
}.freeze
|
|
150
|
+
|
|
151
|
+
START_PAYMENT_FIELDS = {
|
|
152
|
+
'IdempotencyKey' => :idempotency_key,
|
|
153
|
+
'StatusCallback' => :status_callback,
|
|
154
|
+
'BankAccountType' => :bank_account_type,
|
|
155
|
+
'ChargeAmount' => :charge_amount,
|
|
156
|
+
'Currency' => :currency,
|
|
157
|
+
'Description' => :description,
|
|
158
|
+
'Input' => :input,
|
|
159
|
+
'MinPostalCodeLength' => :min_postal_code_length,
|
|
160
|
+
'Parameter' => :parameter,
|
|
161
|
+
'PaymentConnector' => :payment_connector,
|
|
162
|
+
'PaymentMethod' => :payment_method,
|
|
163
|
+
'PostalCode' => :postal_code,
|
|
164
|
+
'SecurityCode' => :security_code,
|
|
165
|
+
'Timeout' => :timeout,
|
|
166
|
+
'TokenType' => :token_type,
|
|
167
|
+
'ValidCardTypes' => :valid_card_types,
|
|
168
|
+
'RequireMatchingInputs' => :require_matching_inputs,
|
|
169
|
+
'Confirmation' => :confirmation
|
|
170
|
+
}.freeze
|
|
171
|
+
|
|
172
|
+
UPDATE_PAYMENT_FIELDS = {
|
|
173
|
+
'IdempotencyKey' => :idempotency_key,
|
|
174
|
+
'StatusCallback' => :status_callback,
|
|
175
|
+
'Capture' => :capture,
|
|
176
|
+
'Status' => :status
|
|
177
|
+
}.freeze
|
|
178
|
+
|
|
179
|
+
# @return [VoiceML::CallList]
|
|
180
|
+
def list(**kwargs)
|
|
181
|
+
data = @transport.request(:get, path('Calls'), params: form_params(LIST_FIELDS, kwargs))
|
|
182
|
+
CallList.from_hash(data)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Walk every page of /Calls and yield each Call. Returns an Enumerator when
|
|
186
|
+
# called without a block.
|
|
187
|
+
#
|
|
188
|
+
# @yield [VoiceML::Call]
|
|
189
|
+
# @return [Enumerator<VoiceML::Call>] when no block given
|
|
190
|
+
def each(**kwargs, &block)
|
|
191
|
+
return enum_for(:each, **kwargs) unless block
|
|
192
|
+
|
|
193
|
+
page_num = kwargs.delete(:page) || 0
|
|
194
|
+
loop do
|
|
195
|
+
chunk = list(**kwargs, page: page_num)
|
|
196
|
+
chunk.calls.each(&block)
|
|
197
|
+
break if chunk.next_page_uri.nil? || chunk.next_page_uri.empty? || chunk.calls.empty?
|
|
198
|
+
page_num += 1
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Create a new outbound call.
|
|
203
|
+
#
|
|
204
|
+
# Pass at most one of `url:` / `twiml:` / `application_sid:` (Twiml wins if multiple are set
|
|
205
|
+
# — Twilio's documented precedence).
|
|
206
|
+
#
|
|
207
|
+
# @return [VoiceML::Call]
|
|
208
|
+
def create(**kwargs)
|
|
209
|
+
data = @transport.request(:post, path('Calls'), form: form_params(CREATE_FIELDS, kwargs))
|
|
210
|
+
Call.from_hash(data)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# @return [VoiceML::Call]
|
|
214
|
+
def get(call_sid)
|
|
215
|
+
data = @transport.request(:get, path('Calls', call_sid))
|
|
216
|
+
Call.from_hash(data)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# @return [VoiceML::Call]
|
|
220
|
+
def update(call_sid, **kwargs)
|
|
221
|
+
data = @transport.request(:post, path('Calls', call_sid),
|
|
222
|
+
form: form_params(UPDATE_FIELDS, kwargs))
|
|
223
|
+
Call.from_hash(data)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# @return [nil]
|
|
227
|
+
def delete(call_sid)
|
|
228
|
+
@transport.request(:delete, path('Calls', call_sid))
|
|
229
|
+
nil
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# --- Call-scoped Recordings ---
|
|
233
|
+
|
|
234
|
+
# @return [VoiceML::RecordingList]
|
|
235
|
+
def list_recordings(call_sid, **kwargs)
|
|
236
|
+
RecordingList.from_hash(
|
|
237
|
+
@transport.request(:get, path('Calls', call_sid, 'Recordings'),
|
|
238
|
+
params: form_params(LIST_RECORDINGS_FIELDS, kwargs))
|
|
239
|
+
)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# @return [VoiceML::Recording]
|
|
243
|
+
def start_recording(call_sid, **kwargs)
|
|
244
|
+
data = @transport.request(:post, path('Calls', call_sid, 'Recordings'),
|
|
245
|
+
form: form_params(START_RECORDING_FIELDS, kwargs))
|
|
246
|
+
Recording.from_hash(data)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# @return [VoiceML::Recording]
|
|
250
|
+
def get_recording(call_sid, recording_sid)
|
|
251
|
+
Recording.from_hash(
|
|
252
|
+
@transport.request(:get, path('Calls', call_sid, 'Recordings', recording_sid))
|
|
253
|
+
)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# @param status [String] one of "in-progress", "paused", "stopped".
|
|
257
|
+
# @return [VoiceML::Recording]
|
|
258
|
+
def update_recording(call_sid, recording_sid, status:)
|
|
259
|
+
data = @transport.request(:post, path('Calls', call_sid, 'Recordings', recording_sid),
|
|
260
|
+
form: { 'Status' => status })
|
|
261
|
+
Recording.from_hash(data)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# @return [nil]
|
|
265
|
+
def delete_recording(call_sid, recording_sid)
|
|
266
|
+
@transport.request(:delete, path('Calls', call_sid, 'Recordings', recording_sid))
|
|
267
|
+
nil
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# --- Streams ---
|
|
271
|
+
|
|
272
|
+
# @return [VoiceML::StreamList]
|
|
273
|
+
def list_streams(call_sid)
|
|
274
|
+
StreamList.from_hash(@transport.request(:get, path('Calls', call_sid, 'Streams')))
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# @return [VoiceML::Stream]
|
|
278
|
+
def start_stream(call_sid, **kwargs)
|
|
279
|
+
data = @transport.request(:post, path('Calls', call_sid, 'Streams'),
|
|
280
|
+
form: form_params(START_STREAM_FIELDS, kwargs))
|
|
281
|
+
Stream.from_hash(data)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# @return [VoiceML::Stream]
|
|
285
|
+
def get_stream(call_sid, stream_sid)
|
|
286
|
+
Stream.from_hash(@transport.request(:get, path('Calls', call_sid, 'Streams', stream_sid)))
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# @return [VoiceML::Stream]
|
|
290
|
+
def stop_stream(call_sid, stream_sid)
|
|
291
|
+
data = @transport.request(:post, path('Calls', call_sid, 'Streams', stream_sid),
|
|
292
|
+
form: { 'Status' => 'stopped' })
|
|
293
|
+
Stream.from_hash(data)
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# --- SIPREC ---
|
|
297
|
+
|
|
298
|
+
# @return [VoiceML::SiprecList]
|
|
299
|
+
def list_siprec(call_sid)
|
|
300
|
+
SiprecList.from_hash(@transport.request(:get, path('Calls', call_sid, 'Siprec')))
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# @return [VoiceML::SiprecSession]
|
|
304
|
+
def start_siprec(call_sid, **kwargs)
|
|
305
|
+
data = @transport.request(:post, path('Calls', call_sid, 'Siprec'),
|
|
306
|
+
form: form_params(START_SIPREC_FIELDS, kwargs))
|
|
307
|
+
SiprecSession.from_hash(data)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# @return [VoiceML::SiprecSession]
|
|
311
|
+
def get_siprec(call_sid, siprec_sid)
|
|
312
|
+
SiprecSession.from_hash(
|
|
313
|
+
@transport.request(:get, path('Calls', call_sid, 'Siprec', siprec_sid))
|
|
314
|
+
)
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# @return [VoiceML::SiprecSession]
|
|
318
|
+
def stop_siprec(call_sid, siprec_sid)
|
|
319
|
+
data = @transport.request(:post, path('Calls', call_sid, 'Siprec', siprec_sid),
|
|
320
|
+
form: { 'Status' => 'stopped' })
|
|
321
|
+
SiprecSession.from_hash(data)
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# --- Transcriptions ---
|
|
325
|
+
|
|
326
|
+
# @return [VoiceML::TranscriptionList]
|
|
327
|
+
def list_transcriptions(call_sid)
|
|
328
|
+
TranscriptionList.from_hash(
|
|
329
|
+
@transport.request(:get, path('Calls', call_sid, 'Transcriptions'))
|
|
330
|
+
)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# @return [VoiceML::CallTranscription]
|
|
334
|
+
def start_transcription(call_sid, **kwargs)
|
|
335
|
+
data = @transport.request(:post, path('Calls', call_sid, 'Transcriptions'),
|
|
336
|
+
form: form_params(START_TRANSCRIPTION_FIELDS, kwargs))
|
|
337
|
+
CallTranscription.from_hash(data)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# @return [VoiceML::CallTranscription]
|
|
341
|
+
def get_transcription(call_sid, transcription_sid)
|
|
342
|
+
CallTranscription.from_hash(
|
|
343
|
+
@transport.request(:get, path('Calls', call_sid, 'Transcriptions', transcription_sid))
|
|
344
|
+
)
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# @return [VoiceML::CallTranscription]
|
|
348
|
+
def stop_transcription(call_sid, transcription_sid)
|
|
349
|
+
data = @transport.request(:post,
|
|
350
|
+
path('Calls', call_sid, 'Transcriptions', transcription_sid),
|
|
351
|
+
form: { 'Status' => 'stopped' })
|
|
352
|
+
CallTranscription.from_hash(data)
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# --- Notifications / Events (compat stubs) ---
|
|
356
|
+
|
|
357
|
+
# @return [VoiceML::NotificationsList]
|
|
358
|
+
def list_notifications(call_sid, **kwargs)
|
|
359
|
+
NotificationsList.from_hash(
|
|
360
|
+
@transport.request(:get, path('Calls', call_sid, 'Notifications'),
|
|
361
|
+
params: form_params(LIST_NOTIFICATIONS_FIELDS, kwargs))
|
|
362
|
+
)
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# @return [Hash]
|
|
366
|
+
def get_notification(call_sid, notification_sid)
|
|
367
|
+
@transport.request(:get, path('Calls', call_sid, 'Notifications', notification_sid))
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# @return [VoiceML::EventsList]
|
|
371
|
+
def list_events(call_sid, **kwargs)
|
|
372
|
+
EventsList.from_hash(
|
|
373
|
+
@transport.request(:get, path('Calls', call_sid, 'Events'),
|
|
374
|
+
params: form_params(LIST_STUB_PAGE_FIELDS, kwargs))
|
|
375
|
+
)
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
# `POST /Calls/{sid}/UserDefinedMessages` — always raises `NotImplementedAPIError`.
|
|
379
|
+
# Mounted on the server only as a 501 stub. The SDK forwards the call so callers get a
|
|
380
|
+
# clean exception rather than discovering at runtime that the endpoint doesn't exist.
|
|
381
|
+
def send_user_defined_message(call_sid, payload = nil)
|
|
382
|
+
@transport.request(:post, path('Calls', call_sid, 'UserDefinedMessages'),
|
|
383
|
+
form: payload)
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# --- Payments (the REST companion to the `<Pay>` TwiML verb) ---
|
|
387
|
+
|
|
388
|
+
# Begin a `<Pay>` session on the live call. Returns the freshly-minted
|
|
389
|
+
# CallPayment. Returns 403 when the tenant is not `pay_enabled` or has no
|
|
390
|
+
# `stripe_secret_key` configured.
|
|
391
|
+
#
|
|
392
|
+
# `idempotency_key:` is accepted and persisted for diagnostic visibility but
|
|
393
|
+
# replay-dedup is NOT enforced today.
|
|
394
|
+
#
|
|
395
|
+
# @return [VoiceML::CallPayment]
|
|
396
|
+
def start_payment(call_sid, **kwargs)
|
|
397
|
+
data = @transport.request(:post, path('Calls', call_sid, 'Payments'),
|
|
398
|
+
form: form_params(START_PAYMENT_FIELDS, kwargs))
|
|
399
|
+
CallPayment.from_hash(data)
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
# Advance or terminate an existing Pay session. `status: "complete"`
|
|
403
|
+
# captures the collected fields; `status: "cancel"` aborts the session.
|
|
404
|
+
# `capture: ...` tells the runtime which input the user is about to type
|
|
405
|
+
# next.
|
|
406
|
+
#
|
|
407
|
+
# @return [VoiceML::CallPayment]
|
|
408
|
+
def update_payment(call_sid, payment_sid, **kwargs)
|
|
409
|
+
data = @transport.request(:post, path('Calls', call_sid, 'Payments', payment_sid),
|
|
410
|
+
form: form_params(UPDATE_PAYMENT_FIELDS, kwargs))
|
|
411
|
+
CallPayment.from_hash(data)
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
end
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
require_relative '../models/conferences'
|
|
5
|
+
require_relative '../models/recordings'
|
|
6
|
+
|
|
7
|
+
module VoiceML
|
|
8
|
+
# Operations on `/Conferences` and their participants/recordings.
|
|
9
|
+
class ConferencesResource < BaseResource
|
|
10
|
+
UPDATE_PARTICIPANT_FIELDS = {
|
|
11
|
+
'Muted' => :muted,
|
|
12
|
+
'Hold' => :hold
|
|
13
|
+
}.freeze
|
|
14
|
+
|
|
15
|
+
LIST_FIELDS = {
|
|
16
|
+
'FriendlyName' => :friendly_name,
|
|
17
|
+
'Status' => :status,
|
|
18
|
+
'DateCreated' => :date_created,
|
|
19
|
+
'DateCreated<' => :date_created_lt,
|
|
20
|
+
'DateCreated>' => :date_created_gt,
|
|
21
|
+
'DateUpdated' => :date_updated,
|
|
22
|
+
'DateUpdated<' => :date_updated_lt,
|
|
23
|
+
'DateUpdated>' => :date_updated_gt,
|
|
24
|
+
'Page' => :page,
|
|
25
|
+
'PageSize' => :page_size,
|
|
26
|
+
'PageToken' => :page_token
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
CREATE_PARTICIPANT_FIELDS = {
|
|
30
|
+
'From' => :from,
|
|
31
|
+
'To' => :to,
|
|
32
|
+
'Label' => :label,
|
|
33
|
+
'Muted' => :muted,
|
|
34
|
+
'StartConferenceOnEnter' => :start_conference_on_enter,
|
|
35
|
+
'EndConferenceOnExit' => :end_conference_on_exit,
|
|
36
|
+
'Timeout' => :timeout,
|
|
37
|
+
'StatusCallback' => :status_callback,
|
|
38
|
+
'StatusCallbackMethod' => :status_callback_method,
|
|
39
|
+
'StatusCallbackEvent' => :status_callback_event
|
|
40
|
+
}.freeze
|
|
41
|
+
|
|
42
|
+
UPDATE_RECORDING_FIELDS = {
|
|
43
|
+
'Status' => :status
|
|
44
|
+
}.freeze
|
|
45
|
+
|
|
46
|
+
LIST_PARTICIPANTS_FIELDS = {
|
|
47
|
+
'Muted' => :muted,
|
|
48
|
+
'Hold' => :hold,
|
|
49
|
+
'Coaching' => :coaching,
|
|
50
|
+
'Page' => :page,
|
|
51
|
+
'PageSize' => :page_size,
|
|
52
|
+
'PageToken' => :page_token
|
|
53
|
+
}.freeze
|
|
54
|
+
|
|
55
|
+
LIST_CALL_RECORDINGS_FIELDS = {
|
|
56
|
+
'DateCreated' => :date_created,
|
|
57
|
+
'DateCreated<' => :date_created_lt,
|
|
58
|
+
'DateCreated>' => :date_created_gt,
|
|
59
|
+
'Page' => :page,
|
|
60
|
+
'PageSize' => :page_size,
|
|
61
|
+
'PageToken' => :page_token
|
|
62
|
+
}.freeze
|
|
63
|
+
|
|
64
|
+
# @return [VoiceML::ConferenceList]
|
|
65
|
+
def list(**kwargs)
|
|
66
|
+
ConferenceList.from_hash(
|
|
67
|
+
@transport.request(:get, path('Conferences'), params: form_params(LIST_FIELDS, kwargs))
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @yield [VoiceML::Conference]
|
|
72
|
+
# @return [Enumerator<VoiceML::Conference>] when no block given
|
|
73
|
+
def each(**kwargs, &block)
|
|
74
|
+
return enum_for(:each, **kwargs) unless block
|
|
75
|
+
|
|
76
|
+
page_num = kwargs.delete(:page) || 0
|
|
77
|
+
loop do
|
|
78
|
+
chunk = list(**kwargs, page: page_num)
|
|
79
|
+
chunk.conferences.each(&block)
|
|
80
|
+
break if chunk.next_page_uri.nil? || chunk.next_page_uri.empty? || chunk.conferences.empty?
|
|
81
|
+
page_num += 1
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @return [VoiceML::Conference]
|
|
86
|
+
def get(conference_sid)
|
|
87
|
+
Conference.from_hash(@transport.request(:get, path('Conferences', conference_sid)))
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# End a live conference. v1 supports only `status: "completed"`.
|
|
91
|
+
# @return [VoiceML::Conference]
|
|
92
|
+
def end_conference(conference_sid, status: 'completed')
|
|
93
|
+
data = @transport.request(:post, path('Conferences', conference_sid),
|
|
94
|
+
form: { 'Status' => status })
|
|
95
|
+
Conference.from_hash(data)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# --- Participants ---
|
|
99
|
+
|
|
100
|
+
# @return [VoiceML::ParticipantList]
|
|
101
|
+
def list_participants(conference_sid, **kwargs)
|
|
102
|
+
ParticipantList.from_hash(
|
|
103
|
+
@transport.request(:get, path('Conferences', conference_sid, 'Participants'),
|
|
104
|
+
params: form_params(LIST_PARTICIPANTS_FIELDS, kwargs))
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# @return [VoiceML::Participant]
|
|
109
|
+
def get_participant(conference_sid, call_sid)
|
|
110
|
+
Participant.from_hash(
|
|
111
|
+
@transport.request(:get, path('Conferences', conference_sid, 'Participants', call_sid))
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Mute/unmute or hold/unhold a participant. At least one of `muted:` / `hold:` must be set.
|
|
116
|
+
# @return [VoiceML::Participant]
|
|
117
|
+
def update_participant(conference_sid, call_sid, **kwargs)
|
|
118
|
+
data = @transport.request(
|
|
119
|
+
:post,
|
|
120
|
+
path('Conferences', conference_sid, 'Participants', call_sid),
|
|
121
|
+
form: form_params(UPDATE_PARTICIPANT_FIELDS, kwargs)
|
|
122
|
+
)
|
|
123
|
+
Participant.from_hash(data)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# @return [nil]
|
|
127
|
+
def kick_participant(conference_sid, call_sid)
|
|
128
|
+
@transport.request(:delete,
|
|
129
|
+
path('Conferences', conference_sid, 'Participants', call_sid))
|
|
130
|
+
nil
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Dial a leg into a conference.
|
|
134
|
+
# @return [VoiceML::Participant]
|
|
135
|
+
def create_participant(conference_sid, from:, to:, **kwargs)
|
|
136
|
+
kwargs = kwargs.merge(from: from, to: to)
|
|
137
|
+
data = @transport.request(
|
|
138
|
+
:post,
|
|
139
|
+
path('Conferences', conference_sid, 'Participants'),
|
|
140
|
+
form: form_params(CREATE_PARTICIPANT_FIELDS, kwargs)
|
|
141
|
+
)
|
|
142
|
+
Participant.from_hash(data)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# --- Recordings ---
|
|
146
|
+
|
|
147
|
+
# @return [VoiceML::RecordingList]
|
|
148
|
+
def list_recordings(conference_sid, **kwargs)
|
|
149
|
+
RecordingList.from_hash(
|
|
150
|
+
@transport.request(:get, path('Conferences', conference_sid, 'Recordings'),
|
|
151
|
+
params: form_params(LIST_CALL_RECORDINGS_FIELDS, kwargs))
|
|
152
|
+
)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# @return [VoiceML::Recording]
|
|
156
|
+
def get_recording(conference_sid, recording_sid)
|
|
157
|
+
Recording.from_hash(
|
|
158
|
+
@transport.request(:get, path('Conferences', conference_sid, 'Recordings', recording_sid))
|
|
159
|
+
)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# @return [VoiceML::Recording]
|
|
163
|
+
def update_recording(conference_sid, recording_sid, **kwargs)
|
|
164
|
+
data = @transport.request(
|
|
165
|
+
:post,
|
|
166
|
+
path('Conferences', conference_sid, 'Recordings', recording_sid),
|
|
167
|
+
form: form_params(UPDATE_RECORDING_FIELDS, kwargs)
|
|
168
|
+
)
|
|
169
|
+
Recording.from_hash(data)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# @return [nil]
|
|
173
|
+
def delete_recording(conference_sid, recording_sid)
|
|
174
|
+
@transport.request(:delete, path('Conferences', conference_sid, 'Recordings', recording_sid))
|
|
175
|
+
nil
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'uri'
|
|
6
|
+
|
|
7
|
+
require_relative '../models/diagnostics'
|
|
8
|
+
|
|
9
|
+
module VoiceML
|
|
10
|
+
# Diagnostic surfaces — `/health` and the OpenAPI doc endpoints.
|
|
11
|
+
#
|
|
12
|
+
# These don't sit under `/2010-04-01/Accounts/{AccountSid}/...`; they're mounted at the
|
|
13
|
+
# server root and don't require auth (the spec marks them `security: []`).
|
|
14
|
+
class DiagnosticsResource
|
|
15
|
+
def initialize(transport)
|
|
16
|
+
@transport = transport
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Hit `GET /health`. 200 = all hard checks pass; 503 raises `VoiceML::ServerError`
|
|
20
|
+
# with the failure list on `error.body`.
|
|
21
|
+
#
|
|
22
|
+
# @return [VoiceML::HealthStatus]
|
|
23
|
+
def health
|
|
24
|
+
HealthStatus.from_hash(unauth_request('/health'))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Fetch the OpenAPI spec as parsed JSON.
|
|
28
|
+
#
|
|
29
|
+
# @return [Hash]
|
|
30
|
+
def openapi_json
|
|
31
|
+
unauth_request('/openapi.json')
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def unauth_request(path)
|
|
37
|
+
uri = URI.parse("#{@transport.base_url}#{path}")
|
|
38
|
+
req = Net::HTTP::Get.new(uri)
|
|
39
|
+
req['Accept'] = 'application/json'
|
|
40
|
+
req['User-Agent'] = @transport.user_agent
|
|
41
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
42
|
+
http.use_ssl = uri.scheme == 'https'
|
|
43
|
+
http.open_timeout = 10
|
|
44
|
+
http.read_timeout = 10
|
|
45
|
+
response = http.start { |h| h.request(req) }
|
|
46
|
+
|
|
47
|
+
status = response.code.to_i
|
|
48
|
+
unless status >= 200 && status < 300
|
|
49
|
+
body = begin
|
|
50
|
+
response.body && !response.body.empty? ? JSON.parse(response.body) : response.body
|
|
51
|
+
rescue JSON::ParserError
|
|
52
|
+
response.body
|
|
53
|
+
end
|
|
54
|
+
message = body.is_a?(Hash) && body['message'].is_a?(String) ? body['message'] : "HTTP #{status}"
|
|
55
|
+
more_info = body.is_a?(Hash) && body['more_info'].is_a?(String) ? body['more_info'] : nil
|
|
56
|
+
raise VoiceML.error_from_response(status, message, body: body, more_info: more_info)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
return nil if response.body.nil? || response.body.empty?
|
|
60
|
+
|
|
61
|
+
JSON.parse(response.body)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|