punchblock 1.9.4 → 2.0.0.beta1

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 (142) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +1 -2
  4. data/CHANGELOG.md +17 -0
  5. data/Gemfile +1 -0
  6. data/Guardfile +4 -0
  7. data/README.markdown +6 -0
  8. data/Rakefile +16 -0
  9. data/benchmarks/ami_event_name_comparison.rb +14 -0
  10. data/benchmarks/channel.rb +27 -0
  11. data/lib/punchblock/client.rb +2 -6
  12. data/lib/punchblock/command/accept.rb +3 -24
  13. data/lib/punchblock/command/answer.rb +3 -24
  14. data/lib/punchblock/command/dial.rb +24 -76
  15. data/lib/punchblock/command/hangup.rb +3 -19
  16. data/lib/punchblock/command/join.rb +21 -70
  17. data/lib/punchblock/command/mute.rb +3 -3
  18. data/lib/punchblock/command/redirect.rb +6 -39
  19. data/lib/punchblock/command/reject.rb +14 -54
  20. data/lib/punchblock/command/unjoin.rb +8 -40
  21. data/lib/punchblock/command/unmute.rb +3 -3
  22. data/lib/punchblock/command_node.rb +0 -17
  23. data/lib/punchblock/component/asterisk/agi/command.rb +20 -127
  24. data/lib/punchblock/component/asterisk/ami/action.rb +30 -117
  25. data/lib/punchblock/component/component_node.rb +1 -1
  26. data/lib/punchblock/component/input.rb +89 -268
  27. data/lib/punchblock/component/output.rb +106 -154
  28. data/lib/punchblock/component/prompt.rb +51 -0
  29. data/lib/punchblock/component/record.rb +41 -130
  30. data/lib/punchblock/component.rb +1 -0
  31. data/lib/punchblock/connection/asterisk.rb +31 -4
  32. data/lib/punchblock/connection/xmpp.rb +6 -14
  33. data/lib/punchblock/core_ext/blather/stanza.rb +1 -1
  34. data/lib/punchblock/event/active_speaker.rb +2 -10
  35. data/lib/punchblock/event/answered.rb +3 -3
  36. data/lib/punchblock/event/asterisk/ami/event.rb +15 -47
  37. data/lib/punchblock/event/complete.rb +26 -48
  38. data/lib/punchblock/event/dtmf.rb +3 -13
  39. data/lib/punchblock/event/end.rb +10 -11
  40. data/lib/punchblock/event/joined.rb +5 -25
  41. data/lib/punchblock/event/offer.rb +4 -25
  42. data/lib/punchblock/event/ringing.rb +3 -3
  43. data/lib/punchblock/event/unjoined.rb +5 -25
  44. data/lib/punchblock/event.rb +0 -10
  45. data/lib/punchblock/has_headers.rb +20 -26
  46. data/lib/punchblock/rayo_node.rb +46 -23
  47. data/lib/punchblock/ref.rb +39 -18
  48. data/lib/punchblock/translator/asterisk/agi_app.rb +15 -0
  49. data/lib/punchblock/translator/asterisk/agi_command.rb +3 -1
  50. data/lib/punchblock/translator/asterisk/ami_error_converter.rb +20 -0
  51. data/lib/punchblock/translator/asterisk/call.rb +60 -39
  52. data/lib/punchblock/translator/asterisk/channel.rb +41 -0
  53. data/lib/punchblock/translator/asterisk/component/asterisk/agi_command.rb +4 -1
  54. data/lib/punchblock/translator/asterisk/component/asterisk/ami_action.rb +4 -4
  55. data/lib/punchblock/translator/asterisk/component/composed_prompt.rb +62 -0
  56. data/lib/punchblock/translator/asterisk/component/input.rb +1 -0
  57. data/lib/punchblock/translator/asterisk/component/mrcp_native_prompt.rb +56 -0
  58. data/lib/punchblock/translator/asterisk/component/mrcp_prompt.rb +53 -0
  59. data/lib/punchblock/translator/asterisk/component/mrcp_recog_prompt.rb +99 -0
  60. data/lib/punchblock/translator/asterisk/component/output.rb +30 -22
  61. data/lib/punchblock/translator/asterisk/component/record.rb +8 -6
  62. data/lib/punchblock/translator/asterisk/component.rb +6 -5
  63. data/lib/punchblock/translator/asterisk/unimrcp_app.rb +26 -0
  64. data/lib/punchblock/translator/asterisk.rb +24 -28
  65. data/lib/punchblock/translator/dtmf_recognizer.rb +39 -20
  66. data/lib/punchblock/translator/freeswitch/call.rb +15 -14
  67. data/lib/punchblock/translator/freeswitch/component/abstract_output.rb +5 -4
  68. data/lib/punchblock/translator/freeswitch/component/flite_output.rb +1 -1
  69. data/lib/punchblock/translator/freeswitch/component/input.rb +5 -0
  70. data/lib/punchblock/translator/freeswitch/component/output.rb +2 -2
  71. data/lib/punchblock/translator/freeswitch/component/record.rb +19 -13
  72. data/lib/punchblock/translator/freeswitch/component/tts_output.rb +2 -2
  73. data/lib/punchblock/translator/freeswitch/component.rb +2 -5
  74. data/lib/punchblock/translator/freeswitch.rb +2 -2
  75. data/lib/punchblock/translator/input_component.rb +33 -13
  76. data/lib/punchblock/uri_list.rb +21 -0
  77. data/lib/punchblock/version.rb +1 -1
  78. data/lib/punchblock.rb +4 -3
  79. data/punchblock.gemspec +7 -3
  80. data/spec/punchblock/client/component_registry_spec.rb +1 -1
  81. data/spec/punchblock/client_spec.rb +10 -26
  82. data/spec/punchblock/command/accept_spec.rb +41 -7
  83. data/spec/punchblock/command/answer_spec.rb +51 -7
  84. data/spec/punchblock/command/dial_spec.rb +56 -14
  85. data/spec/punchblock/command/hangup_spec.rb +41 -7
  86. data/spec/punchblock/command/join_spec.rb +53 -11
  87. data/spec/punchblock/command/mute_spec.rb +19 -4
  88. data/spec/punchblock/command/redirect_spec.rb +40 -10
  89. data/spec/punchblock/command/reject_spec.rb +43 -11
  90. data/spec/punchblock/command/unjoin_spec.rb +40 -9
  91. data/spec/punchblock/command/unmute_spec.rb +19 -4
  92. data/spec/punchblock/command_node_spec.rb +0 -4
  93. data/spec/punchblock/component/asterisk/agi/command_spec.rb +16 -39
  94. data/spec/punchblock/component/asterisk/ami/action_spec.rb +50 -53
  95. data/spec/punchblock/component/component_node_spec.rb +3 -5
  96. data/spec/punchblock/component/input_spec.rb +194 -61
  97. data/spec/punchblock/component/output_spec.rb +194 -62
  98. data/spec/punchblock/component/prompt_spec.rb +132 -0
  99. data/spec/punchblock/component/record_spec.rb +70 -32
  100. data/spec/punchblock/connection/asterisk_spec.rb +17 -3
  101. data/spec/punchblock/connection/freeswitch_spec.rb +4 -4
  102. data/spec/punchblock/connection/xmpp_spec.rb +20 -38
  103. data/spec/punchblock/event/answered_spec.rb +12 -10
  104. data/spec/punchblock/event/asterisk/ami/event_spec.rb +27 -22
  105. data/spec/punchblock/event/complete_spec.rb +15 -19
  106. data/spec/punchblock/event/dtmf_spec.rb +5 -6
  107. data/spec/punchblock/event/end_spec.rb +20 -10
  108. data/spec/punchblock/event/joined_spec.rb +8 -7
  109. data/spec/punchblock/event/offer_spec.rb +41 -12
  110. data/spec/punchblock/event/ringing_spec.rb +12 -10
  111. data/spec/punchblock/event/started_speaking_spec.rb +5 -6
  112. data/spec/punchblock/event/stopped_speaking_spec.rb +5 -6
  113. data/spec/punchblock/event/unjoined_spec.rb +7 -7
  114. data/spec/punchblock/ref_spec.rb +86 -9
  115. data/spec/punchblock/translator/asterisk/call_spec.rb +317 -154
  116. data/spec/punchblock/translator/asterisk/component/asterisk/agi_command_spec.rb +28 -5
  117. data/spec/punchblock/translator/asterisk/component/asterisk/ami_action_spec.rb +15 -13
  118. data/spec/punchblock/translator/asterisk/component/composed_prompt_spec.rb +237 -0
  119. data/spec/punchblock/translator/asterisk/component/input_spec.rb +171 -14
  120. data/spec/punchblock/translator/asterisk/component/mrcp_native_prompt_spec.rb +652 -0
  121. data/spec/punchblock/translator/asterisk/component/mrcp_prompt_spec.rb +646 -0
  122. data/spec/punchblock/translator/asterisk/component/output_spec.rb +127 -77
  123. data/spec/punchblock/translator/asterisk/component/record_spec.rb +17 -8
  124. data/spec/punchblock/translator/asterisk/component/stop_by_redirect_spec.rb +2 -2
  125. data/spec/punchblock/translator/asterisk/component_spec.rb +3 -7
  126. data/spec/punchblock/translator/asterisk_spec.rb +20 -24
  127. data/spec/punchblock/translator/freeswitch/call_spec.rb +103 -99
  128. data/spec/punchblock/translator/freeswitch/component/flite_output_spec.rb +17 -8
  129. data/spec/punchblock/translator/freeswitch/component/input_spec.rb +26 -14
  130. data/spec/punchblock/translator/freeswitch/component/output_spec.rb +30 -52
  131. data/spec/punchblock/translator/freeswitch/component/record_spec.rb +23 -19
  132. data/spec/punchblock/translator/freeswitch/component/tts_output_spec.rb +18 -8
  133. data/spec/punchblock/translator/freeswitch/component_spec.rb +4 -8
  134. data/spec/punchblock/translator/freeswitch_spec.rb +11 -14
  135. data/spec/punchblock/uri_list_spec.rb +49 -0
  136. data/spec/punchblock_spec.rb +11 -1
  137. data/spec/spec_helper.rb +7 -11
  138. data/spec/support/mock_connection_with_event_handler.rb +1 -1
  139. metadata +104 -24
  140. data/lib/punchblock/header.rb +0 -9
  141. data/lib/punchblock/key_value_pair_node.rb +0 -51
  142. data/spec/punchblock/header_spec.rb +0 -11
@@ -1,34 +1,55 @@
1
1
  # encoding: utf-8
2
2
 
3
+ require 'ruby_jid'
4
+
3
5
  module Punchblock
4
6
  ##
5
- # An rayo Ref message. This provides the command ID in response to execution of a command.
7
+ # A rayo Ref message. This provides the command ID in response to execution of a command.
6
8
  #
7
9
  class Ref < RayoNode
8
10
  register :ref, :core
9
11
 
10
- def self.new(options = {})
11
- super().tap do |new_node|
12
- options.each_pair { |k,v| new_node.send :"#{k}=", v }
12
+ # @return [String] the command URI
13
+ attribute :uri
14
+ def uri=(other)
15
+ super URI(other)
16
+ end
17
+
18
+ def scheme
19
+ uri.scheme
20
+ end
21
+
22
+ def call_id
23
+ case scheme
24
+ when 'xmpp'
25
+ RubyJID.new(uri.opaque).node
26
+ when nil
27
+ uri.path
28
+ else
29
+ uri.opaque
13
30
  end
14
31
  end
15
32
 
16
- ##
17
- # @return [String] the command ID
18
- #
19
- def id
20
- read_attr :id
33
+ def domain
34
+ case scheme
35
+ when 'xmpp'
36
+ RubyJID.new(uri.opaque).domain
37
+ end
21
38
  end
22
39
 
23
- ##
24
- # @param [String] ref_id the command ID
25
- #
26
- def id=(ref_id)
27
- write_attr :id, ref_id
40
+ def component_id
41
+ case scheme
42
+ when 'xmpp'
43
+ RubyJID.new(uri.opaque).resource
44
+ else
45
+ call_id
46
+ end
28
47
  end
29
48
 
30
- def inspect_attributes # :nodoc:
31
- [:id] + super
49
+ def rayo_attributes
50
+ {}.tap do |atts|
51
+ atts[:uri] = uri if uri
52
+ end
32
53
  end
33
- end # Offer
34
- end # Punchblock
54
+ end
55
+ end
@@ -0,0 +1,15 @@
1
+ module Punchblock
2
+ module Translator
3
+ class Asterisk
4
+ class AGIApp
5
+ def initialize(app, *args)
6
+ @app, @args = app, args
7
+ end
8
+
9
+ def execute(call)
10
+ call.execute_agi_command "EXEC #{@app}", @args.join(',')
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,6 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  require 'active_support/core_ext/string/filters'
4
+ require 'punchblock/translator/asterisk/ami_error_converter'
4
5
 
5
6
  module Punchblock
6
7
  module Translator
@@ -14,8 +15,9 @@ module Punchblock
14
15
  @id, @channel, @command, @params = id, channel, command, params
15
16
  end
16
17
 
18
+ # @raises RubyAMI::Error, ChannelGoneError
17
19
  def execute(ami_client)
18
- ami_client.send_action 'AGI', 'Channel' => @channel, 'Command' => agi_command, 'CommandID' => id
20
+ AMIErrorConverter.convert { ami_client.send_action 'AGI', 'Channel' => @channel, 'Command' => agi_command, 'CommandID' => id }
19
21
  end
20
22
 
21
23
  def parse_result(event)
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+
3
+ module Punchblock
4
+ module Translator
5
+ class Asterisk
6
+ module AMIErrorConverter
7
+ def self.convert
8
+ yield
9
+ rescue RubyAMI::Error => e
10
+ case e.message
11
+ when 'No such channel', /Channel (\S+) does not exist./
12
+ raise ChannelGoneError, e.message
13
+ else
14
+ raise e
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,5 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
+ require 'punchblock/translator/asterisk/ami_error_converter'
4
+
3
5
  module Punchblock
4
6
  module Translator
5
7
  class Asterisk
@@ -11,6 +13,8 @@ module Punchblock
11
13
  extend ActorHasGuardedHandlers
12
14
  execute_guarded_handlers_on_receiver
13
15
 
16
+ InvalidCommandError = Class.new Punchblock::Error
17
+
14
18
  attr_reader :id, :channel, :translator, :agi_env, :direction
15
19
 
16
20
  HANGUP_CAUSE_TO_END_REASON = Hash.new { :error }
@@ -34,6 +38,7 @@ module Punchblock
34
38
  @progress_sent = false
35
39
  @block_commands = false
36
40
  @channel_variables = {}
41
+ @hangup_cause = nil
37
42
  end
38
43
 
39
44
  def register_component(component)
@@ -79,7 +84,7 @@ module Punchblock
79
84
  :params => params
80
85
  originate_action.request!
81
86
  translator.async.execute_global_command originate_action
82
- dial_command.response = Ref.new :id => id
87
+ dial_command.response = Ref.new uri: id
83
88
  end
84
89
 
85
90
  def outbound?
@@ -105,11 +110,11 @@ module Punchblock
105
110
  end
106
111
 
107
112
  def process_ami_event(ami_event)
108
- send_pb_event Event::Asterisk::AMI::Event.new(:name => ami_event.name, :attributes => ami_event.headers)
113
+ send_pb_event Event::Asterisk::AMI::Event.new(name: ami_event.name, headers: ami_event.headers)
109
114
 
110
115
  case ami_event.name
111
116
  when 'Hangup'
112
- handle_hangup_event HANGUP_CAUSE_TO_END_REASON[ami_event['Cause'].to_i]
117
+ handle_hangup_event ami_event['Cause'].to_i
113
118
  when 'AsyncAGI'
114
119
  if component = component_with_id(ami_event['CommandID'])
115
120
  component.handle_ami_event ami_event
@@ -135,23 +140,16 @@ module Punchblock
135
140
  if other_call = translator.call_for_channel(other_call_channel)
136
141
  event = case ami_event['Bridgestate']
137
142
  when 'Link'
138
- Event::Joined.new.tap do |e|
139
- e.call_id = other_call.id
140
- end
143
+ Event::Joined.new call_uri: other_call.id
141
144
  when 'Unlink'
142
- Event::Unjoined.new.tap do |e|
143
- e.call_id = other_call.id
144
- end
145
+ Event::Unjoined.new call_uri: other_call.id
145
146
  end
146
147
  send_pb_event event
147
148
  end
148
149
  when 'Unlink'
149
150
  other_call_channel = ([ami_event['Channel1'], ami_event['Channel2']] - [channel]).first
150
151
  if other_call = translator.call_for_channel(other_call_channel)
151
- event = Event::Unjoined.new.tap do |e|
152
- e.call_id = other_call.id
153
- end
154
- send_pb_event event
152
+ send_pb_event Event::Unjoined.new(call_uri: other_call.id)
155
153
  end
156
154
  when 'VarSet'
157
155
  @channel_variables[ami_event['Variable']] = ami_event['Value']
@@ -184,28 +182,28 @@ module Punchblock
184
182
  @answered = true
185
183
  command.response = true
186
184
  when Command::Hangup
187
- send_ami_action 'Hangup', 'Channel' => channel, 'Cause' => 16
185
+ send_hangup_command
186
+ @hangup_cause = :hangup_command
188
187
  command.response = true
189
188
  when Command::Join
190
- other_call = translator.call_with_id command.call_id
189
+ other_call = translator.call_with_id command.call_uri
191
190
  @pending_joins[other_call.channel] = command
192
191
  execute_agi_command 'EXEC Bridge', other_call.channel
193
192
  when Command::Unjoin
194
- other_call = translator.call_with_id command.call_id
193
+ other_call = translator.call_with_id command.call_uri
195
194
  redirect_back other_call
196
195
  command.response = true
197
196
  when Command::Reject
198
- rejection = case command.reason
197
+ case command.reason
199
198
  when :busy
200
- 'EXEC Busy'
199
+ execute_agi_command 'EXEC Busy'
201
200
  when :decline
202
- 'EXEC Busy'
201
+ send_hangup_command 21
203
202
  when :error
204
- 'EXEC Congestion'
203
+ execute_agi_command 'EXEC Congestion'
205
204
  else
206
- 'EXEC Congestion'
205
+ execute_agi_command 'EXEC Congestion'
207
206
  end
208
- execute_agi_command rejection
209
207
  command.response = true
210
208
  when Punchblock::Component::Asterisk::AGI::Command
211
209
  execute_component Component::Asterisk::AGICommand, command
@@ -213,22 +211,40 @@ module Punchblock
213
211
  execute_component Component::Output, command
214
212
  when Punchblock::Component::Input
215
213
  execute_component Component::Input, command
214
+ when Punchblock::Component::Prompt
215
+ component_class = case command.input.recognizer
216
+ when 'unimrcp'
217
+ case command.output.renderer
218
+ when 'unimrcp'
219
+ Component::MRCPPrompt
220
+ when 'asterisk'
221
+ Component::MRCPNativePrompt
222
+ else
223
+ raise InvalidCommandError, 'Invalid recognizer/renderer combination'
224
+ end
225
+ else
226
+ Component::ComposedPrompt
227
+ end
228
+ execute_component component_class, command
216
229
  when Punchblock::Component::Record
217
230
  execute_component Component::Record, command
218
231
  else
219
232
  command.response = ProtocolError.new.setup 'command-not-acceptable', "Did not understand command for call #{id}", id
220
233
  end
234
+ rescue InvalidCommandError => e
235
+ command.response = ProtocolError.new.setup :invalid_command, e.message, id
236
+ rescue ChannelGoneError
237
+ command.response = ProtocolError.new.setup :item_not_found, "Could not find a call with ID #{id}", id
221
238
  rescue RubyAMI::Error => e
222
- command.response = case e.message
223
- when 'No such channel'
224
- ProtocolError.new.setup :item_not_found, "Could not find a call with ID #{id}", id
225
- else
226
- ProtocolError.new.setup 'error', e.message, id
227
- end
239
+ command.response = ProtocolError.new.setup 'error', e.message, id
228
240
  rescue Celluloid::DeadActorError
229
241
  command.response = ProtocolError.new.setup :item_not_found, "Could not find a component with ID #{command.component_id} for call #{id}", id, command.component_id
230
242
  end
231
243
 
244
+ #
245
+ # @return [Hash] AGI result
246
+ #
247
+ # @raises RubyAMI::Error, ChannelGoneError
232
248
  def execute_agi_command(command, *params)
233
249
  agi = AGICommand.new Punchblock.new_uuid, channel, command, *params
234
250
  condition = Celluloid::Condition.new
@@ -239,7 +255,7 @@ module Punchblock
239
255
  event = condition.wait
240
256
  return unless event
241
257
  agi.parse_result event
242
- rescue RubyAMI::Error => e
258
+ rescue ChannelGoneError, RubyAMI::Error => e
243
259
  abort e
244
260
  end
245
261
 
@@ -263,14 +279,15 @@ module Punchblock
263
279
  send_ami_action 'Redirect', redirect_options
264
280
  end
265
281
 
266
- def handle_hangup_event(reason = :hangup)
282
+ def handle_hangup_event(code = 16)
283
+ reason = @hangup_cause || HANGUP_CAUSE_TO_END_REASON[code]
267
284
  @block_commands = true
268
285
  @components.dup.each_pair do |id, component|
269
286
  safe_from_dead_actors do
270
287
  component.call_ended if component.alive?
271
288
  end
272
289
  end
273
- send_end_event reason
290
+ send_end_event reason, code
274
291
  end
275
292
 
276
293
  def actor_died(actor, reason)
@@ -289,14 +306,18 @@ module Punchblock
289
306
  result['Value'] == '(null)' ? nil : result['Value']
290
307
  end
291
308
 
309
+ def send_hangup_command(cause_code = 16)
310
+ send_ami_action 'Hangup', 'Channel' => channel, 'Cause' => cause_code
311
+ end
312
+
292
313
  def send_ami_action(name, headers = {})
293
- @ami_client.send_action name, headers
314
+ AMIErrorConverter.convert { @ami_client.send_action name, headers }
294
315
  end
295
316
 
296
- def send_end_event(reason)
297
- send_pb_event Event::End.new(:reason => reason)
298
- translator.deregister_call current_actor
299
- after(5) { shutdown }
317
+ def send_end_event(reason, code = nil)
318
+ send_pb_event Event::End.new(reason: reason, platform_code: code)
319
+ translator.deregister_call id, channel
320
+ terminate
300
321
  end
301
322
 
302
323
  def execute_component(type, command, options = {})
@@ -319,7 +340,7 @@ module Punchblock
319
340
 
320
341
  def sip_headers
321
342
  agi_env.to_a.inject({}) do |accumulator, element|
322
- accumulator[('x_' + element[0].to_s).to_sym] = element[1] || ''
343
+ accumulator['X-' + element[0].to_s] = element[1] || ''
323
344
  accumulator
324
345
  end
325
346
  end
@@ -327,8 +348,8 @@ module Punchblock
327
348
  def variable_for_headers(headers)
328
349
  variables = { :punchblock_call_id => id }
329
350
  header_counter = 51
330
- headers.each do |header|
331
- variables["SIPADDHEADER#{header_counter}"] = "\"#{header.name}: #{header.value}\""
351
+ headers.each do |name, value|
352
+ variables["SIPADDHEADER#{header_counter}"] = "\"#{name}: #{value}\""
332
353
  header_counter += 1
333
354
  end
334
355
  variables.inject([]) do |a, (k, v)|
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+
3
+ module Punchblock
4
+ module Translator
5
+ class Asterisk
6
+ class Channel < SimpleDelegator
7
+ NORMALIZATION_REGEXP = /^(?<prefix>Bridge\/)*(?<name>[^<>]*)(?<suffix><.*>)*$/.freeze
8
+
9
+ def self.new(other)
10
+ other.is_a?(self) ? other : super
11
+ end
12
+
13
+ def name
14
+ matchdata[:name]
15
+ end
16
+
17
+ def prefix
18
+ matchdata[:prefix]
19
+ end
20
+
21
+ def suffix
22
+ matchdata[:suffix]
23
+ end
24
+
25
+ def bridged?
26
+ @bridged ||= (prefix || suffix)
27
+ end
28
+
29
+ def to_s
30
+ __getobj__
31
+ end
32
+
33
+ private
34
+
35
+ def matchdata
36
+ @matchdata ||= __getobj__.match(NORMALIZATION_REGEXP)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -7,7 +7,7 @@ module Punchblock
7
7
  module Asterisk
8
8
  class AGICommand < Component
9
9
  def setup
10
- @agi = Punchblock::Translator::Asterisk::AGICommand.new id, @call.channel, @component_node.name, *@component_node.params_array
10
+ @agi = Punchblock::Translator::Asterisk::AGICommand.new id, @call.channel, @component_node.name, *@component_node.params
11
11
  end
12
12
 
13
13
  def execute
@@ -16,6 +16,9 @@ module Punchblock
16
16
  rescue RubyAMI::Error
17
17
  set_node_response false
18
18
  terminate
19
+ rescue ChannelGoneError
20
+ set_node_response ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{call_id}", call_id)
21
+ terminate
19
22
  end
20
23
  exclusive :execute
21
24
 
@@ -29,14 +29,14 @@ module Punchblock
29
29
  end
30
30
 
31
31
  def error_reason(response)
32
- Punchblock::Event::Complete::Error.new :details => response.message
32
+ Punchblock::Event::Complete::Error.new details: response.message
33
33
  end
34
34
 
35
35
  def success_reason(response, final_event = nil)
36
36
  headers = response.headers
37
37
  headers.merge! final_event.headers if final_event
38
38
  headers.delete 'ActionID'
39
- Punchblock::Component::Asterisk::AMI::Action::Complete::Success.new :message => headers.delete('Message'), :attributes => headers
39
+ Punchblock::Component::Asterisk::AMI::Action::Complete::Success.new message: headers.delete('Message'), text_body: response.text_body, headers: headers
40
40
  end
41
41
 
42
42
  def send_events(response)
@@ -50,12 +50,12 @@ module Punchblock
50
50
  def pb_event_from_ami_event(ami_event)
51
51
  headers = ami_event.headers
52
52
  headers.delete 'ActionID'
53
- Event::Asterisk::AMI::Event.new :name => ami_event.name, :attributes => headers
53
+ Event::Asterisk::AMI::Event.new name: ami_event.name, headers: headers
54
54
  end
55
55
 
56
56
  def action_headers
57
57
  headers = {}
58
- @component_node.params_hash.each_pair do |key, value|
58
+ @component_node.params.each_pair do |key, value|
59
59
  headers[key.to_s.capitalize] = value
60
60
  end
61
61
  headers
@@ -0,0 +1,62 @@
1
+ # encoding: utf-8
2
+
3
+ module Punchblock
4
+ module Translator
5
+ class Asterisk
6
+ module Component
7
+ class ComposedPrompt < Component
8
+ include InputComponent
9
+ include StopByRedirect
10
+
11
+ def execute
12
+ validate
13
+ output_command.request!
14
+ setup_dtmf_recognizer
15
+
16
+ output_component = Output.new_link(output_command, @call)
17
+ call.register_component output_component
18
+ fut = output_component.future.execute
19
+
20
+ case output_command.response
21
+ when Ref
22
+ send_ref
23
+ else
24
+ set_node_response output_command.response
25
+ end
26
+
27
+ fut.value unless @component_node.barge_in # Block until output is complete
28
+
29
+ register_dtmf_event_handler
30
+ start_timers
31
+ end
32
+
33
+ def process_dtmf(digit)
34
+ call.async.redirect_back if @component_node.barge_in
35
+ super
36
+ end
37
+
38
+ def output_command
39
+ @output_command ||= @component_node.output
40
+ end
41
+
42
+ private
43
+
44
+ def input_node
45
+ @input_node ||= @component_node.input
46
+ end
47
+
48
+ def register_dtmf_event_handler
49
+ component = current_actor
50
+ @dtmf_handler_id = call.register_handler :ami, :name => 'DTMF', [:[], 'End'] => 'Yes' do |event|
51
+ component.process_dtmf event['Digit']
52
+ end
53
+ end
54
+
55
+ def unregister_dtmf_event_handler
56
+ call.async.unregister_handler :ami, @dtmf_handler_id if instance_variable_defined?(:@dtmf_handler_id)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -11,6 +11,7 @@ module Punchblock
11
11
  def execute
12
12
  @call.send_progress
13
13
  super
14
+ @dtmf_handler_id = register_dtmf_event_handler
14
15
  end
15
16
 
16
17
  private
@@ -0,0 +1,56 @@
1
+ # encoding: utf-8
2
+
3
+ module Punchblock
4
+ module Translator
5
+ class Asterisk
6
+ module Component
7
+ class MRCPNativePrompt < Component
8
+ include StopByRedirect
9
+ include MRCPRecogPrompt
10
+
11
+ private
12
+
13
+ def validate
14
+ raise OptionError, "The renderer #{renderer} is unsupported." unless renderer == 'asterisk'
15
+ raise OptionError, "The recognizer #{recognizer} is unsupported." unless recognizer == 'unimrcp'
16
+
17
+ raise OptionError, 'A document is required.' unless output_node.render_documents.count > 0
18
+ raise OptionError, 'Only one document is allowed.' if output_node.render_documents.count > 1
19
+ raise OptionError, 'Only inline documents are allowed.' if first_doc.url
20
+ raise OptionError, 'Only one audio file is allowed.' if first_doc.value.size > 1
21
+
22
+ raise OptionError, 'A grammar is required.' unless input_node.grammars.count > 0
23
+
24
+ super
25
+ end
26
+
27
+ def renderer
28
+ (output_node.renderer || :asterisk).to_s
29
+ end
30
+
31
+ def recognizer
32
+ (input_node.recognizer || :unimrcp).to_s
33
+ end
34
+
35
+ def execute_unimrcp_app
36
+ execute_app 'MRCPRecog', grammars
37
+ end
38
+
39
+ def first_doc
40
+ output_node.render_documents.first
41
+ end
42
+
43
+ def audio_filename
44
+ first_doc.value.first
45
+ end
46
+
47
+ def unimrcp_app_options
48
+ super do |opts|
49
+ opts[:f] = audio_filename
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,53 @@
1
+ # encoding: utf-8
2
+
3
+ module Punchblock
4
+ module Translator
5
+ class Asterisk
6
+ module Component
7
+ class MRCPPrompt < Component
8
+ include StopByRedirect
9
+ include MRCPRecogPrompt
10
+
11
+ private
12
+
13
+ def validate
14
+ raise OptionError, "The renderer #{renderer} is unsupported." unless renderer == 'unimrcp'
15
+ raise OptionError, "The recognizer #{recognizer} is unsupported." unless recognizer == 'unimrcp'
16
+ raise OptionError, 'An SSML document is required.' unless output_node.render_documents.count > 0
17
+ raise OptionError, 'A grammar is required.' unless input_node.grammars.count > 0
18
+
19
+ super
20
+ end
21
+
22
+ def renderer
23
+ (output_node.renderer || :unimrcp).to_s
24
+ end
25
+
26
+ def recognizer
27
+ (input_node.recognizer || :unimrcp).to_s
28
+ end
29
+
30
+ def execute_unimrcp_app
31
+ execute_app 'SynthAndRecog', render_docs, grammars
32
+ end
33
+
34
+ def render_docs
35
+ output_node.render_documents.map do |d|
36
+ if d.content_type
37
+ d.value.to_doc.to_s
38
+ else
39
+ d.url
40
+ end
41
+ end.join ','
42
+ end
43
+
44
+ def unimrcp_app_options
45
+ super do |opts|
46
+ opts[:vn] = output_node.voice if output_node.voice
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end