voiceml 0.9.2 → 0.9.3

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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -13
  3. data/lib/voiceml/client.rb +53 -13
  4. data/lib/voiceml/errors.rb +15 -2
  5. data/lib/voiceml/models/accounts.rb +47 -3
  6. data/lib/voiceml/models/applications.rb +6 -0
  7. data/lib/voiceml/models/assistants_v1.rb +40 -0
  8. data/lib/voiceml/models/calls.rb +4 -0
  9. data/lib/voiceml/models/common.rb +35 -4
  10. data/lib/voiceml/models/conferences.rb +8 -0
  11. data/lib/voiceml/models/conversations_v1.rb +131 -29
  12. data/lib/voiceml/models/diagnostics.rb +33 -12
  13. data/lib/voiceml/models/exports.rb +58 -0
  14. data/lib/voiceml/models/incoming_phone_numbers.rb +4 -0
  15. data/lib/voiceml/models/media.rb +4 -0
  16. data/lib/voiceml/models/messages.rb +29 -0
  17. data/lib/voiceml/models/messaging_v1.rb +35 -2
  18. data/lib/voiceml/models/monitor_v1.rb +54 -0
  19. data/lib/voiceml/models/notifications.rb +2 -0
  20. data/lib/voiceml/models/outgoing_caller_ids.rb +6 -0
  21. data/lib/voiceml/models/payments.rb +2 -0
  22. data/lib/voiceml/models/pricing.rb +38 -4
  23. data/lib/voiceml/models/queues.rb +8 -0
  24. data/lib/voiceml/models/recordings.rb +5 -1
  25. data/lib/voiceml/models/routes_v2.rb +6 -2
  26. data/lib/voiceml/models/sip.rb +34 -6
  27. data/lib/voiceml/models/sip_registrations.rb +49 -0
  28. data/lib/voiceml/models/siprec.rb +5 -17
  29. data/lib/voiceml/models/streams.rb +4 -16
  30. data/lib/voiceml/models/transcriptions.rb +35 -4
  31. data/lib/voiceml/models/usage_records.rb +48 -0
  32. data/lib/voiceml/models/user_defined_messages.rb +2 -0
  33. data/lib/voiceml/models/voice_v1.rb +29 -19
  34. data/lib/voiceml/request_validator.rb +125 -0
  35. data/lib/voiceml/resources/accounts.rb +75 -0
  36. data/lib/voiceml/resources/applications.rb +3 -1
  37. data/lib/voiceml/resources/assistants_v1.rb +53 -77
  38. data/lib/voiceml/resources/base.rb +138 -16
  39. data/lib/voiceml/resources/calls.rb +34 -49
  40. data/lib/voiceml/resources/conferences.rb +26 -5
  41. data/lib/voiceml/resources/conversations_v1.rb +147 -172
  42. data/lib/voiceml/resources/diagnostics.rb +20 -35
  43. data/lib/voiceml/resources/exports.rb +36 -0
  44. data/lib/voiceml/resources/incoming_phone_numbers.rb +53 -32
  45. data/lib/voiceml/resources/messages.rb +79 -15
  46. data/lib/voiceml/resources/messaging_v1.rb +47 -13
  47. data/lib/voiceml/resources/monitor_v1.rb +68 -0
  48. data/lib/voiceml/resources/notifications.rb +1 -1
  49. data/lib/voiceml/resources/outgoing_caller_ids.rb +77 -0
  50. data/lib/voiceml/resources/pricing.rb +13 -5
  51. data/lib/voiceml/resources/queues.rb +6 -5
  52. data/lib/voiceml/resources/recordings.rb +39 -5
  53. data/lib/voiceml/resources/routes_v2.rb +10 -4
  54. data/lib/voiceml/resources/sip.rb +30 -11
  55. data/lib/voiceml/resources/transcriptions.rb +40 -0
  56. data/lib/voiceml/resources/usage_records.rb +51 -0
  57. data/lib/voiceml/resources/voice_v1.rb +31 -38
  58. data/lib/voiceml/transport.rb +277 -230
  59. data/lib/voiceml/version.rb +1 -1
  60. data/lib/voiceml.rb +14 -15
  61. metadata +18 -6
@@ -1,64 +1,49 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
- require 'net/http'
5
- require 'uri'
6
-
7
3
  require_relative '../models/diagnostics'
8
4
 
9
5
  module VoiceML
10
6
  # Diagnostic surfaces — `/health` and the OpenAPI doc endpoints.
11
7
  #
12
8
  # 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: []`).
9
+ # server root and require no credentials (the spec marks them `security: []`), so every
10
+ # call routes through the normal transport — the configured HTTP client, timeout, proxy
11
+ # and User-Agent apply — with auth omitted.
14
12
  class DiagnosticsResource
15
13
  def initialize(transport)
16
14
  @transport = transport
17
15
  end
18
16
 
19
17
  # Hit `GET /health`. 200 = all hard checks pass; 503 raises `VoiceML::ServerError`
20
- # with the failure list on `error.body`.
18
+ # with the failure list on `error.body`. `warnings`/`failures` are optional in the
19
+ # response body.
21
20
  #
22
21
  # @return [VoiceML::HealthStatus]
23
22
  def health
24
- HealthStatus.from_hash(unauth_request('/health'))
23
+ HealthStatus.from_hash(@transport.request(:get, '/health', auth: false))
25
24
  end
26
25
 
27
26
  # Fetch the OpenAPI spec as parsed JSON.
28
27
  #
29
28
  # @return [Hash]
30
29
  def openapi_json
31
- unauth_request('/openapi.json')
30
+ @transport.request(:get, '/openapi.json', auth: false)
32
31
  end
33
32
 
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?
33
+ # Fetch the OpenAPI spec as raw YAML (`/openapi.yaml`).
34
+ #
35
+ # @return [String]
36
+ def openapi_yaml
37
+ _status, body, _headers = @transport.request_raw(:get, '/openapi.yaml', auth: false)
38
+ body
39
+ end
60
40
 
61
- JSON.parse(response.body)
41
+ # Fetch the OpenAPI spec as raw YAML (`/openapi.yml`).
42
+ #
43
+ # @return [String]
44
+ def openapi_yml
45
+ _status, body, _headers = @transport.request_raw(:get, '/openapi.yml', auth: false)
46
+ body
62
47
  end
63
48
  end
64
49
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require_relative '../models/exports'
5
+
6
+ module VoiceML
7
+ # Operations on `/Exports` — the account's daily usage archives.
8
+ class ExportsResource < BaseResource
9
+ # List the account's available archives, newest first.
10
+ #
11
+ # @return [VoiceML::ExportList]
12
+ def list
13
+ ExportList.from_hash(@transport.request(:get, path('Exports')))
14
+ end
15
+
16
+ # Download one day's archive. Like recording audio, the server may answer
17
+ # with the bytes directly or with a 302 to a presigned object; redirects
18
+ # are followed with credentials stripped and https -> http downgrades
19
+ # refused.
20
+ #
21
+ # @param date [String] archive day, `YYYY-MM-DD`.
22
+ # @return [VoiceML::ExportDownload]
23
+ def download(date)
24
+ status, content, headers = @transport.fetch_bytes(path('Exports', date, suffix: ''))
25
+ content_type = headers['content-type']
26
+ content_type = content_type.first if content_type.is_a?(Array)
27
+ x_amz_id = headers['x-amz-id-2']
28
+ ExportDownload.new(
29
+ date: date,
30
+ content: content,
31
+ content_type: content_type || 'application/octet-stream',
32
+ via_redirect: status == 200 && !x_amz_id.nil?
33
+ )
34
+ end
35
+ end
36
+ end
@@ -11,10 +11,13 @@ module VoiceML
11
11
  # returning a 0-or-1-row envelope, then `get(sid)`) is supported.
12
12
  class IncomingPhoneNumbersResource < BaseResource
13
13
  LIST_FIELDS = {
14
- 'PhoneNumber' => :phone_number,
15
- 'Page' => :page,
16
- 'PageSize' => :page_size,
17
- 'PageToken' => :page_token
14
+ 'PhoneNumber' => :phone_number,
15
+ 'FriendlyName' => :friendly_name,
16
+ 'Beta' => :beta,
17
+ 'Origin' => :origin,
18
+ 'Page' => :page,
19
+ 'PageSize' => :page_size,
20
+ 'PageToken' => :page_token
18
21
  }.freeze
19
22
 
20
23
  CREATE_FIELDS = {
@@ -23,7 +26,11 @@ module VoiceML
23
26
  'VoiceMethod' => :voice_method,
24
27
  'VoiceFallbackUrl' => :voice_fallback_url,
25
28
  'VoiceFallbackMethod' => :voice_fallback_method,
26
- 'FriendlyName' => :friendly_name
29
+ 'SmsUrl' => :sms_url,
30
+ 'SmsMethod' => :sms_method,
31
+ 'SmsFallbackUrl' => :sms_fallback_url,
32
+ 'SmsFallbackMethod' => :sms_fallback_method,
33
+ 'SmsApplicationSid' => :sms_application_sid
27
34
  }.freeze
28
35
 
29
36
  UPDATE_FIELDS = {
@@ -31,28 +38,36 @@ module VoiceML
31
38
  'VoiceMethod' => :voice_method,
32
39
  'VoiceFallbackUrl' => :voice_fallback_url,
33
40
  'VoiceFallbackMethod' => :voice_fallback_method,
34
- 'FriendlyName' => :friendly_name
41
+ 'SmsUrl' => :sms_url,
42
+ 'SmsMethod' => :sms_method,
43
+ 'SmsFallbackUrl' => :sms_fallback_url,
44
+ 'SmsFallbackMethod' => :sms_fallback_method,
45
+ 'SmsApplicationSid' => :sms_application_sid
35
46
  }.freeze
36
47
 
37
- LIST_TYPED_FIELDS = {
38
- 'PhoneNumber' => :phone_number,
39
- 'FriendlyName' => :friendly_name,
40
- 'Beta' => :beta,
41
- 'Origin' => :origin,
42
- 'Page' => :page,
43
- 'PageSize' => :page_size,
44
- 'PageToken' => :page_token
48
+ # The typed Local/Mobile/TollFree creates take a reduced body: only
49
+ # `PhoneNumber`, `VoiceUrl` and `VoiceMethod`.
50
+ CREATE_TYPED_FIELDS = {
51
+ 'PhoneNumber' => :phone_number,
52
+ 'VoiceUrl' => :voice_url,
53
+ 'VoiceMethod' => :voice_method
45
54
  }.freeze
46
55
 
47
56
  # List DIDs assigned to the authenticated tenant.
48
57
  #
49
58
  # @param phone_number [String, nil] exact-match E.164 filter. Returns a 0-or-1-row
50
59
  # envelope when set — the standard twilio-ruby lookup pattern.
51
- # @param page [Integer, nil] 0-indexed page number.
52
- # @param page_size [Integer, nil] page size (server-bounded).
60
+ # @param friendly_name [String, nil] filter by display label.
61
+ # @param beta [Boolean, nil] filter new (beta) numbers.
62
+ # @param origin [String, nil] filter by number origin (`twilio`, `hosted`).
63
+ # @param page [Integer, nil] 0-indexed page number.
64
+ # @param page_size [Integer, nil] page size (server-bounded).
65
+ # @param page_token [String, nil] continuation token from a previous page.
53
66
  # @return [VoiceML::IncomingPhoneNumberList]
54
- def list(phone_number: nil, page: nil, page_size: nil)
55
- kwargs = { phone_number: phone_number, page: page, page_size: page_size }
67
+ def list(phone_number: nil, friendly_name: nil, beta: nil, origin: nil,
68
+ page: nil, page_size: nil, page_token: nil)
69
+ kwargs = { phone_number: phone_number, friendly_name: friendly_name, beta: beta,
70
+ origin: origin, page: page, page_size: page_size, page_token: page_token }
56
71
  IncomingPhoneNumberList.from_hash(
57
72
  @transport.request(:get, path('IncomingPhoneNumbers'),
58
73
  params: form_params(LIST_FIELDS, kwargs))
@@ -62,22 +77,29 @@ module VoiceML
62
77
  # Assign a DID to the authenticated tenant. Idempotent re-POSTing the same
63
78
  # `phone_number:` rebinds the voice routing (matches Twilio update semantics).
64
79
  #
65
- # @param phone_number [String] required. E.164 — leading `+`, 7-15 digits.
66
- # @param voice_url [String, nil] inbound-voice handler URL.
67
- # @param voice_method [String, nil] HTTP method for `voice_url` (`GET`/`POST`).
68
- # @param voice_fallback_url [String, nil] fallback handler URL.
80
+ # @param phone_number [String] required. E.164 — leading `+`, 7-15 digits.
81
+ # @param voice_url [String, nil] inbound-voice handler URL.
82
+ # @param voice_method [String, nil] HTTP method for `voice_url` (`GET`/`POST`).
83
+ # @param voice_fallback_url [String, nil] fallback handler URL.
69
84
  # @param voice_fallback_method [String, nil] HTTP method for `voice_fallback_url`.
70
- # @param friendly_name [String, nil] display label (server may ignore in v0.5.x).
85
+ # @param sms_url [String, nil] inbound-SMS webhook URL.
86
+ # @param sms_method [String, nil] HTTP method for `sms_url`.
87
+ # @param sms_fallback_url [String, nil] inbound-SMS fallback URL.
88
+ # @param sms_fallback_method [String, nil] HTTP method for `sms_fallback_url`.
89
+ # @param sms_application_sid [String, nil] `AP`-prefixed Application that
90
+ # handles inbound SMS (`^AP[0-9a-fA-F]{32}$`).
71
91
  # @return [VoiceML::IncomingPhoneNumber]
72
92
  def create(phone_number:, voice_url: nil, voice_method: nil,
73
93
  voice_fallback_url: nil, voice_fallback_method: nil,
74
- friendly_name: nil)
94
+ sms_url: nil, sms_method: nil, sms_fallback_url: nil,
95
+ sms_fallback_method: nil, sms_application_sid: nil)
75
96
  kwargs = {
76
97
  phone_number: phone_number,
77
98
  voice_url: voice_url, voice_method: voice_method,
78
- voice_fallback_url: voice_fallback_url,
79
- voice_fallback_method: voice_fallback_method,
80
- friendly_name: friendly_name
99
+ voice_fallback_url: voice_fallback_url, voice_fallback_method: voice_fallback_method,
100
+ sms_url: sms_url, sms_method: sms_method,
101
+ sms_fallback_url: sms_fallback_url, sms_fallback_method: sms_fallback_method,
102
+ sms_application_sid: sms_application_sid
81
103
  }
82
104
  IncomingPhoneNumber.from_hash(
83
105
  @transport.request(:post, path('IncomingPhoneNumbers'),
@@ -93,11 +115,10 @@ module VoiceML
93
115
  )
94
116
  end
95
117
 
96
- # Update voice routing on an assigned DID. Only set fields are touched.
118
+ # Update voice/SMS routing on an assigned DID. Only set fields are touched.
97
119
  #
98
120
  # @param sid [String] the `PN`-prefixed identifier.
99
- # @param opts [Hash] any of `voice_url:`, `voice_method:`, `voice_fallback_url:`,
100
- # `voice_fallback_method:`, `friendly_name:`.
121
+ # @param opts [Hash] any of the `create` kwargs except `phone_number:`.
101
122
  # @return [VoiceML::IncomingPhoneNumber]
102
123
  def update(sid, **opts)
103
124
  IncomingPhoneNumber.from_hash(
@@ -148,14 +169,14 @@ module VoiceML
148
169
  def list_typed(kind, kwargs)
149
170
  IncomingPhoneNumberList.from_hash(
150
171
  @transport.request(:get, path('IncomingPhoneNumbers', kind),
151
- params: form_params(LIST_TYPED_FIELDS, kwargs))
172
+ params: form_params(LIST_FIELDS, kwargs))
152
173
  )
153
174
  end
154
175
 
155
176
  def create_typed(kind, kwargs)
156
177
  IncomingPhoneNumber.from_hash(
157
178
  @transport.request(:post, path('IncomingPhoneNumbers', kind),
158
- form: form_params(CREATE_FIELDS, kwargs))
179
+ form: form_params(CREATE_TYPED_FIELDS, kwargs))
159
180
  )
160
181
  end
161
182
  end
@@ -2,21 +2,25 @@
2
2
 
3
3
  require_relative 'base'
4
4
  require_relative '../models/messages'
5
+ require_relative '../models/media'
5
6
 
6
7
  module VoiceML
7
- # Operations on `/Messages` — VoiceTel's Twilio-compatible SMS surface,
8
- # backed by the SDK 2.2 gateway. Outbound-only today (no MMS, no inbound
9
- # webhook delivery).
8
+ # Operations on `/Messages` — VoiceTel's Twilio-compatible SMS/MMS surface,
9
+ # backed by the SDK 2.2 gateway.
10
10
  #
11
11
  # All methods accept idiomatic snake_case keyword arguments — they're
12
- # translated to the PascalCase wire names internally.
12
+ # translated to the PascalCase wire names internally. Unknown kwargs raise
13
+ # `ArgumentError`.
13
14
  class MessagesResource < BaseResource
14
15
  CREATE_FIELDS = {
15
16
  'To' => :to,
16
17
  'Body' => :body,
18
+ 'MediaUrl' => :media_url,
17
19
  'From' => :from,
18
20
  'MessagingServiceSid' => :messaging_service_sid,
19
- 'StatusCallback' => :status_callback
21
+ 'ValidityPeriod' => :validity_period,
22
+ 'StatusCallback' => :status_callback,
23
+ 'StatusCallbackMethod' => :status_callback_method
20
24
  }.freeze
21
25
 
22
26
  LIST_FIELDS = {
@@ -35,12 +39,31 @@ module VoiceML
35
39
  'Status' => :status
36
40
  }.freeze
37
41
 
38
- # Dispatch an outbound SMS. `to:` and `body:` are required; `from:` falls
39
- # back to the tenant's configured default sender when omitted.
42
+ FEEDBACK_FIELDS = {
43
+ 'Outcome' => :outcome
44
+ }.freeze
45
+
46
+ LIST_MEDIA_FIELDS = {
47
+ 'DateCreated' => :date_created,
48
+ 'DateCreated<' => :date_created_lt,
49
+ 'DateCreated>' => :date_created_gt,
50
+ 'Page' => :page,
51
+ 'PageSize' => :page_size,
52
+ 'PageToken' => :page_token
53
+ }.freeze
54
+
55
+ # Dispatch an outbound SMS or MMS. `to:` is required, plus at least one of
56
+ # `body:` (SMS text) or `media_url:` (MMS media; pass a string or an array —
57
+ # repeated keys are sent on the wire). `from:` falls back to the tenant's
58
+ # configured default sender when omitted.
40
59
  #
41
60
  # @return [VoiceML::Message]
42
61
  def create(**kwargs)
43
- data = @transport.request(:post, path('Messages'), form: form_params(CREATE_FIELDS, kwargs))
62
+ form = form_params(CREATE_FIELDS, kwargs)
63
+ raise ArgumentError, 'to: is required' if form['To'].nil?
64
+ raise ArgumentError, 'either body: or media_url: is required' if form['Body'].nil? && form['MediaUrl'].nil?
65
+
66
+ data = @transport.request(:post, path('Messages'), form: form)
44
67
  Message.from_hash(data)
45
68
  end
46
69
 
@@ -61,21 +84,22 @@ module VoiceML
61
84
  )
62
85
  end
63
86
 
64
- # Walk every page of /Messages and yield each Message. Returns an Enumerator
65
- # when called without a block.
87
+ # Walk every page of /Messages and yield each Message, following the
88
+ # envelope's `next_page_uri` verbatim (the server requires its `PageToken`
89
+ # on page 2+). Returns an Enumerator when called without a block.
66
90
  #
67
91
  # @yield [VoiceML::Message]
68
92
  # @return [Enumerator<VoiceML::Message>] when no block given
69
93
  def each(**kwargs, &block)
70
94
  return enum_for(:each, **kwargs) unless block
71
95
 
72
- page_num = kwargs.delete(:page) || 0
96
+ page = list(**kwargs)
73
97
  loop do
74
- chunk = list(**kwargs, page: page_num)
75
- chunk.messages.each(&block)
76
- break if chunk.next_page_uri.nil? || chunk.next_page_uri.empty? || chunk.messages.empty?
98
+ page.messages.each(&block)
99
+ nxt = next_page(page)
100
+ break if nxt.nil?
77
101
 
78
- page_num += 1
102
+ page = nxt
79
103
  end
80
104
  end
81
105
 
@@ -90,6 +114,19 @@ module VoiceML
90
114
  Message.from_hash(data)
91
115
  end
92
116
 
117
+ # Submit delivery feedback for a Message
118
+ # (`POST /Messages/{Sid}/Feedback.json`). `outcome:` is `"confirmed"` or
119
+ # `"unconfirmed"`; any other value echoes back as `unconfirmed` and the
120
+ # feedback is not persisted.
121
+ #
122
+ # @return [VoiceML::MessageFeedback]
123
+ def create_feedback(sid, outcome:)
124
+ MessageFeedback.from_hash(
125
+ @transport.request(:post, path('Messages', sid, 'Feedback'),
126
+ form: form_params(FEEDBACK_FIELDS, { outcome: outcome }))
127
+ )
128
+ end
129
+
93
130
  # Remove a Message resource from the account's store.
94
131
  #
95
132
  # @return [nil]
@@ -97,5 +134,32 @@ module VoiceML
97
134
  @transport.request(:delete, path('Messages', sid))
98
135
  nil
99
136
  end
137
+
138
+ # --- Media (`/Messages/{MessageSid}/Media`) ---
139
+
140
+ # List the media items of a message.
141
+ #
142
+ # @return [VoiceML::MediaList]
143
+ def list_media(message_sid, **kwargs)
144
+ MediaList.from_hash(
145
+ @transport.request(:get, path('Messages', message_sid, 'Media'),
146
+ params: form_params(LIST_MEDIA_FIELDS, kwargs))
147
+ )
148
+ end
149
+
150
+ # Fetch one media item's metadata.
151
+ #
152
+ # @return [VoiceML::Media]
153
+ def get_media(message_sid, sid)
154
+ Media.from_hash(@transport.request(:get, path('Messages', message_sid, 'Media', sid)))
155
+ end
156
+
157
+ # Delete a media item.
158
+ #
159
+ # @return [nil]
160
+ def delete_media(message_sid, sid)
161
+ @transport.request(:delete, path('Messages', message_sid, 'Media', sid))
162
+ nil
163
+ end
100
164
  end
101
165
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'base'
3
4
  require_relative '../models/messaging_v1'
4
5
 
5
6
  module VoiceML
@@ -20,6 +21,9 @@ module VoiceML
20
21
  # `create` / `list` / `fetch` / `delete` reuse the shared path; `update`
21
22
  # (`POST /v1/Services/{sid}`) is unique to Messaging Service.
22
23
  class MessagingV1ServicesResource
24
+ include V1Pages
25
+ include FieldMapping
26
+
23
27
  SERVICE_FIELDS = {
24
28
  'FriendlyName' => :friendly_name,
25
29
  'InboundRequestUrl' => :inbound_request_url,
@@ -46,7 +50,7 @@ module VoiceML
46
50
  def create(friendly_name:, **kwargs)
47
51
  kwargs[:friendly_name] = friendly_name
48
52
  MessagingService.from_hash(
49
- @transport.request(:post, '/v1/Services', form: build_form(kwargs))
53
+ @transport.request(:post, '/v1/Services', form: form_params(SERVICE_FIELDS, kwargs))
50
54
  )
51
55
  end
52
56
 
@@ -57,31 +61,61 @@ module VoiceML
57
61
  end
58
62
 
59
63
  def fetch(sid)
60
- MessagingService.from_hash(@transport.request(:get, "/v1/Services/#{sid}"))
64
+ MessagingService.from_hash(@transport.request(:get, "/v1/Services/#{encode_path_segment(sid)}"))
61
65
  end
62
66
 
63
67
  def update(sid, **kwargs)
64
68
  MessagingService.from_hash(
65
- @transport.request(:post, "/v1/Services/#{sid}", form: build_form(kwargs))
69
+ @transport.request(:post, "/v1/Services/#{encode_path_segment(sid)}",
70
+ form: form_params(SERVICE_FIELDS, kwargs))
66
71
  )
67
72
  end
68
73
 
69
74
  def delete(sid)
70
- @transport.request(:delete, "/v1/Services/#{sid}")
75
+ @transport.request(:delete, "/v1/Services/#{encode_path_segment(sid)}")
71
76
  nil
72
77
  end
73
78
 
74
- private
79
+ # --- Sender-pool phone numbers (`/v1/Services/{ChatServiceSid}/PhoneNumbers`) ---
80
+
81
+ # List the phone numbers in a Messaging Service's sender pool. Note the
82
+ # spec declares only `Page`/`PageSize` here — no `PageToken`.
83
+ #
84
+ # @return [VoiceML::MessagingServicePhoneNumberList]
85
+ def list_phone_numbers(chat_service_sid, page: nil, page_size: nil)
86
+ params = {}
87
+ params['Page'] = page unless page.nil?
88
+ params['PageSize'] = page_size unless page_size.nil?
89
+ MessagingServicePhoneNumberList.new(
90
+ @transport.request(:get, "/v1/Services/#{encode_path_segment(chat_service_sid)}/PhoneNumbers",
91
+ params: params)
92
+ )
93
+ end
94
+
95
+ # Add a phone number (by its `PN` sid) to a Messaging Service's sender
96
+ # pool. HTTP 409 means the number is already in the pool.
97
+ #
98
+ # @return [VoiceML::MessagingServicePhoneNumber]
99
+ def create_phone_number(chat_service_sid, phone_number_sid:)
100
+ MessagingServicePhoneNumber.from_hash(
101
+ @transport.request(:post, "/v1/Services/#{encode_path_segment(chat_service_sid)}/PhoneNumbers",
102
+ form: { 'PhoneNumberSid' => phone_number_sid })
103
+ )
104
+ end
75
105
 
76
- def build_form(kwargs)
77
- out = {}
78
- SERVICE_FIELDS.each do |wire, k|
79
- value = kwargs[k]
80
- next if value.nil?
106
+ # @return [VoiceML::MessagingServicePhoneNumber]
107
+ def fetch_phone_number(chat_service_sid, sid)
108
+ MessagingServicePhoneNumber.from_hash(
109
+ @transport.request(:get, "/v1/Services/#{encode_path_segment(chat_service_sid)}/PhoneNumbers/#{encode_path_segment(sid)}")
110
+ )
111
+ end
81
112
 
82
- out[wire] = value
83
- end
84
- out
113
+ # Remove a phone number from a Messaging Service's sender pool.
114
+ #
115
+ # @return [nil]
116
+ def delete_phone_number(chat_service_sid, sid)
117
+ @transport.request(:delete, "/v1/Services/#{encode_path_segment(chat_service_sid)}/PhoneNumbers/#{encode_path_segment(sid)}")
118
+ nil
85
119
  end
86
120
  end
87
121
  end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ require_relative '../models/monitor_v1'
6
+
7
+ module VoiceML
8
+ # `client.monitor_alerts` — Monitor v1 Alerts (`/v1/Alerts`), on the
9
+ # default host: failure events for calls and messaging delivery.
10
+ #
11
+ # The server serves a single page (`meta.next_page_url` is always `null`);
12
+ # `next_page` follows the envelope verbatim regardless and returns `nil`
13
+ # when there is no continuation.
14
+ class MonitorAlertsResource
15
+ include V1Pages
16
+ include FieldMapping
17
+
18
+ LIST_FIELDS = {
19
+ 'LogLevel' => :log_level,
20
+ 'StartDate' => :start_date,
21
+ 'EndDate' => :end_date,
22
+ 'PageSize' => :page_size
23
+ }.freeze
24
+
25
+ def initialize(transport)
26
+ @transport = transport
27
+ end
28
+
29
+ # Return one page of alerts, narrowed by severity (`log_level:` `"error"`
30
+ # or `"warning"`) and generation window (`start_date:` / `end_date:`,
31
+ # `YYYY-MM-DD` or a full ISO-8601 timestamp). `page_size:` limits this
32
+ # collection (default 50, capped at 1000 server-side).
33
+ #
34
+ # @return [VoiceML::MonitorAlertList]
35
+ def list(**kwargs)
36
+ MonitorAlertList.new(
37
+ @transport.request(:get, '/v1/Alerts', params: form_params(LIST_FIELDS, kwargs))
38
+ )
39
+ end
40
+
41
+ # Walk every page of `/v1/Alerts` and yield each alert, following the
42
+ # envelope's `meta.next_page_url` verbatim. Returns an Enumerator when
43
+ # called without a block; stopping early makes no further requests.
44
+ #
45
+ # @yield [VoiceML::MonitorAlert]
46
+ # @return [Enumerator<VoiceML::MonitorAlert>] when no block given
47
+ def each(**kwargs, &block)
48
+ return enum_for(:each, **kwargs) unless block
49
+
50
+ page = list(**kwargs)
51
+ loop do
52
+ page.alerts.each(&block)
53
+ nxt = next_page(page)
54
+ break if nxt.nil?
55
+
56
+ page = nxt
57
+ end
58
+ end
59
+
60
+ # Fetch one alert by sid, with the stored request/response detail when
61
+ # the underlying event carries any.
62
+ #
63
+ # @return [VoiceML::MonitorAlert]
64
+ def fetch(sid)
65
+ MonitorAlert.from_hash(@transport.request(:get, "/v1/Alerts/#{encode_path_segment(sid)}"))
66
+ end
67
+ end
68
+ end
@@ -26,7 +26,7 @@ module VoiceML
26
26
 
27
27
  # @return [Hash]
28
28
  def get(notification_sid)
29
- @transport.request(:get, path('Notifications', notification_sid))
29
+ Notification.from_hash(@transport.request(:get, path('Notifications', notification_sid)))
30
30
  end
31
31
  end
32
32
  end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require_relative '../models/outgoing_caller_ids'
5
+
6
+ module VoiceML
7
+ # Operations on `/OutgoingCallerIds` — verified caller-id numbers.
8
+ class OutgoingCallerIdsResource < BaseResource
9
+ LIST_FIELDS = {
10
+ 'PhoneNumber' => :phone_number,
11
+ 'FriendlyName' => :friendly_name,
12
+ 'Page' => :page,
13
+ 'PageSize' => :page_size,
14
+ 'PageToken' => :page_token
15
+ }.freeze
16
+
17
+ CREATE_FIELDS = {
18
+ 'PhoneNumber' => :phone_number,
19
+ 'FriendlyName' => :friendly_name
20
+ }.freeze
21
+
22
+ UPDATE_FIELDS = {
23
+ 'FriendlyName' => :friendly_name
24
+ }.freeze
25
+
26
+ # List verified outgoing caller ids.
27
+ #
28
+ # @return [VoiceML::OutgoingCallerIdList]
29
+ def list(**kwargs)
30
+ OutgoingCallerIdList.from_hash(
31
+ @transport.request(:get, path('OutgoingCallerIds'),
32
+ params: form_params(LIST_FIELDS, kwargs))
33
+ )
34
+ end
35
+
36
+ # Start verification of a new outgoing caller id. The server places a
37
+ # validation call; the returned `VoiceML::ValidationRequest` carries the
38
+ # `validation_code` the user must enter on that call.
39
+ #
40
+ # @param phone_number [String] required. E.164.
41
+ # @param friendly_name [String, nil] display label.
42
+ # @return [VoiceML::ValidationRequest]
43
+ def create(phone_number:, friendly_name: nil)
44
+ ValidationRequest.from_hash(
45
+ @transport.request(:post, path('OutgoingCallerIds'),
46
+ form: form_params(CREATE_FIELDS,
47
+ { phone_number: phone_number,
48
+ friendly_name: friendly_name }))
49
+ )
50
+ end
51
+
52
+ # Fetch a verified caller id by its `PN`-prefixed sid.
53
+ #
54
+ # @return [VoiceML::OutgoingCallerId]
55
+ def fetch(sid)
56
+ OutgoingCallerId.from_hash(@transport.request(:get, path('OutgoingCallerIds', sid)))
57
+ end
58
+
59
+ # Update a caller id's friendly name.
60
+ #
61
+ # @return [VoiceML::OutgoingCallerId]
62
+ def update(sid, friendly_name: nil)
63
+ OutgoingCallerId.from_hash(
64
+ @transport.request(:post, path('OutgoingCallerIds', sid),
65
+ form: form_params(UPDATE_FIELDS, { friendly_name: friendly_name }))
66
+ )
67
+ end
68
+
69
+ # Remove a caller id.
70
+ #
71
+ # @return [nil]
72
+ def delete(sid)
73
+ @transport.request(:delete, path('OutgoingCallerIds', sid))
74
+ nil
75
+ end
76
+ end
77
+ end