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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +1 -2
- data/CHANGELOG.md +17 -0
- data/Gemfile +1 -0
- data/Guardfile +4 -0
- data/README.markdown +6 -0
- data/Rakefile +16 -0
- data/benchmarks/ami_event_name_comparison.rb +14 -0
- data/benchmarks/channel.rb +27 -0
- data/lib/punchblock/client.rb +2 -6
- data/lib/punchblock/command/accept.rb +3 -24
- data/lib/punchblock/command/answer.rb +3 -24
- data/lib/punchblock/command/dial.rb +24 -76
- data/lib/punchblock/command/hangup.rb +3 -19
- data/lib/punchblock/command/join.rb +21 -70
- data/lib/punchblock/command/mute.rb +3 -3
- data/lib/punchblock/command/redirect.rb +6 -39
- data/lib/punchblock/command/reject.rb +14 -54
- data/lib/punchblock/command/unjoin.rb +8 -40
- data/lib/punchblock/command/unmute.rb +3 -3
- data/lib/punchblock/command_node.rb +0 -17
- data/lib/punchblock/component/asterisk/agi/command.rb +20 -127
- data/lib/punchblock/component/asterisk/ami/action.rb +30 -117
- data/lib/punchblock/component/component_node.rb +1 -1
- data/lib/punchblock/component/input.rb +89 -268
- data/lib/punchblock/component/output.rb +106 -154
- data/lib/punchblock/component/prompt.rb +51 -0
- data/lib/punchblock/component/record.rb +41 -130
- data/lib/punchblock/component.rb +1 -0
- data/lib/punchblock/connection/asterisk.rb +31 -4
- data/lib/punchblock/connection/xmpp.rb +6 -14
- data/lib/punchblock/core_ext/blather/stanza.rb +1 -1
- data/lib/punchblock/event/active_speaker.rb +2 -10
- data/lib/punchblock/event/answered.rb +3 -3
- data/lib/punchblock/event/asterisk/ami/event.rb +15 -47
- data/lib/punchblock/event/complete.rb +26 -48
- data/lib/punchblock/event/dtmf.rb +3 -13
- data/lib/punchblock/event/end.rb +10 -11
- data/lib/punchblock/event/joined.rb +5 -25
- data/lib/punchblock/event/offer.rb +4 -25
- data/lib/punchblock/event/ringing.rb +3 -3
- data/lib/punchblock/event/unjoined.rb +5 -25
- data/lib/punchblock/event.rb +0 -10
- data/lib/punchblock/has_headers.rb +20 -26
- data/lib/punchblock/rayo_node.rb +46 -23
- data/lib/punchblock/ref.rb +39 -18
- data/lib/punchblock/translator/asterisk/agi_app.rb +15 -0
- data/lib/punchblock/translator/asterisk/agi_command.rb +3 -1
- data/lib/punchblock/translator/asterisk/ami_error_converter.rb +20 -0
- data/lib/punchblock/translator/asterisk/call.rb +60 -39
- data/lib/punchblock/translator/asterisk/channel.rb +41 -0
- data/lib/punchblock/translator/asterisk/component/asterisk/agi_command.rb +4 -1
- data/lib/punchblock/translator/asterisk/component/asterisk/ami_action.rb +4 -4
- data/lib/punchblock/translator/asterisk/component/composed_prompt.rb +62 -0
- data/lib/punchblock/translator/asterisk/component/input.rb +1 -0
- data/lib/punchblock/translator/asterisk/component/mrcp_native_prompt.rb +56 -0
- data/lib/punchblock/translator/asterisk/component/mrcp_prompt.rb +53 -0
- data/lib/punchblock/translator/asterisk/component/mrcp_recog_prompt.rb +99 -0
- data/lib/punchblock/translator/asterisk/component/output.rb +30 -22
- data/lib/punchblock/translator/asterisk/component/record.rb +8 -6
- data/lib/punchblock/translator/asterisk/component.rb +6 -5
- data/lib/punchblock/translator/asterisk/unimrcp_app.rb +26 -0
- data/lib/punchblock/translator/asterisk.rb +24 -28
- data/lib/punchblock/translator/dtmf_recognizer.rb +39 -20
- data/lib/punchblock/translator/freeswitch/call.rb +15 -14
- data/lib/punchblock/translator/freeswitch/component/abstract_output.rb +5 -4
- data/lib/punchblock/translator/freeswitch/component/flite_output.rb +1 -1
- data/lib/punchblock/translator/freeswitch/component/input.rb +5 -0
- data/lib/punchblock/translator/freeswitch/component/output.rb +2 -2
- data/lib/punchblock/translator/freeswitch/component/record.rb +19 -13
- data/lib/punchblock/translator/freeswitch/component/tts_output.rb +2 -2
- data/lib/punchblock/translator/freeswitch/component.rb +2 -5
- data/lib/punchblock/translator/freeswitch.rb +2 -2
- data/lib/punchblock/translator/input_component.rb +33 -13
- data/lib/punchblock/uri_list.rb +21 -0
- data/lib/punchblock/version.rb +1 -1
- data/lib/punchblock.rb +4 -3
- data/punchblock.gemspec +7 -3
- data/spec/punchblock/client/component_registry_spec.rb +1 -1
- data/spec/punchblock/client_spec.rb +10 -26
- data/spec/punchblock/command/accept_spec.rb +41 -7
- data/spec/punchblock/command/answer_spec.rb +51 -7
- data/spec/punchblock/command/dial_spec.rb +56 -14
- data/spec/punchblock/command/hangup_spec.rb +41 -7
- data/spec/punchblock/command/join_spec.rb +53 -11
- data/spec/punchblock/command/mute_spec.rb +19 -4
- data/spec/punchblock/command/redirect_spec.rb +40 -10
- data/spec/punchblock/command/reject_spec.rb +43 -11
- data/spec/punchblock/command/unjoin_spec.rb +40 -9
- data/spec/punchblock/command/unmute_spec.rb +19 -4
- data/spec/punchblock/command_node_spec.rb +0 -4
- data/spec/punchblock/component/asterisk/agi/command_spec.rb +16 -39
- data/spec/punchblock/component/asterisk/ami/action_spec.rb +50 -53
- data/spec/punchblock/component/component_node_spec.rb +3 -5
- data/spec/punchblock/component/input_spec.rb +194 -61
- data/spec/punchblock/component/output_spec.rb +194 -62
- data/spec/punchblock/component/prompt_spec.rb +132 -0
- data/spec/punchblock/component/record_spec.rb +70 -32
- data/spec/punchblock/connection/asterisk_spec.rb +17 -3
- data/spec/punchblock/connection/freeswitch_spec.rb +4 -4
- data/spec/punchblock/connection/xmpp_spec.rb +20 -38
- data/spec/punchblock/event/answered_spec.rb +12 -10
- data/spec/punchblock/event/asterisk/ami/event_spec.rb +27 -22
- data/spec/punchblock/event/complete_spec.rb +15 -19
- data/spec/punchblock/event/dtmf_spec.rb +5 -6
- data/spec/punchblock/event/end_spec.rb +20 -10
- data/spec/punchblock/event/joined_spec.rb +8 -7
- data/spec/punchblock/event/offer_spec.rb +41 -12
- data/spec/punchblock/event/ringing_spec.rb +12 -10
- data/spec/punchblock/event/started_speaking_spec.rb +5 -6
- data/spec/punchblock/event/stopped_speaking_spec.rb +5 -6
- data/spec/punchblock/event/unjoined_spec.rb +7 -7
- data/spec/punchblock/ref_spec.rb +86 -9
- data/spec/punchblock/translator/asterisk/call_spec.rb +317 -154
- data/spec/punchblock/translator/asterisk/component/asterisk/agi_command_spec.rb +28 -5
- data/spec/punchblock/translator/asterisk/component/asterisk/ami_action_spec.rb +15 -13
- data/spec/punchblock/translator/asterisk/component/composed_prompt_spec.rb +237 -0
- data/spec/punchblock/translator/asterisk/component/input_spec.rb +171 -14
- data/spec/punchblock/translator/asterisk/component/mrcp_native_prompt_spec.rb +652 -0
- data/spec/punchblock/translator/asterisk/component/mrcp_prompt_spec.rb +646 -0
- data/spec/punchblock/translator/asterisk/component/output_spec.rb +127 -77
- data/spec/punchblock/translator/asterisk/component/record_spec.rb +17 -8
- data/spec/punchblock/translator/asterisk/component/stop_by_redirect_spec.rb +2 -2
- data/spec/punchblock/translator/asterisk/component_spec.rb +3 -7
- data/spec/punchblock/translator/asterisk_spec.rb +20 -24
- data/spec/punchblock/translator/freeswitch/call_spec.rb +103 -99
- data/spec/punchblock/translator/freeswitch/component/flite_output_spec.rb +17 -8
- data/spec/punchblock/translator/freeswitch/component/input_spec.rb +26 -14
- data/spec/punchblock/translator/freeswitch/component/output_spec.rb +30 -52
- data/spec/punchblock/translator/freeswitch/component/record_spec.rb +23 -19
- data/spec/punchblock/translator/freeswitch/component/tts_output_spec.rb +18 -8
- data/spec/punchblock/translator/freeswitch/component_spec.rb +4 -8
- data/spec/punchblock/translator/freeswitch_spec.rb +11 -14
- data/spec/punchblock/uri_list_spec.rb +49 -0
- data/spec/punchblock_spec.rb +11 -1
- data/spec/spec_helper.rb +7 -11
- data/spec/support/mock_connection_with_event_handler.rb +1 -1
- metadata +104 -24
- data/lib/punchblock/header.rb +0 -9
- data/lib/punchblock/key_value_pair_node.rb +0 -51
- data/spec/punchblock/header_spec.rb +0 -11
data/lib/punchblock/ref.rb
CHANGED
@@ -1,34 +1,55 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
+
require 'ruby_jid'
|
4
|
+
|
3
5
|
module Punchblock
|
4
6
|
##
|
5
|
-
#
|
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
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
33
|
+
def domain
|
34
|
+
case scheme
|
35
|
+
when 'xmpp'
|
36
|
+
RubyJID.new(uri.opaque).domain
|
37
|
+
end
|
21
38
|
end
|
22
39
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
31
|
-
|
49
|
+
def rayo_attributes
|
50
|
+
{}.tap do |atts|
|
51
|
+
atts[:uri] = uri if uri
|
52
|
+
end
|
32
53
|
end
|
33
|
-
end
|
34
|
-
end
|
54
|
+
end
|
55
|
+
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 :
|
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(:
|
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
|
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
|
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
|
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
|
-
|
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
|
-
|
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.
|
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.
|
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
|
-
|
197
|
+
case command.reason
|
199
198
|
when :busy
|
200
|
-
'EXEC Busy'
|
199
|
+
execute_agi_command 'EXEC Busy'
|
201
200
|
when :decline
|
202
|
-
|
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 =
|
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(
|
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
|
298
|
-
translator.deregister_call
|
299
|
-
|
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[
|
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 |
|
331
|
-
variables["SIPADDHEADER#{header_counter}"] = "\"#{
|
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.
|
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 :
|
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 :
|
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 :
|
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.
|
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
|
@@ -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
|