punchblock 1.9.4 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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