plivo 4.17.1 → 4.21.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/plivo/utils.rb CHANGED
@@ -67,6 +67,117 @@ module Plivo
67
67
  expected_value?(param_name, expected_values, param_value)
68
68
  end
69
69
 
70
+ def valid_multiple_destination_nos?(param_name, param_value, options = nil)
71
+ if param_value.split(options[:delimiter]).size > 1 && options[:role].downcase != 'agent'
72
+ raise_invalid_request("Multiple #{param_name} values given for role #{options[:role]}")
73
+ elsif param_value.split(options[:delimiter]).size >= options[:agent_limit]
74
+ raise_invalid_request("No of #{param_name} values provided should be lesser than #{options[:agent_limit]}")
75
+ else
76
+ return true
77
+ end
78
+ end
79
+
80
+ def valid_multiple_destination_integers?(param_name, param_value)
81
+ if (param_value.is_a? String)
82
+ values = param_value.split("<")
83
+ for i in values
84
+ unless (Integer(i) rescue false)
85
+ raise_invalid_request("#{param_name} Destination Value must be integer")
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ def valid_url?(param_name, param_value, mandatory = false)
92
+ if mandatory && param_value.nil?
93
+ raise_invalid_request("#{param_name} is a required parameter")
94
+ end
95
+
96
+ return true if param_value.nil?
97
+ return raise_invalid_request("#{param_name}: Expected a String but received #{param_value.class} instead") unless expected_type?(param_name, String, param_value)
98
+
99
+ if param_value =~ /^(http[s]?:\/\/([a-zA-Z]|[0-9]|[\$\-\_\@\.\&\+\/\#]|[\!\*\(\)\,]|(%[0-9a-fA-F][0-9a-fA-F]))+|nil)$/ix
100
+ return true
101
+ else
102
+ return raise_invalid_request("Invalid URL : Doesn't satisfy the URL format")
103
+ end
104
+ end
105
+
106
+ def valid_range?(param_name, param_value, mandatory = false, lower_bound = nil, upper_bound = nil)
107
+ if mandatory && param_value.nil?
108
+ raise_invalid_request("#{param_name} is a required parameter")
109
+ end
110
+
111
+ return true if param_value.nil?
112
+
113
+ return raise_invalid_request("#{param_name}: Expected an Integer but received #{param_value.class} instead") unless expected_type?(param_name, Integer, param_value)
114
+ if lower_bound && upper_bound
115
+ return raise_invalid_request("#{param_name} ranges between #{lower_bound} and #{upper_bound}") if param_value < lower_bound or param_value > upper_bound
116
+
117
+ return true if param_value >= lower_bound and param_value <= upper_bound
118
+ elsif lower_bound
119
+ return raise_invalid_request("#{param_name} should be greater than #{lower_bound}") if param_value < lower_bound
120
+
121
+ return true if param_value >= lower_bound
122
+ elsif upper_bound
123
+ return raise_invalid_request("#{param_name} should be lesser than #{upper_bound}") if param_value > upper_bound
124
+
125
+ return true if param_value <= upper_bound
126
+ else
127
+ return raise_invalid_request("Any one or both of lower and upper bound should be provided")
128
+ end
129
+ end
130
+
131
+ def multi_valid_param?(param_name, param_value, expected_types = nil, mandatory = false, expected_values = nil, make_down_case = false, seperator = ',')
132
+ if mandatory && param_value.nil?
133
+ raise_invalid_request("#{param_name} is a required parameter")
134
+ end
135
+
136
+ return true if param_value.nil?
137
+
138
+ if make_down_case
139
+ param_value = param_value.downcase
140
+ else
141
+ param_value = param_value.uppercase
142
+ end
143
+
144
+ for val in param_value.split(seperator)
145
+ return expected_type?(param_name, expected_types, val.strip) unless expected_values
146
+ expected_value?(param_name, expected_values, val.strip)
147
+ end
148
+ end
149
+
150
+ def valid_date_format?(param_name, param_value, mandatory = false)
151
+ if mandatory && param_value.nil?
152
+ raise_invalid_request("#{param_name} is a required parameter")
153
+ end
154
+
155
+ return true if param_value.nil?
156
+
157
+ if param_value =~ /^(\d{4}\-\d{2}\-\d{2}\ \d{2}\:\d{2}(\:\d{2}(\.\d{1,6})?)?)$/ix
158
+ return true
159
+ else
160
+ return raise_invalid_request("Invalid Date Format")
161
+ end
162
+ end
163
+
164
+ def is_one_among_string_url?(param_name, param_value, mandatory = false, expected_values= nil)
165
+ if mandatory && param_value.nil?
166
+ raise_invalid_request("#{param_name} is a required parameter")
167
+ end
168
+
169
+ return true if param_value.nil?
170
+ return raise_invalid_request("#{param_name}: Expected a String but received #{param_value.class} instead") unless expected_type?(param_name, String, param_value)
171
+
172
+ if expected_values.include? param_value.downcase or expected_values.include? param_value.upcase
173
+ return true
174
+ elsif valid_url?(param_name, param_value)
175
+ return true
176
+ else
177
+ raise_invalid_request("#{param_name} neither a valid URL nor in the expected values")
178
+ end
179
+ end
180
+
70
181
  def expected_type?(param_name, expected_types, param_value)
71
182
  return true if expected_types.nil?
72
183
  param_value_class = param_value.class
data/lib/plivo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Plivo
2
- VERSION = "4.17.1".freeze
2
+ VERSION = "4.21.0".freeze
3
3
  end
@@ -0,0 +1,13 @@
1
+ module Plivo
2
+ module XML
3
+ class Cont < Element
4
+ @nestables = []
5
+ @valid_attributes = []
6
+
7
+ def initialize(body)
8
+ super(body)
9
+ end
10
+ end
11
+ end
12
+ end
13
+
@@ -80,8 +80,15 @@ module Plivo
80
80
  def add(element)
81
81
  raise PlivoXMLError, 'invalid element' unless element
82
82
  if @nestables.include?(element.name)
83
- @node.elements << element.node
84
- element
83
+ if element.name == "Cont"
84
+ @node.elements << element.node
85
+ element
86
+ temp = REXML::Text.new element.node.text
87
+ @node.elements['Cont'] = temp
88
+ else
89
+ @node.elements << element.node
90
+ element
91
+ end
85
92
  else
86
93
  raise PlivoXMLError, "#{element.name} not nestable in #{@name}"
87
94
  end
@@ -1,7 +1,7 @@
1
1
  module Plivo
2
2
  module XML
3
3
  class Emphasis < Element
4
- @nestables = %w(Break Emphasis Lang Phoneme Prosody SayAs Sub W)
4
+ @nestables = %w(Break Cont Emphasis Lang Phoneme Prosody SayAs Sub W)
5
5
  @valid_attributes = %w(level)
6
6
 
7
7
  VALID_LEVEL_ATTRIBUTE_VALUE=%w(strong moderate reduced)
@@ -1,7 +1,7 @@
1
1
  module Plivo
2
2
  module XML
3
3
  class Lang < Element
4
- @nestables = %w(Break Emphasis Lang P Phoneme Prosody S SayAs Sub W)
4
+ @nestables = %w(Break Cont Emphasis Lang P Phoneme Prosody S SayAs Sub W)
5
5
  @valid_attributes = %w(xmllang)
6
6
 
7
7
  VALID_LANG_ATTRIBUTE_VALUES = [
@@ -0,0 +1,188 @@
1
+ module Plivo
2
+ module XML
3
+ class MultiPartyCall < Element
4
+ @nestables = []
5
+ @valid_attributes = %w[role maxDuration maxParticipants waitMusicUrl
6
+ waitMusicMethod agentHoldMusicUrl agentHoldMusicMethod
7
+ customerHoldMusicUrl customerHoldMusicMethod record
8
+ recordFileFormat recordingCallbackUrl recordingCallbackMethod
9
+ statusCallbackEvents statusCallbackUrl statusCallbackMethod
10
+ stayAlone coachMode mute hold startMpcOnEnter endMpcOnExit
11
+ enterSound enterSoundMethod exitSound exitSoundMethod
12
+ onExitActionUrl onExitActionMethod relayDTMFInputs]
13
+
14
+ VALID_ROLE_VALUES = %w[agent supervisor customer]
15
+ VALID_METHOD_VALUES = %w[GET POST]
16
+ VALID_BOOL_VALUES = [true, false]
17
+ VALID_RECORD_FILE_FORMAT_VALUES = %w[mp3 wav]
18
+
19
+ def initialize(body, attributes = {})
20
+ if attributes[:role] && !VALID_ROLE_VALUES.include?(attributes[:role].downcase)
21
+ raise PlivoXMLError, "invalid attribute value #{attributes[:role]} for role"
22
+ elsif !attributes[:role]
23
+ raise PlivoXMLError, "role not mentioned : possible values - Agent / Supervisor / Customer"
24
+ end
25
+
26
+ if attributes[:maxDuration] && (attributes[:maxDuration]<300 || attributes[:maxDuration]>28800)
27
+ raise PlivoXMLError, "invalid attribute value #{attributes[:maxDuration]} for maxDuration"
28
+ elsif !attributes[:maxDuration]
29
+ attributes[:maxDuration] = 14400
30
+ end
31
+
32
+ if attributes[:maxParticipants] && (attributes[:maxParticipants]<2 || attributes[:maxParticipants]>10)
33
+ raise PlivoXMLError, "invalid attribute value #{attributes[:maxParticipants]} for maxParticipants"
34
+ elsif !attributes[:maxParticipants]
35
+ attributes[:maxParticipants] = 10
36
+ end
37
+
38
+ if attributes[:waitMusicMethod] && !VALID_METHOD_VALUES.include?(attributes[:waitMusicMethod].upcase)
39
+ raise PlivoXMLError, "invalid attribute value #{attributes[:waitMusicMethod]} for waitMusicMethod"
40
+ elsif !attributes[:waitMusicMethod]
41
+ attributes[:waitMusicMethod] = 'GET'
42
+ end
43
+
44
+ if attributes[:agentHoldMusicMethod] && !VALID_METHOD_VALUES.include?(attributes[:agentHoldMusicMethod].upcase)
45
+ raise PlivoXMLError, "invalid attribute value #{attributes[:agentHoldMusicMethod]} for agentHoldMusicMethod"
46
+ elsif !attributes[:agentHoldMusicMethod]
47
+ attributes[:agentHoldMusicMethod] = 'GET'
48
+ end
49
+
50
+ if attributes[:customerHoldMusicMethod] && !VALID_METHOD_VALUES.include?(attributes[:customerHoldMusicMethod].upcase)
51
+ raise PlivoXMLError, "invalid attribute value #{attributes[:customerHoldMusicMethod]} for customerHoldMusicMethod"
52
+ elsif !attributes[:customerHoldMusicMethod]
53
+ attributes[:customerHoldMusicMethod] = 'GET'
54
+ end
55
+
56
+ if attributes[:record] && !VALID_BOOL_VALUES.include?(attributes[:record])
57
+ raise PlivoXMLError, "invalid attribute value #{attributes[:record]} for record"
58
+ elsif !attributes[:record]
59
+ attributes[:record] = false
60
+ end
61
+
62
+ if attributes[:recordFileFormat] && !VALID_RECORD_FILE_FORMAT_VALUES.include?(attributes[:recordFileFormat])
63
+ raise PlivoXMLError, "invalid attribute value #{attributes[:recordFileFormat]} for recordFileFormat"
64
+ elsif !attributes[:recordFileFormat]
65
+ attributes[:recordFileFormat] = 'mp3'
66
+ end
67
+
68
+ if attributes[:recordingCallbackMethod] && !VALID_METHOD_VALUES.include?(attributes[:recordingCallbackMethod].upcase)
69
+ raise PlivoXMLError, "invalid attribute value #{attributes[:recordingCallbackMethod]} for recordingCallbackMethod"
70
+ elsif !attributes[:recordingCallbackMethod]
71
+ attributes[:recordingCallbackMethod] = 'GET'
72
+ end
73
+
74
+ if attributes[:statusCallbackEvents] && !multi_valid_param?(:statusCallbackEvents, attributes[:statusCallbackEvents], String, false, %w[mpc-state-changes participant-state-changes participant-speak-events participant-digit-input-events add-participant-api-events], true, ',') == true
75
+ raise PlivoXMLError, "invalid attribute value #{attributes[:statusCallbackEvents]} for statusCallbackEvents"
76
+ elsif !attributes[:statusCallbackEvents]
77
+ attributes[:statusCallbackEvents] = 'mpc-state-changes,participant-state-changes'
78
+ end
79
+
80
+ if attributes[:statusCallbackMethod] && !VALID_METHOD_VALUES.include?(attributes[:statusCallbackMethod].upcase)
81
+ raise PlivoXMLError, "invalid attribute value #{attributes[:statusCallbackMethod]} for statusCallbackMethod"
82
+ elsif !attributes[:statusCallbackMethod]
83
+ attributes[:statusCallbackMethod] = 'POST'
84
+ end
85
+
86
+ if attributes[:stayAlone] && !VALID_BOOL_VALUES.include?(attributes[:stayAlone])
87
+ raise PlivoXMLError, "invalid attribute value #{attributes[:stayAlone]} for stayAlone"
88
+ elsif !attributes[:stayAlone]
89
+ attributes[:stayAlone] = false
90
+ end
91
+
92
+ if attributes[:coachMode] && !VALID_BOOL_VALUES.include?(attributes[:coachMode])
93
+ raise PlivoXMLError, "invalid attribute value #{attributes[:coachMode]} for coachMode"
94
+ elsif !attributes[:coachMode]
95
+ attributes[:coachMode] = true
96
+ end
97
+
98
+ if attributes[:mute] && !VALID_BOOL_VALUES.include?(attributes[:mute])
99
+ raise PlivoXMLError, "invalid attribute value #{attributes[:mute]} for mute"
100
+ elsif !attributes[:mute]
101
+ attributes[:mute] = false
102
+ end
103
+
104
+ if attributes[:hold] && !VALID_BOOL_VALUES.include?(attributes[:hold])
105
+ raise PlivoXMLError, "invalid attribute value #{attributes[:hold]} for hold"
106
+ elsif !attributes[:hold]
107
+ attributes[:hold] = false
108
+ end
109
+
110
+ if attributes[:startMpcOnEnter] && !VALID_BOOL_VALUES.include?(attributes[:startMpcOnEnter])
111
+ raise PlivoXMLError, "invalid attribute value #{attributes[:startMpcOnEnter]} for startMpcOnEnter"
112
+ elsif !attributes[:startMpcOnEnter]
113
+ attributes[:startMpcOnEnter] = true
114
+ end
115
+
116
+ if attributes[:endMpcOnExit] && !VALID_BOOL_VALUES.include?(attributes[:endMpcOnExit])
117
+ raise PlivoXMLError, "invalid attribute value #{attributes[:endMpcOnExit]} for endMpcOnExit"
118
+ elsif !attributes[:endMpcOnExit]
119
+ attributes[:endMpcOnExit] = false
120
+ end
121
+
122
+ if attributes[:enterSound] && !is_one_among_string_url?(:enterSound, attributes[:enterSound], false, %w[beep:1 beep:2 none])
123
+ raise PlivoXMLError, "invalid attribute value #{attributes[:enterSound]} for enterSound"
124
+ elsif !attributes[:enterSound]
125
+ attributes[:enterSound] = 'beep:1'
126
+ end
127
+
128
+ if attributes[:enterSoundMethod] && !VALID_METHOD_VALUES.include?(attributes[:enterSoundMethod].upcase)
129
+ raise PlivoXMLError, "invalid attribute value #{attributes[:enterSoundMethod]} for enterSoundMethod"
130
+ elsif !attributes[:enterSoundMethod]
131
+ attributes[:enterSoundMethod] = 'GET'
132
+ end
133
+
134
+ if attributes[:exitSound] && !is_one_among_string_url?(:exitSound, attributes[:exitSound], false, %w[beep:1 beep:2 none])
135
+ raise PlivoXMLError, "invalid attribute value #{attributes[:exitSound]} for exitSound"
136
+ elsif !attributes[:exitSound]
137
+ attributes[:exitSound] = 'beep:2'
138
+ end
139
+
140
+ if attributes[:exitSoundMethod] && !VALID_METHOD_VALUES.include?(attributes[:exitSoundMethod].upcase)
141
+ raise PlivoXMLError, "invalid attribute value #{attributes[:exitSoundMethod]} for exitSoundMethod"
142
+ elsif !attributes[:exitSoundMethod]
143
+ attributes[:exitSoundMethod] = 'GET'
144
+ end
145
+
146
+ if attributes[:onExitActionMethod] && !VALID_METHOD_VALUES.include?(attributes[:onExitActionMethod].upcase)
147
+ raise PlivoXMLError, "invalid attribute value #{attributes[:onExitActionMethod]} for onExitActionMethod"
148
+ elsif !attributes[:onExitActionMethod]
149
+ attributes[:onExitActionMethod] = 'POST'
150
+ end
151
+
152
+ if attributes[:relayDTMFInputs] && !VALID_BOOL_VALUES.include?(attributes[:relayDTMFInputs])
153
+ raise PlivoXMLError, "invalid attribute value #{attributes[:relayDTMFInputs]} for relayDTMFInputs"
154
+ elsif !attributes[:relayDTMFInputs]
155
+ attributes[:relayDTMFInputs] = false
156
+ end
157
+
158
+ if attributes[:waitMusicUrl] && !valid_url?(:waitMusicUrl, attributes[:waitMusicUrl], false)
159
+ raise PlivoXMLError, "invalid attribute value #{attributes[:waitMusicUrl]} for waitMusicUrl"
160
+ end
161
+
162
+ if attributes[:agentHoldMusicUrl] && !valid_url?(:agentHoldMusicUrl, attributes[:agentHoldMusicUrl], false)
163
+ raise PlivoXMLError, "invalid attribute value #{attributes[:agentHoldMusicUrl]} for agentHoldMusicUrl"
164
+ end
165
+
166
+ if attributes[:customerHoldMusicUrl] && !valid_url?(:customerHoldMusicUrl, attributes[:customerHoldMusicUrl], false)
167
+ raise PlivoXMLError, "invalid attribute value #{attributes[:customerHoldMusicUrl]} for customerHoldMusicUrl"
168
+ end
169
+
170
+ if attributes[:recordingCallbackUrl] && !valid_url?(:recordingCallbackUrl, attributes[:recordingCallbackUrl], false)
171
+ raise PlivoXMLError, "invalid attribute value #{attributes[:recordingCallbackUrl]} for recordingCallbackUrl"
172
+ end
173
+
174
+ if attributes[:statusCallbackUrl] && !valid_url?(:statusCallbackUrl, attributes[:statusCallbackUrl], false)
175
+ raise PlivoXMLError, "invalid attribute value #{attributes[:statusCallbackUrl]} for statusCallbackUrl"
176
+ end
177
+
178
+ if attributes[:customerHoldMusicUrl] && !valid_url?(:customerHoldMusicUrl, attributes[:customerHoldMusicUrl], false)
179
+ raise PlivoXMLError, "invalid attribute value #{attributes[:customerHoldMusicUrl]} for customerHoldMusicUrl"
180
+ end
181
+
182
+ raise PlivoXMLError, 'No MPC name set for the MPC' unless body
183
+ super(body, attributes)
184
+ end
185
+
186
+ end
187
+ end
188
+ end
data/lib/plivo/xml/p.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  module Plivo
2
2
  module XML
3
3
  class P < Element
4
- @nestables = %w(Break Emphasis Lang Phoneme Prosody SayAs Sub S W)
4
+ @nestables = %w(Break Cont Emphasis Lang Phoneme Prosody SayAs Sub S W)
5
5
  @valid_attributes = []
6
6
 
7
7
  def initialize(body)
@@ -1,7 +1,7 @@
1
1
  module Plivo
2
2
  module XML
3
3
  class Prosody < Element
4
- @nestables = %w(Break Emphasis Lang P Phoneme Prosody S SayAs Sub W)
4
+ @nestables = %w(Break Cont Emphasis Lang P Phoneme Prosody S SayAs Sub W)
5
5
  @valid_attributes = %w(volume rate pitch)
6
6
 
7
7
  VALID_VOLUME_ATTRIBUTE_VALUES=%w(default silent x-soft soft medium loud x-loud)
@@ -2,7 +2,7 @@ module Plivo
2
2
  module XML
3
3
  class Response < Element
4
4
  @nestables = %w[Speak Play GetDigits GetInput Record Dial Message
5
- Redirect Wait Hangup PreAnswer Conference DTMF]
5
+ Redirect Wait Hangup PreAnswer Conference DTMF MultiPartyCall]
6
6
  @valid_attributes = []
7
7
 
8
8
  def initialize
data/lib/plivo/xml/s.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  module Plivo
2
2
  module XML
3
3
  class S < Element
4
- @nestables = %w(Break Emphasis Lang Phoneme Prosody SayAs Sub W)
4
+ @nestables = %w(Break Cont Emphasis Lang Phoneme Prosody SayAs Sub W)
5
5
  @valid_attributes = []
6
6
 
7
7
  def initialize(body)
@@ -13,7 +13,7 @@ module Plivo
13
13
  engine = attributes[:voice].split('.')[0]
14
14
  voice = attributes[:voice].split('.')[1]
15
15
  if SUPPORTED_ENGINES.include?(engine) && SUPPORTED_VOICES.include?(voice)
16
- @nestables = %w(Break Emphasis Lang P Phoneme Prosody S SayAs Sub W)
16
+ @nestables = %w(Break Cont Emphasis Lang P Phoneme Prosody S SayAs Sub W)
17
17
  else
18
18
  raise PlivoXMLError, "<Speak> voice #{attributes[:voice]} is not valid."
19
19
  end
data/lib/plivo/xml/w.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  module Plivo
2
2
  module XML
3
3
  class W < Element
4
- @nestables = %w(Break Emphasis Phoneme Prosody SayAs Sub)
4
+ @nestables = %w(Break Cont Emphasis Phoneme Prosody SayAs Sub)
5
5
  @valid_attributes = %w(role)
6
6
 
7
7
  VALID_ROLE_ATTRIBUTE_VALUES=%w(amazon:VB amazon:VBD amazon:SENSE_1)
data/lib/plivo/xml.rb CHANGED
@@ -29,7 +29,8 @@ require_relative 'xml/say_as'
29
29
  require_relative 'xml/sub'
30
30
  require_relative 'xml/w'
31
31
  require_relative 'xml/plivo_xml'
32
-
32
+ require_relative 'xml/multipartycall'
33
+ require_relative 'xml/cont'
33
34
  include Plivo::Exceptions
34
35
 
35
36
  module Plivo
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plivo
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.17.1
4
+ version: 4.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - The Plivo SDKs Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-05 00:00:00.000000000 Z
11
+ date: 2021-10-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -190,6 +190,7 @@ files:
190
190
  - lib/plivo/resources/lookup.rb
191
191
  - lib/plivo/resources/media.rb
192
192
  - lib/plivo/resources/messages.rb
193
+ - lib/plivo/resources/multipartycalls.rb
193
194
  - lib/plivo/resources/nodes.rb
194
195
  - lib/plivo/resources/numbers.rb
195
196
  - lib/plivo/resources/phlo_member.rb
@@ -204,6 +205,7 @@ files:
204
205
  - lib/plivo/xml.rb
205
206
  - lib/plivo/xml/break.rb
206
207
  - lib/plivo/xml/conference.rb
208
+ - lib/plivo/xml/cont.rb
207
209
  - lib/plivo/xml/dial.rb
208
210
  - lib/plivo/xml/dtmf.rb
209
211
  - lib/plivo/xml/element.rb
@@ -213,6 +215,7 @@ files:
213
215
  - lib/plivo/xml/hangup.rb
214
216
  - lib/plivo/xml/lang.rb
215
217
  - lib/plivo/xml/message.rb
218
+ - lib/plivo/xml/multipartycall.rb
216
219
  - lib/plivo/xml/number.rb
217
220
  - lib/plivo/xml/p.rb
218
221
  - lib/plivo/xml/phoneme.rb