plivo 0.3.19 → 4.20.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +14 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +11 -0
  5. data/AUTHORS.md +4 -0
  6. data/CHANGELOG.md +158 -0
  7. data/Gemfile +10 -0
  8. data/Jenkinsfile +7 -0
  9. data/LICENSE.txt +19 -0
  10. data/README.md +155 -24
  11. data/Rakefile +9 -0
  12. data/ci/config.yml +7 -0
  13. data/examples/conference_bridge.rb +108 -0
  14. data/examples/jwt.rb +32 -0
  15. data/examples/lookup.rb +24 -0
  16. data/examples/multi_party_call.rb +295 -0
  17. data/examples/phlos.rb +55 -0
  18. data/examples/regulatory_compliance.rb +167 -0
  19. data/lib/plivo/base/resource.rb +148 -0
  20. data/lib/plivo/base/resource_interface.rb +108 -0
  21. data/lib/plivo/base/response.rb +38 -0
  22. data/lib/plivo/base.rb +17 -0
  23. data/lib/plivo/base_client.rb +393 -0
  24. data/lib/plivo/exceptions.rb +50 -0
  25. data/lib/plivo/jwt.rb +120 -0
  26. data/lib/plivo/phlo_client.rb +29 -0
  27. data/lib/plivo/resources/accounts.rb +181 -0
  28. data/lib/plivo/resources/addresses.rb +302 -0
  29. data/lib/plivo/resources/applications.rb +258 -0
  30. data/lib/plivo/resources/call_feedback.rb +55 -0
  31. data/lib/plivo/resources/calls.rb +559 -0
  32. data/lib/plivo/resources/conferences.rb +367 -0
  33. data/lib/plivo/resources/endpoints.rb +132 -0
  34. data/lib/plivo/resources/identities.rb +319 -0
  35. data/lib/plivo/resources/lookup.rb +88 -0
  36. data/lib/plivo/resources/media.rb +97 -0
  37. data/lib/plivo/resources/messages.rb +215 -0
  38. data/lib/plivo/resources/multipartycalls.rb +554 -0
  39. data/lib/plivo/resources/nodes.rb +83 -0
  40. data/lib/plivo/resources/numbers.rb +319 -0
  41. data/lib/plivo/resources/phlo_member.rb +64 -0
  42. data/lib/plivo/resources/phlos.rb +55 -0
  43. data/lib/plivo/resources/powerpacks.rb +717 -0
  44. data/lib/plivo/resources/pricings.rb +43 -0
  45. data/lib/plivo/resources/recordings.rb +116 -0
  46. data/lib/plivo/resources/regulatory_compliance.rb +610 -0
  47. data/lib/plivo/resources.rb +25 -0
  48. data/lib/plivo/rest_client.rb +63 -0
  49. data/lib/plivo/utils.rb +294 -0
  50. data/lib/plivo/version.rb +3 -0
  51. data/lib/plivo/xml/break.rb +31 -0
  52. data/lib/plivo/xml/conference.rb +20 -0
  53. data/lib/plivo/xml/cont.rb +13 -0
  54. data/lib/plivo/xml/dial.rb +16 -0
  55. data/lib/plivo/xml/dtmf.rb +13 -0
  56. data/lib/plivo/xml/element.rb +106 -0
  57. data/lib/plivo/xml/emphasis.rb +17 -0
  58. data/lib/plivo/xml/get_digits.rb +15 -0
  59. data/lib/plivo/xml/get_input.rb +16 -0
  60. data/lib/plivo/xml/hangup.rb +12 -0
  61. data/lib/plivo/xml/lang.rb +29 -0
  62. data/lib/plivo/xml/message.rb +13 -0
  63. data/lib/plivo/xml/multipartycall.rb +188 -0
  64. data/lib/plivo/xml/number.rb +13 -0
  65. data/lib/plivo/xml/p.rb +12 -0
  66. data/lib/plivo/xml/phoneme.rb +20 -0
  67. data/lib/plivo/xml/play.rb +13 -0
  68. data/lib/plivo/xml/plivo_xml.rb +19 -0
  69. data/lib/plivo/xml/pre_answer.rb +12 -0
  70. data/lib/plivo/xml/prosody.rb +28 -0
  71. data/lib/plivo/xml/record.rb +17 -0
  72. data/lib/plivo/xml/redirect.rb +13 -0
  73. data/lib/plivo/xml/response.rb +21 -0
  74. data/lib/plivo/xml/s.rb +12 -0
  75. data/lib/plivo/xml/say_as.rb +24 -0
  76. data/lib/plivo/xml/speak.rb +28 -0
  77. data/lib/plivo/xml/sub.rb +16 -0
  78. data/lib/plivo/xml/user.rb +13 -0
  79. data/lib/plivo/xml/w.rb +17 -0
  80. data/lib/plivo/xml/wait.rb +12 -0
  81. data/lib/plivo/xml.rb +39 -0
  82. data/lib/plivo.rb +12 -815
  83. data/plivo.gemspec +44 -0
  84. metadata +181 -41
  85. data/ext/mkrf_conf.rb +0 -9
@@ -0,0 +1,367 @@
1
+ module Plivo
2
+ module Resources
3
+ include Plivo::Utils
4
+ class Conference < Base::Resource
5
+ def initialize(client, options = nil)
6
+ @_name = 'Conference'
7
+ @_identifier_string = 'conference_name'
8
+ super
9
+ @_is_voice_request = true
10
+ end
11
+
12
+ def delete
13
+ perform_delete
14
+ end
15
+
16
+ # @param [String] member_id
17
+ def delete_member(member_id)
18
+ valid_param?(:member_id, member_id, [String, Symbol, Integer, Integer], true)
19
+ perform_action('Member/' + member_id.to_s, 'DELETE', nil, true)
20
+ end
21
+
22
+ # @param [String] member_id
23
+ def kick_member(member_id)
24
+ valid_param?(:member_id, member_id, [String, Symbol, Integer, Integer], true)
25
+ perform_action('Member/' + member_id.to_s + '/Kick', 'POST', nil, true)
26
+ end
27
+
28
+ # @param [Array] member_id
29
+ def mute_member(member_id)
30
+ valid_param?(:member_id, member_id, Array, true)
31
+ member_id.each do |member|
32
+ valid_param?(:member, member, [String, Symbol, Integer, Integer], true)
33
+ end
34
+ perform_action('Member/' + member_id.join(',') + '/Mute',
35
+ 'POST', nil, true)
36
+ end
37
+
38
+ # @param [Array] member_id
39
+ def unmute_member(member_id)
40
+ valid_param?(:member_id, member_id, Array, true)
41
+ member_id.each do |member|
42
+ valid_param?(:member, member, [String, Symbol, Integer, Integer], true)
43
+ end
44
+ perform_action('Member/' + member_id.join(',') + '/Mute', 'DELETE')
45
+ end
46
+
47
+ # @param [Array] member_id
48
+ # @param [String] url
49
+ def play_member(member_id, url)
50
+ valid_param?(:member_id, member_id, Array, true)
51
+ valid_param?(:url, url, String, true)
52
+ member_id.each do |member|
53
+ valid_param?(:member, member, [String, Symbol, Integer, Integer], true)
54
+ end
55
+ perform_action('Member/' + member_id.join(',') + '/Play',
56
+ 'POST', { url: url }, true)
57
+ end
58
+
59
+ # @param [Array] member_id
60
+ def stop_play_member(member_id)
61
+ valid_param?(:member_id, member_id, Array, true)
62
+ member_id.each do |member|
63
+ valid_param?(:member, member, [String, Symbol, Integer, Integer], true)
64
+ end
65
+ perform_action('Member/' + member_id.join(',') + '/Play',
66
+ 'DELETE', nil, true)
67
+ end
68
+
69
+ # @param [Array] member_id
70
+ # @param [String] text - The text that the member must hear.
71
+ # @param [Hash] options
72
+ # @option options [String] :voice - The voice to be used. Can be MAN or WOMAN. Defaults to WOMAN.
73
+ # @option options [String] :language - The language to be used, see Supported voices and languages {https://www.plivo.com/docs/api/conference/member/#supported-voice-and-languages}. Defaults to en-US .
74
+ def speak_member(member_id, text, options = nil)
75
+ valid_param?(:member_id, member_id, Array, true)
76
+ valid_param?(:text, text, String, true)
77
+ member_id.each do |member|
78
+ valid_param?(:member, member, [String, Symbol, Integer, Integer], true)
79
+ end
80
+
81
+ params = { text: text }
82
+
83
+ if options.nil?
84
+ return perform_action('Member/' + member_id.join(',') + '/Speak',
85
+ 'POST', params, true)
86
+ end
87
+
88
+ if options.key?(:voice) &&
89
+ valid_param?(:voice, options[:voice],
90
+ [String, Symbol], true, %w[MAN WOMAN])
91
+ params[:voice] = options[:voice]
92
+ end
93
+
94
+ if options.key?(:language) &&
95
+ valid_param?(:language, options[:language],
96
+ String, true)
97
+ params[:language] = options[:language]
98
+ end
99
+
100
+ perform_action('Member/' + member_id.join(',') + '/Speak',
101
+ 'POST', params, true)
102
+ end
103
+
104
+ # @param [Array] member_id
105
+ def stop_speak_member(member_id)
106
+ valid_param?(:member_id, member_id, Array, true)
107
+ member_id.each do |member|
108
+ valid_param?(:member, member, [String, Symbol, Integer, Integer], true)
109
+ end
110
+ perform_action('Member/' + member_id.join(',') + '/Speak',
111
+ 'DELETE', nil, true)
112
+ end
113
+
114
+ # @param [Array] member_id
115
+ def deaf_member(member_id)
116
+ valid_param?(:member_id, member_id, Array, true)
117
+ member_id.each do |member|
118
+ valid_param?(:member, member, [String, Symbol, Integer, Integer], true)
119
+ end
120
+ perform_action('Member/' + member_id.join(',') + '/Deaf',
121
+ 'POST', nil, true)
122
+ end
123
+
124
+ # @param [Array] member_id
125
+ def undeaf_member(member_id)
126
+ valid_param?(:member_id, member_id, Array, true)
127
+ member_id.each do |member|
128
+ valid_param?(:member, member, [String, Symbol, Integer, Integer], true)
129
+ end
130
+ perform_action('Member/' + member_id.join(',') + '/Deaf',
131
+ 'DELETE', nil, true)
132
+ end
133
+
134
+ # @param [Hash] options
135
+ # @option options [String] :file_format The file format of the record can be of mp3 or wav format. Defaults to mp3 format.
136
+ # @option options [String] :transcription_type The type of transcription required. The following values are allowed:
137
+ # - auto - This is the default value. Transcription is completely automated; turnaround time is about 5 minutes.
138
+ # - hybrid - Transcription is a combination of automated and human verification processes; turnaround time is about 10-15 minutes.
139
+ # @option options [String] :transcription_url The URL where the transcription is available.
140
+ # @option options [String] :transcription_method The method used to invoke the transcription_url. Defaults to POST.
141
+ # @option options [String] :callback_url The URL invoked by the API when the recording ends. The following parameters are sent to the callback_url:
142
+ # - api_id - the same API ID returned by the conference record API.
143
+ # - record_url - the URL to access the recorded file.
144
+ # - recording_id - recording ID of the recorded file.
145
+ # - conference_name - the conference name recorded.
146
+ # - recording_duration - duration in seconds of the recording.
147
+ # - recording_duration_ms - duration in milliseconds of the recording.
148
+ # - recording_start_ms - when the recording started (epoch time UTC) in milliseconds.
149
+ # - recording_end_ms - when the recording ended (epoch time UTC) in milliseconds.
150
+ # @option options [String] :callback_method The method which is used to invoke the callback_url URL. Defaults to POST.
151
+ def record(options = nil)
152
+ return perform_action('Record', 'POST', nil, true) if options.nil?
153
+ valid_param?(:options, options, Hash, true)
154
+
155
+ params = {}
156
+ %i[transcription_url callback_url].each do |param|
157
+ if options.key?(param) &&
158
+ valid_param?(param, options[param], [String, Symbol], true)
159
+ params[param] = options[param]
160
+ end
161
+ end
162
+
163
+ %i[transcription_method callback_method].each do |param|
164
+ if options.key?(param) &&
165
+ valid_param?(param, options[param], [String, Symbol], true, %w[GET POST])
166
+ params[param] = options[param]
167
+ end
168
+ end
169
+
170
+ if options.key?(:file_format) &&
171
+ valid_param?(:file_format, options[:file_format],
172
+ [String, Symbol], true, %w[wav mp3])
173
+ params[:file_format] = options[:file_format]
174
+ end
175
+
176
+ if options.key?(:transcription_type) &&
177
+ valid_param?(:transcription_type, options[:transcription_type],
178
+ [String, Symbol], true, %w[auto hybrid])
179
+ params[:transcription_type] = options[:transcription_type]
180
+ end
181
+
182
+ perform_action('Record', 'POST', params, true)
183
+ end
184
+
185
+ def stop_record
186
+ perform_action('Record', 'DELETE')
187
+ end
188
+
189
+ def to_s
190
+ response_json = {}
191
+ response_variables = self.instance_variables.drop(5)
192
+ response_variables.each do |variable|
193
+ response_json[variable.to_s[1..-1]] = self.instance_variable_get(variable)
194
+ end
195
+ return response_json.to_s
196
+ end
197
+
198
+ def to_json_member(member)
199
+ {
200
+ muted: member['muted'],
201
+ member_id: member['member_id'],
202
+ deaf: member['deaf'],
203
+ from: member['from'],
204
+ to: member['to'],
205
+ caller_name: member['caller_name'],
206
+ direction: member['direction'],
207
+ call_uuid: member['call_uuid'],
208
+ join_time: member['join_time']
209
+ }.to_json
210
+ end
211
+ end
212
+
213
+ class ConferenceInterface < Base::ResourceInterface
214
+ def initialize(client, resource_list_json = nil)
215
+ @_name = 'Conference'
216
+ @_resource_type = Conference
217
+ @_identifier_string = 'conference_name'
218
+ super
219
+ @_is_voice_request = true
220
+ end
221
+
222
+ def get(conference_name)
223
+ perform_get(conference_name)
224
+ end
225
+
226
+ def list
227
+ perform_list_without_object
228
+ {
229
+ api_id: @api_id,
230
+ conferences: @conferences
231
+ }
232
+ end
233
+
234
+ def each
235
+ conference_list = list
236
+ conference_list[:conferences].each { |conference| yield conference }
237
+ end
238
+
239
+ def delete_all
240
+ Conference.new(@_client, resource_id: '').delete
241
+ end
242
+
243
+ def delete(conference_name)
244
+ valid_param?(:conference_name, conference_name, [String, Symbol], true)
245
+ if conference_name.empty?
246
+ raise_invalid_request('Invalid conference_name passed')
247
+ end
248
+ Conference.new(@_client, resource_id: conference_name).delete
249
+ end
250
+
251
+ # @param [String] conference_name
252
+ # @param [String] member_id
253
+ def delete_member(conference_name, member_id)
254
+ valid_param?(:conference_name, conference_name, [String, Symbol], true)
255
+ Conference.new(@_client, resource_id: conference_name)
256
+ .delete_member(member_id)
257
+ end
258
+
259
+ # @param [String] conference_name
260
+ # @param [String] member_id
261
+ def kick_member(conference_name, member_id)
262
+ valid_param?(:conference_name, conference_name, [String, Symbol], true)
263
+ Conference.new(@_client, resource_id: conference_name)
264
+ .kick_member(member_id)
265
+ end
266
+
267
+ # @param [String] conference_name
268
+ # @param [Array] member_id
269
+ def mute_member(conference_name, member_id)
270
+ valid_param?(:conference_name, conference_name, [String, Symbol], true)
271
+ Conference.new(@_client, resource_id: conference_name)
272
+ .mute_member(member_id)
273
+ end
274
+
275
+ # @param [String] conference_name
276
+ # @param [Array] member_id
277
+ def unmute_member(conference_name, member_id)
278
+ valid_param?(:conference_name, conference_name, [String, Symbol], true)
279
+ Conference.new(@_client, resource_id: conference_name)
280
+ .unmute_member(member_id)
281
+ end
282
+
283
+ # @param [String] conference_name
284
+ # @param [Array] member_id
285
+ def play_member(conference_name, member_id, url)
286
+ valid_param?(:conference_name, conference_name, [String, Symbol], true)
287
+ Conference.new(@_client, resource_id: conference_name)
288
+ .play_member(member_id, url)
289
+ end
290
+
291
+ # @param [String] conference_name
292
+ # @param [Array] member_id
293
+ def stop_play_member(conference_name, member_id)
294
+ valid_param?(:conference_name, conference_name, [String, Symbol], true)
295
+ Conference.new(@_client, resource_id: conference_name)
296
+ .stop_play_member(member_id)
297
+ end
298
+
299
+ # @param [String] conference_name
300
+ # @param [Array] member_id
301
+ # @param [String] text - The text that the member must hear.
302
+ # @param [Hash] options
303
+ # @option options [String] :voice - The voice to be used. Can be MAN or WOMAN. Defaults to WOMAN.
304
+ # @option options [String] :language - The language to be used, see Supported voices and languages {https://www.plivo.com/docs/api/conference/member/#supported-voice-and-languages}. Defaults to en-US .
305
+ def speak_member(conference_name, member_id, text, options = nil)
306
+ valid_param?(:conference_name, conference_name, [String, Symbol], true)
307
+ Conference.new(@_client, resource_id: conference_name)
308
+ .speak_member(member_id, text, options)
309
+ end
310
+
311
+ # @param [String] conference_name
312
+ # @param [Array] member_id
313
+ def stop_speak_member(conference_name, member_id)
314
+ valid_param?(:conference_name, conference_name, [String, Symbol], true)
315
+ Conference.new(@_client, resource_id: conference_name)
316
+ .stop_speak_member(member_id)
317
+ end
318
+
319
+ # @param [String] conference_name
320
+ # @param [Array] member_id
321
+ def deaf_member(conference_name, member_id)
322
+ valid_param?(:conference_name, conference_name, [String, Symbol], true)
323
+ Conference.new(@_client, resource_id: conference_name)
324
+ .deaf_member(member_id)
325
+ end
326
+
327
+ # @param [String] conference_name
328
+ # @param [Array] member_id
329
+ def undeaf_member(conference_name, member_id)
330
+ valid_param?(:conference_name, conference_name, [String, Symbol], true)
331
+ Conference.new(@_client, resource_id: conference_name)
332
+ .undeaf_member(member_id)
333
+ end
334
+
335
+ # @param [String] conference_name
336
+ # @param [Hash] options
337
+ # @option options [String] :file_format The file format of the record can be of mp3 or wav format. Defaults to mp3 format.
338
+ # @option options [String] :transcription_type The type of transcription required. The following values are allowed:
339
+ # - auto - This is the default value. Transcription is completely automated; turnaround time is about 5 minutes.
340
+ # - hybrid - Transcription is a combination of automated and human verification processes; turnaround time is about 10-15 minutes.
341
+ # @option options [String] :transcription_url The URL where the transcription is available.
342
+ # @option options [String] :transcription_method The method used to invoke the transcription_url. Defaults to POST.
343
+ # @option options [String] :callback_url The URL invoked by the API when the recording ends. The following parameters are sent to the callback_url:
344
+ # - api_id - the same API ID returned by the conference record API.
345
+ # - record_url - the URL to access the recorded file.
346
+ # - recording_id - recording ID of the recorded file.
347
+ # - conference_name - the conference name recorded.
348
+ # - recording_duration - duration in seconds of the recording.
349
+ # - recording_duration_ms - duration in milliseconds of the recording.
350
+ # - recording_start_ms - when the recording started (epoch time UTC) in milliseconds.
351
+ # - recording_end_ms - when the recording ended (epoch time UTC) in milliseconds.
352
+ # @option options [String] :callback_method The method which is used to invoke the callback_url URL. Defaults to POST.
353
+ def record(conference_name, options = nil)
354
+ valid_param?(:conference_name, conference_name, [String, Symbol], true)
355
+ Conference.new(@_client, resource_id: conference_name)
356
+ .record(options)
357
+ end
358
+
359
+ # @param [String] conference_name
360
+ def stop_record(conference_name)
361
+ valid_param?(:conference_name, conference_name, [String, Symbol], true)
362
+ Conference.new(@_client, resource_id: conference_name)
363
+ .stop_record
364
+ end
365
+ end
366
+ end
367
+ end
@@ -0,0 +1,132 @@
1
+ module Plivo
2
+ module Resources
3
+ include Plivo::Utils
4
+ class Endpoint < Base::Resource
5
+ def initialize(client, options = nil)
6
+ @_name = 'Endpoint'
7
+ @_identifier_string = 'endpoint_id'
8
+ super
9
+ @_is_voice_request = true
10
+ end
11
+
12
+ # @param [Hash] options
13
+ # @option options [String] :password The password for your endpoint username.
14
+ # @option options [String] :alias Alias for this endpoint
15
+ # @option options [String] :app_id The app_id of the application that is to be attached to this endpoint. If app_id is not specified, then the endpoint does not point to any application.
16
+ def update(options = nil)
17
+ return if options.nil?
18
+ valid_param?(:options, options, Hash, true)
19
+
20
+ params = {}
21
+ params_expected = %i[password alias app_id]
22
+ params_expected.each do |param|
23
+ if options.key?(param) &&
24
+ valid_param?(param, options[param], [String, Symbol], true)
25
+ params[param] = options[param]
26
+ end
27
+ end
28
+
29
+ perform_update(params)
30
+ end
31
+
32
+ def delete
33
+ perform_delete
34
+ end
35
+
36
+ attr_reader :password
37
+
38
+ attr_reader :sip_expires
39
+
40
+ def sip_contact
41
+ @sip_expires
42
+ end
43
+
44
+ def sip_user_agent
45
+ @sip_expires
46
+ end
47
+
48
+ def to_s
49
+ {
50
+ alias: @alias,
51
+ application: @application,
52
+ endpoint_id: @endpoint_id,
53
+ resource_uri: @resource_uri,
54
+ sip_contact: @sip_contact,
55
+ sip_expires: @sip_expires,
56
+ sip_registered: @sip_registered,
57
+ sip_uri: @sip_uri,
58
+ sip_user_agent: @sip_user_agent,
59
+ sub_account: @sub_account,
60
+ username: @username,
61
+ password: @password
62
+ }.to_s
63
+ end
64
+ end
65
+
66
+ # @!method get
67
+ # @!method create
68
+ # @!method list
69
+ class EndpointInterface < Base::ResourceInterface
70
+ def initialize(client, resource_list_json = nil)
71
+ @_name = 'Endpoint'
72
+ @_resource_type = Endpoint
73
+ @_identifier_string = 'endpoint_id'
74
+ super
75
+ @_is_voice_request = true
76
+ end
77
+
78
+ # @param [String] endpoint_id
79
+ def get(endpoint_id)
80
+ valid_param?(:endpoint_id, endpoint_id, [String, Symbol], true)
81
+ perform_get(endpoint_id)
82
+ end
83
+
84
+ # @param [String] username
85
+ # @param [String] password
86
+ # @param [String] alias_
87
+ # @param [String] app_id
88
+ def create(username, password, alias_, app_id = nil)
89
+ valid_param?(:username, username, [String, Symbol], true)
90
+ valid_param?(:password, password, [String, Symbol], true)
91
+ valid_param?(:alias, alias_, [String, Symbol], true)
92
+
93
+ params = {
94
+ username: username,
95
+ password: password,
96
+ alias: alias_
97
+ }
98
+
99
+ params[:app_id] = app_id unless app_id.nil?
100
+
101
+ perform_create(params)
102
+ end
103
+
104
+ def list
105
+ perform_list
106
+ end
107
+
108
+ def each
109
+ endpoint_list = list
110
+ endpoint_list[:objects].each { |endpoint| yield endpoint }
111
+ end
112
+
113
+ # @param [String] endpoint_id
114
+ # @param [Hash] options
115
+ # @option options [String] :password The password for your endpoint username.
116
+ # @option options [String] :alias Alias for this endpoint
117
+ # @option options [String] :app_id The app_id of the application that is to be attached to this endpoint. If app_id is not specified, then the endpoint does not point to any application.
118
+ def update(endpoint_id, options = nil)
119
+ valid_param?(:endpoint_id, endpoint_id, [String, Symbol], true)
120
+ Endpoint.new(@_client,
121
+ resource_id: endpoint_id).update(options)
122
+ end
123
+
124
+ # @param [String] endpoint_id
125
+ def delete(endpoint_id)
126
+ valid_param?(:endpoint_id, endpoint_id, [String, Symbol], true)
127
+ Endpoint.new(@_client,
128
+ resource_id: endpoint_id).delete
129
+ end
130
+ end
131
+ end
132
+ end