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.
- 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
|