punchblock 2.1.1 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -1
- data/CHANGELOG.md +7 -0
- data/lib/punchblock.rb +1 -1
- data/lib/punchblock/component.rb +2 -0
- data/lib/punchblock/component/input.rb +9 -1
- data/lib/punchblock/component/output.rb +1 -1
- data/lib/punchblock/component/receive_fax.rb +24 -0
- data/lib/punchblock/component/send_fax.rb +62 -0
- data/lib/punchblock/connection/asterisk.rb +1 -1
- data/lib/punchblock/event/complete.rb +15 -1
- data/lib/punchblock/translator/asterisk.rb +30 -15
- data/lib/punchblock/translator/asterisk/call.rb +13 -27
- data/lib/punchblock/translator/asterisk/component.rb +4 -7
- data/lib/punchblock/translator/asterisk/component/asterisk/agi_command.rb +1 -5
- data/lib/punchblock/translator/asterisk/component/composed_prompt.rb +8 -9
- data/lib/punchblock/translator/asterisk/component/input.rb +2 -3
- data/lib/punchblock/translator/asterisk/component/mrcp_prompt.rb +9 -9
- data/lib/punchblock/translator/asterisk/component/output.rb +134 -39
- data/lib/punchblock/translator/asterisk/component/record.rb +2 -3
- data/lib/punchblock/translator/asterisk/component/stop_by_redirect.rb +2 -3
- data/lib/punchblock/translator/dtmf_recognizer.rb +2 -4
- data/lib/punchblock/translator/freeswitch/component/abstract_output.rb +6 -1
- data/lib/punchblock/translator/freeswitch/component/flite_output.rb +1 -1
- data/lib/punchblock/translator/freeswitch/component/output.rb +12 -10
- data/lib/punchblock/translator/freeswitch/component/tts_output.rb +1 -1
- data/lib/punchblock/version.rb +1 -1
- data/spec/punchblock/component/input_spec.rb +91 -0
- data/spec/punchblock/component/output_spec.rb +1 -2
- data/spec/punchblock/component/receive_fax_spec.rb +111 -0
- data/spec/punchblock/component/send_fax_spec.rb +110 -0
- data/spec/punchblock/connection/asterisk_spec.rb +1 -1
- data/spec/punchblock/translator/asterisk/call_spec.rb +53 -79
- data/spec/punchblock/translator/asterisk/component/asterisk/agi_command_spec.rb +0 -3
- data/spec/punchblock/translator/asterisk/component/asterisk/ami_action_spec.rb +1 -1
- data/spec/punchblock/translator/asterisk/component/composed_prompt_spec.rb +2 -2
- data/spec/punchblock/translator/asterisk/component/input_spec.rb +6 -6
- data/spec/punchblock/translator/asterisk/component/mrcp_native_prompt_spec.rb +3 -3
- data/spec/punchblock/translator/asterisk/component/mrcp_prompt_spec.rb +9 -11
- data/spec/punchblock/translator/asterisk/component/output_spec.rb +902 -28
- data/spec/punchblock/translator/asterisk/component/stop_by_redirect_spec.rb +1 -1
- data/spec/punchblock/translator/asterisk/component_spec.rb +2 -9
- data/spec/punchblock/translator/asterisk_spec.rb +42 -94
- data/spec/punchblock/translator/freeswitch/component/flite_output_spec.rb +5 -5
- data/spec/punchblock/translator/freeswitch/component/output_spec.rb +7 -3
- data/spec/punchblock/translator/freeswitch/component/tts_output_spec.rb +17 -5
- metadata +67 -61
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1152b11b0e04b0b908129ed719d27c5d82c3e840
|
4
|
+
data.tar.gz: 8680c53051c70725c6b3aeb1b1fd05af8e416ceb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 26766a491387d61395c7ebb9cd6302ee7e0ac0c981ec6043b83f5ee098cb11684711b147e4094b2ad7da1c20d23fa9a2e5f39521f7197e90eeb8a1e4d360da92
|
7
|
+
data.tar.gz: e214e5d8e43088da4105ce7c3f4b20cba314440208cddfcf19887d6386ac517598e9cd7c151735e851abc20c6a93f1d73af589330ad40f71d14bb33ab866af7b
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
# [develop](https://github.com/adhearsion/punchblock)
|
2
2
|
|
3
|
+
# [v2.2.0](https://github.com/adhearsion/punchblock/compare/v2.1.1...v2.2.0) - [2014-01-15](https://rubygems.org/gems/punchblock/versions/2.2.0)
|
4
|
+
* Feature: Support Rayo CPA and Fax specifications
|
5
|
+
* Feature: Implement a simple filter for AMI events -> Rayo events in `Punchblock::Translator::Asterisk.event_filter=`
|
6
|
+
* Feature: Output on Asterisk supports a new `:native_or_unimrcp` renderer which allows for fallback of output rendering from Asterisk native when possible, to a TTS engine when it's not.
|
7
|
+
* Feature: Support output `repeat-times` on Asterisk.
|
8
|
+
* Bugfix: Remove per-call/component actors from Asterisk translator for performance/stability super-charge
|
9
|
+
|
3
10
|
# [v2.1.1](https://github.com/adhearsion/punchblock/compare/v2.1.0...v2.1.1) - [2013-12-19](https://rubygems.org/gems/punchblock/versions/2.1.1)
|
4
11
|
* Bugfix: Allow sending string SSML docs via Rayo
|
5
12
|
* Bugfix: Ensure that joined calls on Asterisk do not have implicitly linked lifecycles. Previously, joinees would be hungup when the joiner exited the bridge.
|
data/lib/punchblock.rb
CHANGED
@@ -75,7 +75,7 @@ module Punchblock
|
|
75
75
|
RAYO_VERSION = '1'
|
76
76
|
RAYO_NAMESPACES = {:core => [BASE_RAYO_NAMESPACE, RAYO_VERSION].compact.join(':')}
|
77
77
|
|
78
|
-
[:ext, :record, :output, :input, :prompt].each do |ns|
|
78
|
+
[:ext, :record, :output, :input, :prompt, :cpa, :fax].each do |ns|
|
79
79
|
RAYO_NAMESPACES[ns] = [BASE_RAYO_NAMESPACE, ns.to_s, RAYO_VERSION].compact.join(':')
|
80
80
|
RAYO_NAMESPACES[:"#{ns}_complete"] = [BASE_RAYO_NAMESPACE, ns.to_s, 'complete', RAYO_VERSION].compact.join(':')
|
81
81
|
end
|
data/lib/punchblock/component.rb
CHANGED
@@ -98,7 +98,7 @@ module Punchblock
|
|
98
98
|
end
|
99
99
|
|
100
100
|
def rayo_children(root)
|
101
|
-
root.cdata value
|
101
|
+
root.cdata value if value
|
102
102
|
end
|
103
103
|
|
104
104
|
private
|
@@ -108,6 +108,14 @@ module Punchblock
|
|
108
108
|
end
|
109
109
|
end
|
110
110
|
|
111
|
+
class Signal < Event::Complete::Reason
|
112
|
+
register :signal, :cpa
|
113
|
+
|
114
|
+
attribute :type, String
|
115
|
+
attribute :duration, Integer
|
116
|
+
attribute :value, String
|
117
|
+
end
|
118
|
+
|
111
119
|
class Complete
|
112
120
|
class Match < Event::Complete::Reason
|
113
121
|
register :match, :input_complete
|
@@ -80,7 +80,7 @@ module Punchblock
|
|
80
80
|
attribute :start_offset, Integer
|
81
81
|
|
82
82
|
# @return [true, false] Indicates wether or not the component should be started in a paused state to be resumed at a later time.
|
83
|
-
attribute :start_paused, Boolean
|
83
|
+
attribute :start_paused, Boolean
|
84
84
|
|
85
85
|
# @return [Integer] Indicates the duration of silence that should space repeats of the rendered document.
|
86
86
|
attribute :repeat_interval, Integer
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Punchblock
|
4
|
+
module Component
|
5
|
+
class ReceiveFax < ComponentNode
|
6
|
+
register :receivefax, :fax
|
7
|
+
|
8
|
+
class Fax < RayoNode
|
9
|
+
register :fax, :fax_complete
|
10
|
+
|
11
|
+
attribute :url, String
|
12
|
+
attribute :resolution, String
|
13
|
+
attribute :pages, Integer
|
14
|
+
attribute :size, Integer
|
15
|
+
end
|
16
|
+
|
17
|
+
class Complete
|
18
|
+
class Finish < Event::Complete::Reason
|
19
|
+
register :finish, :fax_complete
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Punchblock
|
4
|
+
module Component
|
5
|
+
class SendFax < ComponentNode
|
6
|
+
register :sendfax, :fax
|
7
|
+
|
8
|
+
class FaxDocument < RayoNode
|
9
|
+
register :document, :fax
|
10
|
+
|
11
|
+
attribute :url, String
|
12
|
+
attribute :identity, String
|
13
|
+
attribute :header, String
|
14
|
+
attribute :pages, String
|
15
|
+
|
16
|
+
def inherit(xml_node)
|
17
|
+
super
|
18
|
+
if pages = xml_node[:pages]
|
19
|
+
self.pages = pages.split(',').map { |p| p.include?('-') ? Range.new(*p.split('-').map(&:to_i)) : p.to_i }
|
20
|
+
end
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def rayo_attributes
|
25
|
+
{
|
26
|
+
'url' => url,
|
27
|
+
'identity' => identity,
|
28
|
+
'header' => header,
|
29
|
+
'pages' => rayo_pages
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def rayo_pages
|
36
|
+
pages ? pages.map { |p| p.is_a?(Range) ? "#{p.min}-#{p.max}" : p }.join(',') : nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def inherit(xml_node)
|
41
|
+
document_nodes = xml_node.xpath 'ns:document', ns: self.class.registered_ns
|
42
|
+
self.render_documents = document_nodes.to_a.map { |node| FaxDocument.from_xml node }
|
43
|
+
|
44
|
+
super
|
45
|
+
end
|
46
|
+
|
47
|
+
def rayo_children(root)
|
48
|
+
render_documents.each do |render_document|
|
49
|
+
render_document.to_rayo root.parent
|
50
|
+
end
|
51
|
+
super
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [Document] the document to render
|
55
|
+
attribute :render_documents, Array[FaxDocument], default: []
|
56
|
+
|
57
|
+
def render_document=(other)
|
58
|
+
self.render_documents = [other].compact
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -9,6 +9,9 @@ module Punchblock
|
|
9
9
|
|
10
10
|
attribute :recording
|
11
11
|
|
12
|
+
attribute :fax
|
13
|
+
attribute :fax_metadata, Hash, default: {}
|
14
|
+
|
12
15
|
def inherit(xml_node)
|
13
16
|
if reason_node = xml_node.at_xpath('*')
|
14
17
|
self.reason = RayoNode.from_xml(reason_node).tap do |reason|
|
@@ -24,11 +27,22 @@ module Punchblock
|
|
24
27
|
end
|
25
28
|
end
|
26
29
|
|
30
|
+
if fax_node = xml_node.at_xpath('//ns:fax', ns: RAYO_NAMESPACES[:fax_complete])
|
31
|
+
self.fax = RayoNode.from_xml(fax_node).tap do |fax|
|
32
|
+
fax.target_call_id = target_call_id
|
33
|
+
fax.component_id = component_id
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
xml_node.xpath('//ns:metadata', ns: RAYO_NAMESPACES[:fax_complete]).each do |md|
|
38
|
+
fax_metadata[md['name']] = md['value']
|
39
|
+
end
|
40
|
+
|
27
41
|
super
|
28
42
|
end
|
29
43
|
|
30
44
|
class Reason < RayoNode
|
31
|
-
attribute :name
|
45
|
+
attribute :name, Symbol, default: ->(node,_) { node.class.registered_name.to_sym }
|
32
46
|
|
33
47
|
def inherit(xml_node)
|
34
48
|
self.name = xml_node.name.to_sym
|
@@ -28,6 +28,23 @@ module Punchblock
|
|
28
28
|
|
29
29
|
trap_exit :actor_died
|
30
30
|
|
31
|
+
# Set the AMI event filter to be applied to incoming AMI events. A truthy return value will send the event via Rayo to the client (Adhearsion).
|
32
|
+
#
|
33
|
+
# @param [#[<RubyAMI::Event>]] filter
|
34
|
+
#
|
35
|
+
# @example A lambda
|
36
|
+
# Punchblock::Translator::Asterisk.event_filter = ->(event) { event.name == 'AsyncAGI' }
|
37
|
+
#
|
38
|
+
def self.event_filter=(filter)
|
39
|
+
@event_filter = filter
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.event_passes_filter?(event)
|
43
|
+
@event_filter ? !!@event_filter[event] : true
|
44
|
+
end
|
45
|
+
|
46
|
+
event_filter = nil
|
47
|
+
|
31
48
|
def initialize(ami_client, connection)
|
32
49
|
@ami_client, @connection = ami_client, connection
|
33
50
|
@calls, @components, @channel_to_call_id = {}, {}, {}
|
@@ -59,11 +76,6 @@ module Punchblock
|
|
59
76
|
@components[component_id]
|
60
77
|
end
|
61
78
|
|
62
|
-
def shutdown
|
63
|
-
@calls.values.each { |call| call.async.shutdown }
|
64
|
-
terminate
|
65
|
-
end
|
66
|
-
|
67
79
|
def handle_ami_event(event)
|
68
80
|
return unless event.is_a? RubyAMI::Event
|
69
81
|
|
@@ -77,11 +89,10 @@ module Punchblock
|
|
77
89
|
|
78
90
|
ami_dispatch_to_or_create_call event
|
79
91
|
|
80
|
-
|
92
|
+
if !ami_event_known_call?(event) && self.class.event_passes_filter?(event)
|
81
93
|
handle_pb_event Event::Asterisk::AMI::Event.new(name: event.name, headers: event.headers)
|
82
94
|
end
|
83
95
|
end
|
84
|
-
exclusive :handle_ami_event
|
85
96
|
|
86
97
|
def handle_pb_event(event)
|
87
98
|
connection.handle_event event
|
@@ -104,7 +115,11 @@ module Punchblock
|
|
104
115
|
|
105
116
|
def execute_call_command(command)
|
106
117
|
if call = call_with_id(command.target_call_id)
|
107
|
-
|
118
|
+
begin
|
119
|
+
call.execute_command command
|
120
|
+
rescue => e
|
121
|
+
deregister_call call.id, call.channel
|
122
|
+
end
|
108
123
|
else
|
109
124
|
command.response = ProtocolError.new.setup :item_not_found, "Could not find a call with ID #{command.target_call_id}", command.target_call_id
|
110
125
|
end
|
@@ -112,7 +127,7 @@ module Punchblock
|
|
112
127
|
|
113
128
|
def execute_component_command(command)
|
114
129
|
if (component = component_with_id(command.component_id))
|
115
|
-
component.
|
130
|
+
component.execute_command command
|
116
131
|
else
|
117
132
|
command.response = ProtocolError.new.setup :item_not_found, "Could not find a component with ID #{command.component_id}", command.target_call_id, command.component_id
|
118
133
|
end
|
@@ -123,11 +138,11 @@ module Punchblock
|
|
123
138
|
when Punchblock::Component::Asterisk::AMI::Action
|
124
139
|
component = Component::Asterisk::AMIAction.new command, current_actor, ami_client
|
125
140
|
register_component component
|
126
|
-
component.
|
141
|
+
component.execute
|
127
142
|
when Punchblock::Command::Dial
|
128
|
-
call = Call.
|
143
|
+
call = Call.new command.to, current_actor, ami_client, connection
|
129
144
|
register_call call
|
130
|
-
call.
|
145
|
+
call.dial command
|
131
146
|
else
|
132
147
|
command.response = ProtocolError.new.setup 'command-not-acceptable', "Did not understand command"
|
133
148
|
end
|
@@ -182,7 +197,7 @@ module Punchblock
|
|
182
197
|
if !calls_for_event.empty?
|
183
198
|
calls_for_event.each_pair do |channel, call|
|
184
199
|
next if channel.bridged? && !EVENTS_ALLOWED_BRIDGED.include?(event.name)
|
185
|
-
call.
|
200
|
+
call.process_ami_event event
|
186
201
|
end
|
187
202
|
elsif event.name == "AsyncAGI" && event['SubEvent'] == "Start"
|
188
203
|
handle_async_agi_start_event event
|
@@ -204,9 +219,9 @@ module Punchblock
|
|
204
219
|
|
205
220
|
return if env[:agi_extension] == 'h' || env[:agi_type] == 'Kill'
|
206
221
|
|
207
|
-
call = Call.
|
222
|
+
call = Call.new event['Channel'], current_actor, ami_client, connection, env
|
208
223
|
register_call call
|
209
|
-
call.
|
224
|
+
call.send_offer
|
210
225
|
end
|
211
226
|
end
|
212
227
|
end
|
@@ -7,12 +7,8 @@ module Punchblock
|
|
7
7
|
class Asterisk
|
8
8
|
class Call
|
9
9
|
include HasGuardedHandlers
|
10
|
-
include Celluloid
|
11
10
|
include DeadActorSafety
|
12
11
|
|
13
|
-
extend ActorHasGuardedHandlers
|
14
|
-
execute_guarded_handlers_on_receiver
|
15
|
-
|
16
12
|
InvalidCommandError = Class.new Punchblock::Error
|
17
13
|
|
18
14
|
OUTBOUND_CHANNEL_MATCH = /.* <(?<channel>.*)>/.freeze
|
@@ -29,8 +25,6 @@ module Punchblock
|
|
29
25
|
HANGUP_CAUSE_TO_END_REASON[22] = :reject
|
30
26
|
HANGUP_CAUSE_TO_END_REASON[102] = :timeout
|
31
27
|
|
32
|
-
trap_exit :actor_died
|
33
|
-
|
34
28
|
def initialize(channel, translator, ami_client, connection, agi_env = nil)
|
35
29
|
@channel, @translator, @ami_client, @connection = channel, translator, ami_client, connection
|
36
30
|
@agi_env = agi_env || {}
|
@@ -47,6 +41,10 @@ module Punchblock
|
|
47
41
|
@components[component.id] ||= component
|
48
42
|
end
|
49
43
|
|
44
|
+
def deregister_component(id)
|
45
|
+
@components.delete id
|
46
|
+
end
|
47
|
+
|
50
48
|
def component_with_id(component_id)
|
51
49
|
@components[component_id]
|
52
50
|
end
|
@@ -56,10 +54,6 @@ module Punchblock
|
|
56
54
|
send_pb_event offer_event
|
57
55
|
end
|
58
56
|
|
59
|
-
def shutdown
|
60
|
-
terminate
|
61
|
-
end
|
62
|
-
|
63
57
|
def channel_var(variable)
|
64
58
|
@channel_variables[variable] || fetch_channel_var(variable)
|
65
59
|
end
|
@@ -113,7 +107,9 @@ module Punchblock
|
|
113
107
|
end
|
114
108
|
|
115
109
|
def process_ami_event(ami_event)
|
116
|
-
|
110
|
+
if Asterisk.event_passes_filter?(ami_event)
|
111
|
+
send_pb_event Event::Asterisk::AMI::Event.new(name: ami_event.name, headers: ami_event.headers)
|
112
|
+
end
|
117
113
|
|
118
114
|
case ami_event.name
|
119
115
|
when 'Hangup'
|
@@ -258,8 +254,6 @@ module Punchblock
|
|
258
254
|
event = condition.wait
|
259
255
|
return unless event
|
260
256
|
agi.parse_result event
|
261
|
-
rescue ChannelGoneError, RubyAMI::Error => e
|
262
|
-
abort e
|
263
257
|
end
|
264
258
|
|
265
259
|
def logger_id
|
@@ -285,21 +279,14 @@ module Punchblock
|
|
285
279
|
def handle_hangup_event(code = 16)
|
286
280
|
reason = @hangup_cause || HANGUP_CAUSE_TO_END_REASON[code]
|
287
281
|
@block_commands = true
|
288
|
-
@components.
|
289
|
-
|
290
|
-
component.call_ended if component.alive?
|
291
|
-
end
|
282
|
+
@components.each_pair do |id, component|
|
283
|
+
component.call_ended
|
292
284
|
end
|
293
285
|
send_end_event reason, code
|
294
286
|
end
|
295
287
|
|
296
|
-
def
|
297
|
-
|
298
|
-
@components.delete id
|
299
|
-
return unless reason
|
300
|
-
complete_event = Punchblock::Event::Complete.new :component_id => id, source_uri: id, :reason => Punchblock::Event::Complete::Error.new
|
301
|
-
send_pb_event complete_event
|
302
|
-
end
|
288
|
+
def after(*args, &block)
|
289
|
+
translator.after(*args, &block)
|
303
290
|
end
|
304
291
|
|
305
292
|
private
|
@@ -320,13 +307,12 @@ module Punchblock
|
|
320
307
|
def send_end_event(reason, code = nil)
|
321
308
|
send_pb_event Event::End.new(reason: reason, platform_code: code)
|
322
309
|
translator.deregister_call id, channel
|
323
|
-
terminate
|
324
310
|
end
|
325
311
|
|
326
312
|
def execute_component(type, command, options = {})
|
327
|
-
type.
|
313
|
+
type.new(command, self).tap do |component|
|
328
314
|
register_component component
|
329
|
-
component.
|
315
|
+
component.execute
|
330
316
|
end
|
331
317
|
end
|
332
318
|
|
@@ -17,14 +17,11 @@ module Punchblock
|
|
17
17
|
autoload :StopByRedirect
|
18
18
|
|
19
19
|
class Component
|
20
|
-
include Celluloid
|
21
|
-
include DeadActorSafety
|
22
|
-
|
23
20
|
attr_reader :id, :call, :call_id
|
24
21
|
|
25
22
|
def initialize(component_node, call = nil)
|
26
23
|
@component_node, @call = component_node, call
|
27
|
-
@call_id =
|
24
|
+
@call_id = call.id if call
|
28
25
|
@id = Punchblock.new_uuid
|
29
26
|
@complete = false
|
30
27
|
setup
|
@@ -37,19 +34,19 @@ module Punchblock
|
|
37
34
|
command.response = ProtocolError.new.setup 'command-not-acceptable', "Did not understand command for component #{id}", call_id, id
|
38
35
|
end
|
39
36
|
|
40
|
-
def send_complete_event(reason, recording = nil
|
37
|
+
def send_complete_event(reason, recording = nil)
|
41
38
|
return if @complete
|
42
39
|
@complete = true
|
43
40
|
event = Punchblock::Event::Complete.new reason: reason, recording: recording
|
44
41
|
send_event event
|
45
|
-
|
42
|
+
call.deregister_component id if call
|
46
43
|
end
|
47
44
|
|
48
45
|
def send_event(event)
|
49
46
|
event.component_id = id
|
50
47
|
event.target_call_id = call_id
|
51
48
|
event.source_uri = id
|
52
|
-
|
49
|
+
translator.handle_pb_event event
|
53
50
|
end
|
54
51
|
|
55
52
|
def logger_id
|