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
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'punchblock/translator/asterisk/unimrcp_app'
|
|
4
|
+
|
|
5
|
+
module Punchblock
|
|
6
|
+
module Translator
|
|
7
|
+
class Asterisk
|
|
8
|
+
module Component
|
|
9
|
+
module MRCPRecogPrompt
|
|
10
|
+
UniMRCPError = Class.new Punchblock::Error
|
|
11
|
+
|
|
12
|
+
def execute
|
|
13
|
+
setup_defaults
|
|
14
|
+
validate
|
|
15
|
+
send_ref
|
|
16
|
+
execute_unimrcp_app
|
|
17
|
+
complete
|
|
18
|
+
rescue ChannelGoneError
|
|
19
|
+
call_ended
|
|
20
|
+
rescue UniMRCPError
|
|
21
|
+
complete_with_error 'Terminated due to UniMRCP error'
|
|
22
|
+
rescue RubyAMI::Error => e
|
|
23
|
+
complete_with_error "Terminated due to AMI error '#{e.message}'"
|
|
24
|
+
rescue OptionError => e
|
|
25
|
+
with_error 'option error', e.message
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def validate
|
|
31
|
+
[:interrupt_on, :start_offset, :start_paused, :repeat_interval, :repeat_times, :max_time].each do |opt|
|
|
32
|
+
raise OptionError, "A #{opt} value is unsupported on Asterisk." if output_node.send opt
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
raise OptionError, "An initial-timeout value must be -1 or a positive integer." if @initial_timeout < -1
|
|
36
|
+
raise OptionError, "An inter-digit-timeout value must be -1 or a positive integer." if @inter_digit_timeout < -1
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def execute_app(app, *args)
|
|
40
|
+
UniMRCPApp.new(app, *args, unimrcp_app_options).execute @call
|
|
41
|
+
raise UniMRCPError if @call.channel_var('RECOG_STATUS') == 'ERROR'
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def unimrcp_app_options
|
|
45
|
+
{uer: 1, b: (@component_node.barge_in == false ? 0 : 1)}.tap do |opts|
|
|
46
|
+
opts[:nit] = @initial_timeout if @initial_timeout > -1
|
|
47
|
+
opts[:dit] = @inter_digit_timeout if @inter_digit_timeout > -1
|
|
48
|
+
opts[:dttc] = input_node.terminator if input_node.terminator
|
|
49
|
+
yield opts
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def setup_defaults
|
|
54
|
+
@initial_timeout = input_node.initial_timeout || -1
|
|
55
|
+
@inter_digit_timeout = input_node.inter_digit_timeout || -1
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def grammars
|
|
59
|
+
input_node.grammars.map do |d|
|
|
60
|
+
if d.content_type
|
|
61
|
+
d.value.to_doc.to_s
|
|
62
|
+
else
|
|
63
|
+
d.url
|
|
64
|
+
end
|
|
65
|
+
end.join ','
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def first_doc
|
|
69
|
+
output_node.render_documents.first
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def audio_filename
|
|
73
|
+
first_doc.value.first
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def output_node
|
|
77
|
+
@component_node.output
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def input_node
|
|
81
|
+
@component_node.input
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def complete
|
|
85
|
+
send_complete_event case @call.channel_var('RECOG_COMPLETION_CAUSE')
|
|
86
|
+
when '000'
|
|
87
|
+
nlsml = RubySpeech.parse URI.decode(@call.channel_var('RECOG_RESULT'))
|
|
88
|
+
Punchblock::Component::Input::Complete::Match.new nlsml: nlsml
|
|
89
|
+
when '001'
|
|
90
|
+
Punchblock::Component::Input::Complete::NoMatch.new
|
|
91
|
+
when '002'
|
|
92
|
+
Punchblock::Component::Input::Complete::NoInput.new
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
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/unimrcp_app'
|
|
4
5
|
|
|
5
6
|
module Punchblock
|
|
6
7
|
module Translator
|
|
@@ -10,14 +11,17 @@ module Punchblock
|
|
|
10
11
|
include StopByRedirect
|
|
11
12
|
|
|
12
13
|
UnrenderableDocError = Class.new OptionError
|
|
14
|
+
UniMRCPError = Class.new Punchblock::Error
|
|
15
|
+
PlaybackError = Class.new Punchblock::Error
|
|
13
16
|
|
|
14
17
|
def setup
|
|
15
18
|
@media_engine = @call.translator.media_engine
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
def execute
|
|
19
|
-
raise OptionError, 'An SSML document is required.' unless @component_node.
|
|
20
|
-
raise OptionError, '
|
|
22
|
+
raise OptionError, 'An SSML document is required.' unless @component_node.render_documents.first.value
|
|
23
|
+
raise OptionError, 'Only a single document is supported.' unless @component_node.render_documents.size == 1
|
|
24
|
+
raise OptionError, 'An interrupt-on value of speech is unsupported.' if @component_node.interrupt_on == :voice
|
|
21
25
|
|
|
22
26
|
[:start_offset, :start_paused, :repeat_interval, :repeat_times, :max_time].each do |opt|
|
|
23
27
|
raise OptionError, "A #{opt} value is unsupported on Asterisk." if @component_node.send opt
|
|
@@ -44,7 +48,7 @@ module Punchblock
|
|
|
44
48
|
if interrupt
|
|
45
49
|
output_component = current_actor
|
|
46
50
|
call.register_handler :ami, :name => 'DTMF', [:[], 'End'] => 'Yes' do |event|
|
|
47
|
-
output_component.stop_by_redirect
|
|
51
|
+
output_component.stop_by_redirect finish_reason
|
|
48
52
|
end
|
|
49
53
|
end
|
|
50
54
|
|
|
@@ -52,10 +56,12 @@ module Punchblock
|
|
|
52
56
|
|
|
53
57
|
opts = early ? "#{path},noanswer" : path
|
|
54
58
|
@call.execute_agi_command 'EXEC Playback', opts
|
|
59
|
+
raise PlaybackError if @call.channel_var('PLAYBACKSTATUS') == 'FAILED'
|
|
55
60
|
when :unimrcp
|
|
56
61
|
@call.send_progress if early
|
|
57
62
|
send_ref
|
|
58
|
-
|
|
63
|
+
UniMRCPApp.new('MRCPSynth', render_doc, mrcpsynth_options).execute @call
|
|
64
|
+
raise UniMRCPError if @call.channel_var('SYNTHSTATUS') == 'ERROR'
|
|
59
65
|
when :swift
|
|
60
66
|
@call.send_progress if early
|
|
61
67
|
send_ref
|
|
@@ -63,7 +69,13 @@ module Punchblock
|
|
|
63
69
|
else
|
|
64
70
|
raise OptionError, "The renderer #{rendering_engine} is unsupported."
|
|
65
71
|
end
|
|
66
|
-
|
|
72
|
+
send_finish
|
|
73
|
+
rescue ChannelGoneError
|
|
74
|
+
call_ended
|
|
75
|
+
rescue PlaybackError
|
|
76
|
+
complete_with_error 'Terminated due to playback error'
|
|
77
|
+
rescue UniMRCPError
|
|
78
|
+
complete_with_error 'Terminated due to UniMRCP error'
|
|
67
79
|
rescue RubyAMI::Error => e
|
|
68
80
|
complete_with_error "Terminated due to AMI error '#{e.message}'"
|
|
69
81
|
rescue UnrenderableDocError => e
|
|
@@ -75,10 +87,10 @@ module Punchblock
|
|
|
75
87
|
private
|
|
76
88
|
|
|
77
89
|
def filenames
|
|
78
|
-
@filenames ||=
|
|
90
|
+
@filenames ||= render_doc.children.map do |node|
|
|
79
91
|
case node
|
|
80
92
|
when RubySpeech::SSML::Audio
|
|
81
|
-
node.src
|
|
93
|
+
node.src.sub('file://', '')
|
|
82
94
|
when String
|
|
83
95
|
raise if node.include?(' ')
|
|
84
96
|
node
|
|
@@ -90,34 +102,30 @@ module Punchblock
|
|
|
90
102
|
raise UnrenderableDocError, 'The provided document could not be rendered. See http://adhearsion.com/docs/common_problems#unrenderable-document-error for details.'
|
|
91
103
|
end
|
|
92
104
|
|
|
93
|
-
def
|
|
94
|
-
@component_node.
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def escape_commas(text)
|
|
98
|
-
text.gsub(',', '\\,')
|
|
105
|
+
def render_doc
|
|
106
|
+
@component_node.render_documents.first.value
|
|
99
107
|
end
|
|
100
108
|
|
|
101
109
|
def mrcpsynth_options
|
|
102
|
-
|
|
103
|
-
opts
|
|
104
|
-
opts
|
|
105
|
-
end
|
|
110
|
+
{}.tap do |opts|
|
|
111
|
+
opts[:i] = 'any' if [:any, :dtmf].include? @component_node.interrupt_on
|
|
112
|
+
opts[:v] = @component_node.voice if @component_node.voice
|
|
113
|
+
end
|
|
106
114
|
end
|
|
107
115
|
|
|
108
116
|
def swift_doc
|
|
109
|
-
doc =
|
|
117
|
+
doc = render_doc.to_s.squish.gsub(/["\\]/) { |m| "\\#{m}" }
|
|
110
118
|
doc << "|1|1" if [:any, :dtmf].include? @component_node.interrupt_on
|
|
111
119
|
doc.insert 0, "#{@component_node.voice}^" if @component_node.voice
|
|
112
120
|
doc
|
|
113
121
|
end
|
|
114
122
|
|
|
115
|
-
def
|
|
116
|
-
send_complete_event
|
|
123
|
+
def send_finish
|
|
124
|
+
send_complete_event finish_reason
|
|
117
125
|
end
|
|
118
126
|
|
|
119
|
-
def
|
|
120
|
-
Punchblock::Component::Output::Complete::
|
|
127
|
+
def finish_reason
|
|
128
|
+
Punchblock::Component::Output::Complete::Finish.new
|
|
121
129
|
end
|
|
122
130
|
end
|
|
123
131
|
end
|
|
@@ -27,8 +27,6 @@ module Punchblock
|
|
|
27
27
|
component.finished
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
send_ref
|
|
31
|
-
|
|
32
30
|
if @component_node.start_beep
|
|
33
31
|
@call.execute_agi_command 'STREAM FILE', 'beep', '""'
|
|
34
32
|
end
|
|
@@ -39,8 +37,12 @@ module Punchblock
|
|
|
39
37
|
ami_client.send_action 'StopMonitor', 'Channel' => call.channel
|
|
40
38
|
end
|
|
41
39
|
end
|
|
40
|
+
|
|
41
|
+
send_ref
|
|
42
|
+
rescue ChannelGoneError
|
|
43
|
+
set_node_response ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{call_id}", call_id)
|
|
42
44
|
rescue RubyAMI::Error => e
|
|
43
|
-
|
|
45
|
+
with_error :platform_error, "Terminated due to AMI error '#{e.message}'"
|
|
44
46
|
rescue OptionError => e
|
|
45
47
|
with_error 'option error', e.message
|
|
46
48
|
end
|
|
@@ -63,7 +65,7 @@ module Punchblock
|
|
|
63
65
|
end
|
|
64
66
|
|
|
65
67
|
def finished
|
|
66
|
-
send_complete_event(@complete_reason ||
|
|
68
|
+
send_complete_event(@complete_reason || max_duration_reason)
|
|
67
69
|
end
|
|
68
70
|
|
|
69
71
|
private
|
|
@@ -80,8 +82,8 @@ module Punchblock
|
|
|
80
82
|
Punchblock::Event::Complete::Stop.new
|
|
81
83
|
end
|
|
82
84
|
|
|
83
|
-
def
|
|
84
|
-
Punchblock::Component::Record::Complete::
|
|
85
|
+
def max_duration_reason
|
|
86
|
+
Punchblock::Component::Record::Complete::MaxDuration.new
|
|
85
87
|
end
|
|
86
88
|
|
|
87
89
|
def send_complete_event(reason)
|
|
@@ -7,8 +7,12 @@ module Punchblock
|
|
|
7
7
|
extend ActiveSupport::Autoload
|
|
8
8
|
|
|
9
9
|
autoload :Asterisk
|
|
10
|
+
autoload :ComposedPrompt
|
|
10
11
|
autoload :Input
|
|
11
12
|
autoload :Output
|
|
13
|
+
autoload :MRCPPrompt
|
|
14
|
+
autoload :MRCPNativePrompt
|
|
15
|
+
autoload :MRCPRecogPrompt
|
|
12
16
|
autoload :Record
|
|
13
17
|
autoload :StopByRedirect
|
|
14
18
|
|
|
@@ -36,10 +40,7 @@ module Punchblock
|
|
|
36
40
|
def send_complete_event(reason, recording = nil, should_terminate = true)
|
|
37
41
|
return if @complete
|
|
38
42
|
@complete = true
|
|
39
|
-
event = Punchblock::Event::Complete.new
|
|
40
|
-
c.reason = reason
|
|
41
|
-
c << recording if recording
|
|
42
|
-
end
|
|
43
|
+
event = Punchblock::Event::Complete.new reason: reason, recording: recording
|
|
43
44
|
send_event event
|
|
44
45
|
terminate if should_terminate
|
|
45
46
|
end
|
|
@@ -73,7 +74,7 @@ module Punchblock
|
|
|
73
74
|
end
|
|
74
75
|
|
|
75
76
|
def send_ref
|
|
76
|
-
set_node_response Ref.new :
|
|
77
|
+
set_node_response Ref.new uri: id
|
|
77
78
|
end
|
|
78
79
|
|
|
79
80
|
def with_error(name, text)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'punchblock/translator/asterisk/agi_app'
|
|
2
|
+
require 'active_support/core_ext/string/filters'
|
|
3
|
+
|
|
4
|
+
module Punchblock
|
|
5
|
+
module Translator
|
|
6
|
+
class Asterisk
|
|
7
|
+
class UniMRCPApp
|
|
8
|
+
def initialize(app, *args, options)
|
|
9
|
+
args.map! { |o| "\"#{o.to_s.squish.gsub('"', '\"')}\"" }
|
|
10
|
+
args << prepare_options(options)
|
|
11
|
+
@agi_app = AGIApp.new(app, *args)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def execute(call)
|
|
15
|
+
@agi_app.execute call
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def prepare_options(options)
|
|
21
|
+
options.map { |o| o.join '=' }.join '&'
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -8,10 +8,14 @@ module Punchblock
|
|
|
8
8
|
class Asterisk
|
|
9
9
|
include Celluloid
|
|
10
10
|
|
|
11
|
+
# Indicates that a command was executed against a channel which no longer exists
|
|
12
|
+
ChannelGoneError = Class.new Punchblock::Error
|
|
13
|
+
|
|
11
14
|
extend ActiveSupport::Autoload
|
|
12
15
|
|
|
13
16
|
autoload :AGICommand
|
|
14
17
|
autoload :Call
|
|
18
|
+
autoload :Channel
|
|
15
19
|
autoload :Component
|
|
16
20
|
|
|
17
21
|
attr_reader :ami_client, :connection, :media_engine, :calls
|
|
@@ -20,15 +24,13 @@ module Punchblock
|
|
|
20
24
|
REDIRECT_EXTENSION = '1'
|
|
21
25
|
REDIRECT_PRIORITY = '1'
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
EVENTS_ALLOWED_BRIDGED = %w{agiexec asyncagi}
|
|
27
|
+
EVENTS_ALLOWED_BRIDGED = %w{AGIExec AsyncAGI}
|
|
25
28
|
|
|
26
29
|
trap_exit :actor_died
|
|
27
30
|
|
|
28
31
|
def initialize(ami_client, connection, media_engine = nil)
|
|
29
32
|
@ami_client, @connection, @media_engine = ami_client, connection, media_engine
|
|
30
33
|
@calls, @components, @channel_to_call_id = {}, {}, {}
|
|
31
|
-
@fully_booted_count = 0
|
|
32
34
|
end
|
|
33
35
|
|
|
34
36
|
def register_call(call)
|
|
@@ -36,9 +38,9 @@ module Punchblock
|
|
|
36
38
|
@calls[call.id] ||= call
|
|
37
39
|
end
|
|
38
40
|
|
|
39
|
-
def deregister_call(
|
|
40
|
-
@channel_to_call_id.delete
|
|
41
|
-
@calls.delete
|
|
41
|
+
def deregister_call(id, channel)
|
|
42
|
+
@channel_to_call_id.delete channel
|
|
43
|
+
@calls.delete id
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
def call_with_id(call_id)
|
|
@@ -46,7 +48,7 @@ module Punchblock
|
|
|
46
48
|
end
|
|
47
49
|
|
|
48
50
|
def call_for_channel(channel)
|
|
49
|
-
call_with_id @channel_to_call_id[
|
|
51
|
+
call_with_id @channel_to_call_id[Channel.new(channel).name]
|
|
50
52
|
end
|
|
51
53
|
|
|
52
54
|
def register_component(component)
|
|
@@ -65,7 +67,7 @@ module Punchblock
|
|
|
65
67
|
def handle_ami_event(event)
|
|
66
68
|
return unless event.is_a? RubyAMI::Event
|
|
67
69
|
|
|
68
|
-
if event.name
|
|
70
|
+
if event.name == 'FullyBooted'
|
|
69
71
|
handle_pb_event Connection::Connected.new
|
|
70
72
|
run_at_fully_booted
|
|
71
73
|
return
|
|
@@ -76,7 +78,7 @@ module Punchblock
|
|
|
76
78
|
ami_dispatch_to_or_create_call event
|
|
77
79
|
|
|
78
80
|
unless ami_event_known_call?(event)
|
|
79
|
-
handle_pb_event Event::Asterisk::AMI::Event.new(:
|
|
81
|
+
handle_pb_event Event::Asterisk::AMI::Event.new(name: event.name, headers: event.headers)
|
|
80
82
|
end
|
|
81
83
|
end
|
|
82
84
|
exclusive :handle_ami_event
|
|
@@ -171,24 +173,24 @@ module Punchblock
|
|
|
171
173
|
end
|
|
172
174
|
|
|
173
175
|
def ami_dispatch_to_or_create_call(event)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
176
|
+
calls_for_event = channels_for_ami_event(event).inject({}) do |h, channel|
|
|
177
|
+
call = call_for_channel channel
|
|
178
|
+
h[channel] = call if call
|
|
179
|
+
h
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
if !calls_for_event.empty?
|
|
183
|
+
calls_for_event.each_pair do |channel, call|
|
|
184
|
+
next if channel.bridged? && !EVENTS_ALLOWED_BRIDGED.include?(event.name)
|
|
185
|
+
call.async.process_ami_event event
|
|
184
186
|
end
|
|
185
|
-
elsif event.name
|
|
187
|
+
elsif event.name == "AsyncAGI" && event['SubEvent'] == "Start"
|
|
186
188
|
handle_async_agi_start_event event
|
|
187
189
|
end
|
|
188
190
|
end
|
|
189
191
|
|
|
190
192
|
def channels_for_ami_event(event)
|
|
191
|
-
[event['Channel'], event['Channel1'], event['Channel2']].compact
|
|
193
|
+
[event['Channel'], event['Channel1'], event['Channel2']].compact.map { |channel| Channel.new(channel) }
|
|
192
194
|
end
|
|
193
195
|
|
|
194
196
|
def ami_event_known_call?(event)
|
|
@@ -197,18 +199,12 @@ module Punchblock
|
|
|
197
199
|
(event['Channel2'] && call_for_channel(event['Channel2']))
|
|
198
200
|
end
|
|
199
201
|
|
|
200
|
-
def channel_is_bridged?(channel)
|
|
201
|
-
matches = channel.match CHANNEL_NORMALIZATION_REGEXP
|
|
202
|
-
matches[:prefix] || matches[:suffix]
|
|
203
|
-
end
|
|
204
|
-
|
|
205
202
|
def handle_async_agi_start_event(event)
|
|
206
203
|
env = RubyAMI::AsyncAGIEnvironmentParser.new(event['Env']).to_hash
|
|
207
204
|
|
|
208
205
|
return if env[:agi_extension] == 'h' || env[:agi_type] == 'Kill'
|
|
209
206
|
|
|
210
|
-
call = Call.
|
|
211
|
-
link call
|
|
207
|
+
call = Call.new_link event['Channel'], current_actor, ami_client, connection, env
|
|
212
208
|
register_call call
|
|
213
209
|
call.async.send_offer
|
|
214
210
|
end
|
|
@@ -3,42 +3,45 @@
|
|
|
3
3
|
module Punchblock
|
|
4
4
|
module Translator
|
|
5
5
|
class DTMFRecognizer
|
|
6
|
-
def initialize(responder, grammar, initial_timeout = nil, inter_digit_timeout = nil)
|
|
6
|
+
def initialize(responder, grammar, initial_timeout = nil, inter_digit_timeout = nil, terminator = nil)
|
|
7
7
|
@responder = responder
|
|
8
|
-
self.grammar = grammar
|
|
9
8
|
self.initial_timeout = initial_timeout || -1
|
|
10
9
|
self.inter_digit_timeout = inter_digit_timeout || -1
|
|
10
|
+
@terminator = terminator
|
|
11
|
+
@finished = false
|
|
11
12
|
|
|
13
|
+
@matcher = RubySpeech::GRXML::Matcher.new RubySpeech::GRXML.import(grammar.to_s)
|
|
12
14
|
@buffer = ""
|
|
13
|
-
|
|
14
|
-
begin_initial_timer @initial_timeout/1000 unless @initial_timeout == -1
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def <<(digit)
|
|
18
|
-
@buffer << digit
|
|
19
18
|
cancel_initial_timer
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@responder.match match.mode, match.confidence, match.utterance, match.interpretation
|
|
19
|
+
@buffer << digit unless terminating?(digit)
|
|
20
|
+
case (match = get_match)
|
|
23
21
|
when RubySpeech::GRXML::NoMatch
|
|
24
|
-
|
|
22
|
+
finalize :nomatch
|
|
23
|
+
when RubySpeech::GRXML::MaxMatch
|
|
24
|
+
finalize :match, match
|
|
25
|
+
when RubySpeech::GRXML::Match
|
|
26
|
+
finalize :match, match if terminating?(digit)
|
|
25
27
|
when RubySpeech::GRXML::PotentialMatch
|
|
26
|
-
|
|
28
|
+
finalize :nomatch if terminating?(digit)
|
|
27
29
|
end
|
|
30
|
+
reset_inter_digit_timer unless @finished
|
|
28
31
|
end
|
|
29
32
|
|
|
30
|
-
def
|
|
31
|
-
|
|
32
|
-
cancel_inter_digit_timer
|
|
33
|
+
def start_timers
|
|
34
|
+
begin_initial_timer @initial_timeout/1000 unless @initial_timeout == -1
|
|
33
35
|
end
|
|
34
36
|
|
|
35
37
|
private
|
|
36
38
|
|
|
37
|
-
def
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
def terminating?(digit)
|
|
40
|
+
digit == @terminator
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def get_match
|
|
44
|
+
@matcher.match @buffer.dup
|
|
42
45
|
end
|
|
43
46
|
|
|
44
47
|
def after(*args, &block)
|
|
@@ -57,7 +60,7 @@ module Punchblock
|
|
|
57
60
|
|
|
58
61
|
def begin_initial_timer(timeout)
|
|
59
62
|
@initial_timer = after timeout do
|
|
60
|
-
|
|
63
|
+
finalize :noinput
|
|
61
64
|
end
|
|
62
65
|
end
|
|
63
66
|
|
|
@@ -71,7 +74,12 @@ module Punchblock
|
|
|
71
74
|
return if @inter_digit_timeout == -1
|
|
72
75
|
@inter_digit_timer ||= begin
|
|
73
76
|
after @inter_digit_timeout/1000 do
|
|
74
|
-
|
|
77
|
+
case (match = get_match)
|
|
78
|
+
when RubySpeech::GRXML::Match
|
|
79
|
+
finalize :match, match
|
|
80
|
+
else
|
|
81
|
+
finalize :nomatch
|
|
82
|
+
end
|
|
75
83
|
end
|
|
76
84
|
end
|
|
77
85
|
@inter_digit_timer.reset
|
|
@@ -82,6 +90,17 @@ module Punchblock
|
|
|
82
90
|
@inter_digit_timer.cancel
|
|
83
91
|
@inter_digit_timer = nil
|
|
84
92
|
end
|
|
93
|
+
|
|
94
|
+
def finalize(match_type, match = nil)
|
|
95
|
+
cancel_initial_timer
|
|
96
|
+
cancel_inter_digit_timer
|
|
97
|
+
if match
|
|
98
|
+
@responder.send match_type, match
|
|
99
|
+
else
|
|
100
|
+
@responder.send match_type
|
|
101
|
+
end
|
|
102
|
+
@finished = true
|
|
103
|
+
end
|
|
85
104
|
end
|
|
86
105
|
end
|
|
87
106
|
end
|
|
@@ -14,9 +14,10 @@ module Punchblock
|
|
|
14
14
|
HANGUP_CAUSE_TO_END_REASON = Hash.new :error
|
|
15
15
|
|
|
16
16
|
HANGUP_CAUSE_TO_END_REASON['USER_BUSY'] = :busy
|
|
17
|
+
HANGUP_CAUSE_TO_END_REASON['MANAGER_REQUEST'] = :hangup_command
|
|
17
18
|
|
|
18
19
|
%w{
|
|
19
|
-
NORMAL_CLEARING ORIGINATOR_CANCEL SYSTEM_SHUTDOWN
|
|
20
|
+
NORMAL_CLEARING ORIGINATOR_CANCEL SYSTEM_SHUTDOWN
|
|
20
21
|
BLIND_TRANSFER ATTENDED_TRANSFER PICKED_OFF NORMAL_UNSPECIFIED
|
|
21
22
|
}.each { |c| HANGUP_CAUSE_TO_END_REASON[c] = :hangup }
|
|
22
23
|
|
|
@@ -91,16 +92,16 @@ module Punchblock
|
|
|
91
92
|
command = @pending_joins[event[:other_leg_unique_id]]
|
|
92
93
|
command.response = true if command
|
|
93
94
|
|
|
94
|
-
|
|
95
|
-
send_pb_event Event::Joined.new(:
|
|
95
|
+
other_call_uri = event[:unique_id] == id ? event[:other_leg_unique_id] : event[:unique_id]
|
|
96
|
+
send_pb_event Event::Joined.new(:call_uri => other_call_uri)
|
|
96
97
|
end
|
|
97
98
|
|
|
98
99
|
register_handler :es, :event_name => 'CHANNEL_UNBRIDGE' do |event|
|
|
99
100
|
command = @pending_unjoins[event[:other_leg_unique_id]]
|
|
100
101
|
command.response = true if command
|
|
101
102
|
|
|
102
|
-
|
|
103
|
-
send_pb_event Event::Unjoined.new(:
|
|
103
|
+
other_call_uri = event[:unique_id] == id ? event[:other_leg_unique_id] : event[:unique_id]
|
|
104
|
+
send_pb_event Event::Unjoined.new(:call_uri => other_call_uri)
|
|
104
105
|
end
|
|
105
106
|
|
|
106
107
|
register_handler :es, [:has_key?, :scope_variable_punchblock_component_id] => true do |event|
|
|
@@ -145,8 +146,8 @@ module Punchblock
|
|
|
145
146
|
options[:origination_caller_id_number] = "'#{cid_number}'" if cid_number.present?
|
|
146
147
|
options[:origination_caller_id_name] = "'#{cid_name}'" if cid_name.present?
|
|
147
148
|
options[:originate_timeout] = dial_command.timeout/1000 if dial_command.timeout
|
|
148
|
-
dial_command.headers.each do |
|
|
149
|
-
options["sip_h_#{
|
|
149
|
+
dial_command.headers.each do |name, value|
|
|
150
|
+
options["sip_h_#{name}"] = "'#{value}'"
|
|
150
151
|
end
|
|
151
152
|
opts = options.inject([]) do |a, (k, v)|
|
|
152
153
|
a << "#{k}=#{v}"
|
|
@@ -154,7 +155,7 @@ module Punchblock
|
|
|
154
155
|
|
|
155
156
|
stream.bgapi "originate {#{opts}}#{dial_command.to} &park()"
|
|
156
157
|
|
|
157
|
-
dial_command.response = Ref.new :
|
|
158
|
+
dial_command.response = Ref.new uri: id
|
|
158
159
|
end
|
|
159
160
|
|
|
160
161
|
def outbound?
|
|
@@ -195,10 +196,10 @@ module Punchblock
|
|
|
195
196
|
hangup
|
|
196
197
|
command.response = true
|
|
197
198
|
when Command::Join
|
|
198
|
-
@pending_joins[command.
|
|
199
|
-
uuid_foo :bridge, command.
|
|
199
|
+
@pending_joins[command.call_uri] = command
|
|
200
|
+
uuid_foo :bridge, command.call_uri
|
|
200
201
|
when Command::Unjoin
|
|
201
|
-
@pending_unjoins[command.
|
|
202
|
+
@pending_unjoins[command.call_uri] = command
|
|
202
203
|
uuid_foo :transfer, '-both park inline'
|
|
203
204
|
when Command::Reject
|
|
204
205
|
hangup REJECT_TO_HANGUP_REASON[command.reason]
|
|
@@ -222,7 +223,7 @@ module Punchblock
|
|
|
222
223
|
end
|
|
223
224
|
end
|
|
224
225
|
|
|
225
|
-
def hangup(reason = '
|
|
226
|
+
def hangup(reason = 'MANAGER_REQUEST')
|
|
226
227
|
sendmsg :call_command => 'hangup', :hangup_cause => reason
|
|
227
228
|
end
|
|
228
229
|
|
|
@@ -244,7 +245,7 @@ module Punchblock
|
|
|
244
245
|
|
|
245
246
|
def send_end_event(reason)
|
|
246
247
|
send_pb_event Event::End.new(:reason => reason)
|
|
247
|
-
translator.deregister_call
|
|
248
|
+
translator.deregister_call id
|
|
248
249
|
terminate
|
|
249
250
|
end
|
|
250
251
|
|
|
@@ -268,7 +269,7 @@ module Punchblock
|
|
|
268
269
|
|
|
269
270
|
def headers
|
|
270
271
|
es_env.to_a.inject({}) do |accumulator, element|
|
|
271
|
-
accumulator[
|
|
272
|
+
accumulator['X-' + element[0].to_s] = element[1] || ''
|
|
272
273
|
accumulator
|
|
273
274
|
end
|
|
274
275
|
end
|
|
@@ -35,20 +35,21 @@ module Punchblock
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def validate
|
|
38
|
-
raise OptionError, 'An SSML document is required.' unless @component_node.
|
|
38
|
+
raise OptionError, 'An SSML document is required.' unless @component_node.render_documents.first.value
|
|
39
|
+
raise OptionError, 'Only a single document is supported.' unless @component_node.render_documents.size == 1
|
|
39
40
|
|
|
40
41
|
[:start_offset, :start_paused, :repeat_interval, :repeat_times, :max_time].each do |opt|
|
|
41
42
|
raise OptionError, "A #{opt} value is unsupported." if @component_node.send opt
|
|
42
43
|
end
|
|
43
44
|
|
|
44
45
|
case @component_node.interrupt_on
|
|
45
|
-
when :
|
|
46
|
+
when :voice, :dtmf, :any
|
|
46
47
|
raise OptionError, "An interrupt-on value of #{@component_node.interrupt_on} is unsupported."
|
|
47
48
|
end
|
|
48
49
|
end
|
|
49
50
|
|
|
50
|
-
def
|
|
51
|
-
Punchblock::Component::Output::Complete::
|
|
51
|
+
def finish_reason
|
|
52
|
+
Punchblock::Component::Output::Complete::Finish.new
|
|
52
53
|
end
|
|
53
54
|
end
|
|
54
55
|
end
|