adhearsion 1.2.6 → 2.0.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -10
- data/CHANGELOG.md +273 -0
- data/Gemfile +1 -1
- data/Guardfile +17 -0
- data/README.markdown +61 -9
- data/Rakefile +16 -48
- data/adhearsion.gemspec +21 -7
- data/bin/ahn +3 -1
- data/cucumber.yml +4 -0
- data/features/app_generator.feature +42 -0
- data/features/cli.feature +108 -0
- data/features/step_definitions/app_generator_steps.rb +6 -0
- data/features/step_definitions/cli_steps.rb +74 -0
- data/features/support/aruba_helper.rb +22 -0
- data/features/support/env.rb +37 -0
- data/features/support/utils.rb +8 -0
- data/lib/adhearsion.rb +85 -41
- data/lib/adhearsion/call.rb +176 -0
- data/lib/adhearsion/call_controller.rb +134 -0
- data/lib/adhearsion/call_controller/dial.rb +70 -0
- data/lib/adhearsion/call_controller/input.rb +173 -0
- data/lib/adhearsion/call_controller/menu.rb +124 -0
- data/lib/adhearsion/call_controller/output.rb +267 -0
- data/lib/adhearsion/call_controller/record.rb +42 -0
- data/lib/adhearsion/call_controller/utility.rb +60 -0
- data/lib/adhearsion/calls.rb +81 -0
- data/lib/adhearsion/cli.rb +1 -3
- data/lib/adhearsion/cli_commands.rb +142 -0
- data/lib/adhearsion/configuration.rb +149 -0
- data/lib/adhearsion/console.rb +19 -8
- data/lib/adhearsion/dialplan_controller.rb +9 -0
- data/lib/adhearsion/events.rb +84 -0
- data/lib/adhearsion/foundation/all.rb +0 -7
- data/lib/adhearsion/foundation/custom_daemonizer.rb +4 -6
- data/lib/adhearsion/foundation/exception_handler.rb +9 -0
- data/lib/adhearsion/foundation/object.rb +26 -8
- data/lib/adhearsion/foundation/synchronized_hash.rb +3 -6
- data/lib/adhearsion/foundation/thread_safety.rb +17 -1
- data/lib/adhearsion/generators/app/app_generator.rb +4 -13
- data/lib/adhearsion/generators/app/templates/Gemfile +10 -5
- data/lib/adhearsion/generators/app/templates/Procfile +1 -0
- data/lib/adhearsion/generators/app/templates/README.md +28 -0
- data/lib/adhearsion/generators/app/templates/config/adhearsion.rb +41 -0
- data/lib/adhearsion/generators/app/templates/{components/simon_game → lib}/simon_game.rb +6 -18
- data/lib/adhearsion/generators/app/templates/script/ahn +2 -2
- data/lib/adhearsion/initializer.rb +151 -293
- data/lib/adhearsion/initializer/logging.rb +33 -0
- data/lib/adhearsion/logging.rb +65 -69
- data/lib/adhearsion/menu_dsl.rb +15 -0
- data/lib/adhearsion/menu_dsl/calculated_match.rb +39 -0
- data/lib/adhearsion/menu_dsl/calculated_match_collection.rb +41 -0
- data/lib/adhearsion/menu_dsl/fixnum_match_calculator.rb +18 -0
- data/lib/adhearsion/menu_dsl/match_calculator.rb +36 -0
- data/lib/adhearsion/{voip/menu_state_machine/menu_class.rb → menu_dsl/menu.rb} +38 -40
- data/lib/adhearsion/menu_dsl/menu_builder.rb +69 -0
- data/lib/adhearsion/menu_dsl/range_match_calculator.rb +55 -0
- data/lib/adhearsion/menu_dsl/string_match_calculator.rb +21 -0
- data/lib/adhearsion/outbound_call.rb +64 -0
- data/lib/adhearsion/plugin.rb +319 -0
- data/lib/adhearsion/plugin/collection.rb +19 -0
- data/lib/adhearsion/plugin/initializer.rb +37 -0
- data/lib/adhearsion/plugin/methods_container.rb +6 -0
- data/lib/adhearsion/process.rb +94 -0
- data/lib/adhearsion/punchblock_plugin.rb +29 -0
- data/lib/adhearsion/punchblock_plugin/initializer.rb +137 -0
- data/lib/adhearsion/router.rb +30 -0
- data/lib/adhearsion/router/route.rb +42 -0
- data/lib/adhearsion/script_ahn_loader.rb +2 -2
- data/lib/adhearsion/tasks.rb +14 -9
- data/lib/adhearsion/tasks/configuration.rb +26 -0
- data/lib/adhearsion/tasks/plugins.rb +17 -0
- data/lib/adhearsion/version.rb +8 -14
- data/spec/adhearsion/call_controller/dial_spec.rb +138 -0
- data/spec/adhearsion/call_controller/input_spec.rb +278 -0
- data/spec/adhearsion/call_controller/menu_spec.rb +120 -0
- data/spec/adhearsion/call_controller/output_spec.rb +466 -0
- data/spec/adhearsion/call_controller/record_spec.rb +125 -0
- data/spec/adhearsion/call_controller_spec.rb +395 -0
- data/spec/adhearsion/call_spec.rb +438 -0
- data/spec/adhearsion/calls_spec.rb +47 -0
- data/spec/adhearsion/configuration_spec.rb +308 -0
- data/spec/adhearsion/dialplan_controller_spec.rb +26 -0
- data/spec/adhearsion/events_spec.rb +112 -0
- data/spec/adhearsion/initializer/logging_spec.rb +58 -0
- data/spec/adhearsion/initializer_spec.rb +209 -122
- data/spec/adhearsion/logging_spec.rb +58 -47
- data/spec/adhearsion/menu_dsl/calculated_match_collection_spec.rb +56 -0
- data/spec/adhearsion/menu_dsl/calculated_match_spec.rb +57 -0
- data/spec/adhearsion/menu_dsl/fixnum_match_calculator_spec.rb +33 -0
- data/spec/adhearsion/menu_dsl/match_calculator_spec.rb +13 -0
- data/spec/adhearsion/menu_dsl/menu_builder_spec.rb +118 -0
- data/spec/adhearsion/menu_dsl/menu_spec.rb +210 -0
- data/spec/adhearsion/menu_dsl/range_match_calculator_spec.rb +28 -0
- data/spec/adhearsion/menu_dsl/string_match_calculator_spec.rb +36 -0
- data/spec/adhearsion/menu_dsl_spec.rb +12 -0
- data/spec/adhearsion/outbound_call_spec.rb +174 -0
- data/spec/adhearsion/plugin_spec.rb +489 -0
- data/spec/adhearsion/process_spec.rb +34 -0
- data/spec/adhearsion/punchblock_plugin/initializer_spec.rb +294 -0
- data/spec/adhearsion/router/route_spec.rb +99 -0
- data/spec/adhearsion/router_spec.rb +106 -0
- data/spec/adhearsion_spec.rb +46 -0
- data/spec/spec_helper.rb +14 -14
- data/spec/support/call_controller_test_helpers.rb +48 -0
- data/spec/support/initializer_stubs.rb +8 -13
- data/spec/support/punchblock_mocks.rb +6 -0
- metadata +255 -253
- data/CHANGELOG +0 -174
- data/bin/ahnctl +0 -68
- data/bin/jahn +0 -43
- data/examples/asterisk_manager_interface/standalone.rb +0 -51
- data/lib/adhearsion/commands.rb +0 -302
- data/lib/adhearsion/component_manager.rb +0 -278
- data/lib/adhearsion/component_manager/component_tester.rb +0 -54
- data/lib/adhearsion/component_manager/spec_framework.rb +0 -18
- data/lib/adhearsion/events_support.rb +0 -65
- data/lib/adhearsion/foundation/blank_slate.rb +0 -3
- data/lib/adhearsion/foundation/event_socket.rb +0 -205
- data/lib/adhearsion/foundation/future_resource.rb +0 -36
- data/lib/adhearsion/foundation/metaprogramming.rb +0 -17
- data/lib/adhearsion/foundation/numeric.rb +0 -13
- data/lib/adhearsion/foundation/pseudo_guid.rb +0 -10
- data/lib/adhearsion/foundation/relationship_properties.rb +0 -42
- data/lib/adhearsion/foundation/string.rb +0 -26
- data/lib/adhearsion/generators/app/templates/.ahnrc +0 -34
- data/lib/adhearsion/generators/app/templates/README +0 -8
- data/lib/adhearsion/generators/app/templates/components/ami_remote/ami_remote.rb +0 -15
- data/lib/adhearsion/generators/app/templates/components/disabled/HOW_TO_ENABLE +0 -7
- data/lib/adhearsion/generators/app/templates/components/disabled/stomp_gateway/README.markdown +0 -47
- data/lib/adhearsion/generators/app/templates/components/disabled/stomp_gateway/stomp_gateway.rb +0 -34
- data/lib/adhearsion/generators/app/templates/components/disabled/stomp_gateway/stomp_gateway.yml +0 -12
- data/lib/adhearsion/generators/app/templates/components/disabled/xmpp_gateway/README.markdown +0 -3
- data/lib/adhearsion/generators/app/templates/components/disabled/xmpp_gateway/xmpp_gateway.rb +0 -11
- data/lib/adhearsion/generators/app/templates/components/disabled/xmpp_gateway/xmpp_gateway.yml +0 -0
- data/lib/adhearsion/generators/app/templates/config/startup.rb +0 -81
- data/lib/adhearsion/generators/app/templates/dialplan.rb +0 -3
- data/lib/adhearsion/generators/app/templates/events.rb +0 -33
- data/lib/adhearsion/host_definitions.rb +0 -67
- data/lib/adhearsion/initializer/asterisk.rb +0 -86
- data/lib/adhearsion/initializer/configuration.rb +0 -324
- data/lib/adhearsion/initializer/database.rb +0 -60
- data/lib/adhearsion/initializer/drb.rb +0 -31
- data/lib/adhearsion/initializer/freeswitch.rb +0 -22
- data/lib/adhearsion/initializer/ldap.rb +0 -57
- data/lib/adhearsion/initializer/rails.rb +0 -41
- data/lib/adhearsion/initializer/xmpp.rb +0 -42
- data/lib/adhearsion/tasks/components.rb +0 -32
- data/lib/adhearsion/tasks/database.rb +0 -5
- data/lib/adhearsion/tasks/deprecations.rb +0 -59
- data/lib/adhearsion/tasks/generating.rb +0 -20
- data/lib/adhearsion/tasks/lint.rb +0 -4
- data/lib/adhearsion/voip/asterisk.rb +0 -4
- data/lib/adhearsion/voip/asterisk/agi_server.rb +0 -121
- data/lib/adhearsion/voip/asterisk/commands.rb +0 -1966
- data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +0 -140
- data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +0 -102
- data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +0 -250
- data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +0 -240
- data/lib/adhearsion/voip/asterisk/config_manager.rb +0 -64
- data/lib/adhearsion/voip/asterisk/manager_interface.rb +0 -697
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rb +0 -1681
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb +0 -341
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_messages.rb +0 -78
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_protocol_lexer_machine.rl +0 -87
- data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +0 -80
- data/lib/adhearsion/voip/call.rb +0 -521
- data/lib/adhearsion/voip/call_routing.rb +0 -64
- data/lib/adhearsion/voip/commands.rb +0 -17
- data/lib/adhearsion/voip/constants.rb +0 -39
- data/lib/adhearsion/voip/conveniences.rb +0 -18
- data/lib/adhearsion/voip/dial_plan.rb +0 -252
- data/lib/adhearsion/voip/dsl/dialing_dsl.rb +0 -151
- data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +0 -37
- data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +0 -27
- data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +0 -124
- data/lib/adhearsion/voip/dsl/dialplan/parser.rb +0 -69
- data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +0 -16
- data/lib/adhearsion/voip/dsl/numerical_string.rb +0 -128
- data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +0 -48
- data/lib/adhearsion/voip/freeswitch/event_handler.rb +0 -58
- data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +0 -129
- data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +0 -38
- data/lib/adhearsion/voip/freeswitch/oes_server.rb +0 -195
- data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +0 -80
- data/lib/adhearsion/voip/menu_state_machine/matchers.rb +0 -123
- data/lib/adhearsion/voip/menu_state_machine/menu_builder.rb +0 -57
- data/lib/adhearsion/xmpp/connection.rb +0 -61
- data/lib/theatre.rb +0 -147
- data/lib/theatre/README.markdown +0 -64
- data/lib/theatre/callback_definition_loader.rb +0 -86
- data/lib/theatre/guid.rb +0 -23
- data/lib/theatre/invocation.rb +0 -131
- data/lib/theatre/namespace_manager.rb +0 -153
- data/lib/theatre/version.rb +0 -2
- data/spec/adhearsion/cli_spec.rb +0 -306
- data/spec/adhearsion/component_manager_spec.rb +0 -292
- data/spec/adhearsion/constants_spec.rb +0 -8
- data/spec/adhearsion/drb_spec.rb +0 -65
- data/spec/adhearsion/fixtures/dialplan.rb +0 -3
- data/spec/adhearsion/foundation/event_socket_spec.rb +0 -168
- data/spec/adhearsion/host_definitions_spec.rb +0 -79
- data/spec/adhearsion/initializer/configuration_spec.rb +0 -291
- data/spec/adhearsion/initializer/loading_spec.rb +0 -154
- data/spec/adhearsion/initializer/paths_spec.rb +0 -74
- data/spec/adhearsion/relationship_properties_spec.rb +0 -54
- data/spec/adhearsion/voip/asterisk/agi_server_spec.rb +0 -473
- data/spec/adhearsion/voip/asterisk/ami/ami_spec.rb +0 -550
- data/spec/adhearsion/voip/asterisk/ami/lexer/ami_fixtures.yml +0 -30
- data/spec/adhearsion/voip/asterisk/ami/lexer/lexer_story +0 -291
- data/spec/adhearsion/voip/asterisk/ami/lexer/lexer_story.rb +0 -241
- data/spec/adhearsion/voip/asterisk/ami/lexer/story_helper.rb +0 -124
- data/spec/adhearsion/voip/asterisk/commands_spec.rb +0 -3241
- data/spec/adhearsion/voip/asterisk/config_file_generators/agents_spec.rb +0 -251
- data/spec/adhearsion/voip/asterisk/config_file_generators/queues_spec.rb +0 -323
- data/spec/adhearsion/voip/asterisk/config_file_generators/voicemail_spec.rb +0 -306
- data/spec/adhearsion/voip/asterisk/config_manager_spec.rb +0 -127
- data/spec/adhearsion/voip/asterisk/menu_command/calculated_match_spec.rb +0 -109
- data/spec/adhearsion/voip/asterisk/menu_command/matchers_spec.rb +0 -97
- data/spec/adhearsion/voip/call_routing_spec.rb +0 -125
- data/spec/adhearsion/voip/dialplan_manager_spec.rb +0 -468
- data/spec/adhearsion/voip/dsl/dialing_dsl_spec.rb +0 -270
- data/spec/adhearsion/voip/dsl/dispatcher_spec.rb +0 -82
- data/spec/adhearsion/voip/dsl/dispatcher_spec_helper.rb +0 -45
- data/spec/adhearsion/voip/dsl/parser_spec.rb +0 -69
- data/spec/adhearsion/voip/freeswitch/basic_connection_manager_spec.rb +0 -39
- data/spec/adhearsion/voip/freeswitch/inbound_connection_manager_spec.rb +0 -39
- data/spec/adhearsion/voip/freeswitch/oes_server_spec.rb +0 -9
- data/spec/adhearsion/voip/numerical_string_spec.rb +0 -61
- data/spec/adhearsion/voip/phone_number_spec.rb +0 -45
- data/spec/support/the_following_code.rb +0 -3
- data/spec/theatre/dsl_examples/simple_before_call.rb +0 -7
- data/spec/theatre/dsl_spec.rb +0 -69
- data/spec/theatre/invocation_spec.rb +0 -182
- data/spec/theatre/namespace_spec.rb +0 -125
- data/spec/theatre/spec_helper_spec.rb +0 -28
- data/spec/theatre/theatre_class_spec.rb +0 -148
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Adhearsion
|
4
|
+
##
|
5
|
+
# Encapsulates call-related data and behavior.
|
6
|
+
#
|
7
|
+
class Call
|
8
|
+
|
9
|
+
include HasGuardedHandlers
|
10
|
+
|
11
|
+
attr_accessor :offer, :client, :end_reason, :commands
|
12
|
+
|
13
|
+
def initialize(offer = nil)
|
14
|
+
if offer
|
15
|
+
@offer = offer
|
16
|
+
@client = offer.client
|
17
|
+
end
|
18
|
+
|
19
|
+
@tag_mutex = Mutex.new
|
20
|
+
@tags = []
|
21
|
+
@end_reason_mutex = Mutex.new
|
22
|
+
end_reason = nil
|
23
|
+
@commands = CommandRegistry.new
|
24
|
+
|
25
|
+
register_initial_handlers
|
26
|
+
end
|
27
|
+
|
28
|
+
def id
|
29
|
+
@offer.call_id
|
30
|
+
end
|
31
|
+
|
32
|
+
def tags
|
33
|
+
@tag_mutex.synchronize { @tags.clone }
|
34
|
+
end
|
35
|
+
|
36
|
+
# This may still be a symbol, but no longer requires the tag to be a symbol although beware
|
37
|
+
# that using a symbol would create a memory leak if used improperly
|
38
|
+
# @param [String, Symbol] label String or Symbol with which to tag this call
|
39
|
+
def tag(label)
|
40
|
+
raise ArgumentError, "Tag must be a String or Symbol" unless [String, Symbol].include?(label.class)
|
41
|
+
@tag_mutex.synchronize { @tags << label }
|
42
|
+
end
|
43
|
+
|
44
|
+
def remove_tag(symbol)
|
45
|
+
@tag_mutex.synchronize do
|
46
|
+
@tags.reject! { |tag| tag == symbol }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def tagged_with?(symbol)
|
51
|
+
@tag_mutex.synchronize { @tags.include? symbol }
|
52
|
+
end
|
53
|
+
|
54
|
+
def register_event_handler(*guards, &block)
|
55
|
+
register_handler :event, *guards, &block
|
56
|
+
end
|
57
|
+
|
58
|
+
def deliver_message(message)
|
59
|
+
trigger_handler :event, message
|
60
|
+
end
|
61
|
+
alias << deliver_message
|
62
|
+
|
63
|
+
def register_initial_handlers
|
64
|
+
on_end do |event|
|
65
|
+
hangup
|
66
|
+
@end_reason_mutex.synchronize { @end_reason = event.reason }
|
67
|
+
commands.terminate
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def on_end(&block)
|
72
|
+
register_event_handler :class => Punchblock::Event::End do |event|
|
73
|
+
block.call event
|
74
|
+
throw :pass
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def active?
|
79
|
+
@end_reason_mutex.synchronize { !end_reason }
|
80
|
+
end
|
81
|
+
|
82
|
+
def accept(headers = nil)
|
83
|
+
write_and_await_response Punchblock::Command::Accept.new(:headers => headers)
|
84
|
+
end
|
85
|
+
|
86
|
+
def answer(headers = nil)
|
87
|
+
write_and_await_response Punchblock::Command::Answer.new(:headers => headers)
|
88
|
+
end
|
89
|
+
|
90
|
+
def reject(reason = :busy, headers = nil)
|
91
|
+
write_and_await_response Punchblock::Command::Reject.new(:reason => reason, :headers => headers)
|
92
|
+
end
|
93
|
+
|
94
|
+
def hangup!(headers = nil)
|
95
|
+
return false unless active?
|
96
|
+
@end_reason_mutex.synchronize { @end_reason = true }
|
97
|
+
write_and_await_response Punchblock::Command::Hangup.new(:headers => headers)
|
98
|
+
end
|
99
|
+
|
100
|
+
def hangup
|
101
|
+
Adhearsion.active_calls.remove_inactive_call self
|
102
|
+
end
|
103
|
+
|
104
|
+
def join(other_call_id)
|
105
|
+
write_and_await_response Punchblock::Command::Join.new :other_call_id => other_call_id
|
106
|
+
end
|
107
|
+
|
108
|
+
# Lock the socket for a command. Can be used to allow the console to take
|
109
|
+
# control of the thread in between AGI commands coming from the dialplan.
|
110
|
+
def with_command_lock
|
111
|
+
@command_monitor ||= Monitor.new
|
112
|
+
@command_monitor.synchronize { yield }
|
113
|
+
end
|
114
|
+
|
115
|
+
def write_and_await_response(command, timeout = 60)
|
116
|
+
logger.trace "Executing command #{command.inspect}"
|
117
|
+
commands << command
|
118
|
+
write_command command
|
119
|
+
response = command.response timeout
|
120
|
+
raise response if response.is_a? Exception
|
121
|
+
command
|
122
|
+
end
|
123
|
+
|
124
|
+
def write_command(command)
|
125
|
+
raise Hangup unless active? || command.is_a?(Punchblock::Command::Hangup)
|
126
|
+
client.execute_command command, :call_id => id
|
127
|
+
end
|
128
|
+
|
129
|
+
# Sanitize the offer id
|
130
|
+
def logger_id
|
131
|
+
"#{self.class}: #{id}"
|
132
|
+
end
|
133
|
+
|
134
|
+
def variables
|
135
|
+
offer ? offer.headers_hash : nil or {}
|
136
|
+
end
|
137
|
+
|
138
|
+
def execute_controller(controller, latch = nil)
|
139
|
+
Adhearsion::Process.important_threads << Thread.new do
|
140
|
+
catching_standard_errors do
|
141
|
+
begin
|
142
|
+
CallController.exec controller
|
143
|
+
ensure
|
144
|
+
hangup!
|
145
|
+
end
|
146
|
+
latch.countdown! if latch
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
class CommandRegistry < ThreadSafeArray
|
152
|
+
def terminate
|
153
|
+
hangup = Hangup.new
|
154
|
+
each { |command| command.response = hangup if command.requested? }
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
class Registry
|
159
|
+
@registry = Hash.new
|
160
|
+
@mutex = Mutex.new
|
161
|
+
|
162
|
+
def self.[](k)
|
163
|
+
@mutex.synchronize do
|
164
|
+
@registry[k]
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def self.[]=(k, value)
|
169
|
+
@mutex.synchronize do
|
170
|
+
@registry[k] = value
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end#Registry
|
174
|
+
|
175
|
+
end#Call
|
176
|
+
end#Adhearsion
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module Adhearsion
|
2
|
+
class CallController
|
3
|
+
extend ActiveSupport::Autoload
|
4
|
+
|
5
|
+
autoload :Dial
|
6
|
+
autoload :Input
|
7
|
+
autoload :Output
|
8
|
+
autoload :Record
|
9
|
+
autoload :Menu
|
10
|
+
autoload :Utility
|
11
|
+
|
12
|
+
include Dial
|
13
|
+
include Input
|
14
|
+
include Output
|
15
|
+
include Record
|
16
|
+
include Menu
|
17
|
+
include Utility
|
18
|
+
|
19
|
+
class_attribute :callbacks
|
20
|
+
|
21
|
+
self.callbacks = {:before_call => [], :after_call => []}
|
22
|
+
|
23
|
+
self.callbacks.keys.each do |name|
|
24
|
+
class_eval <<-STOP
|
25
|
+
def self.#{name}(method_name = nil, &block)
|
26
|
+
callback = if method_name
|
27
|
+
lambda { send method_name }
|
28
|
+
elsif block
|
29
|
+
block
|
30
|
+
end
|
31
|
+
self.callbacks = self.callbacks.dup.tap { |cb| cb[:#{name}] += Array(callback) }
|
32
|
+
end
|
33
|
+
STOP
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.exec(controller, fresh_call = true)
|
37
|
+
return unless controller
|
38
|
+
|
39
|
+
new_controller = catch :pass_controller do
|
40
|
+
controller.skip_accept! unless fresh_call
|
41
|
+
controller.execute!
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
exec new_controller, false
|
46
|
+
end
|
47
|
+
|
48
|
+
attr_reader :call, :metadata
|
49
|
+
|
50
|
+
delegate :[], :[]=, :to => :@metadata
|
51
|
+
delegate :variables, :logger, :to => :call
|
52
|
+
delegate :write_and_await_response, :accept, :answer, :reject, :to => :call
|
53
|
+
|
54
|
+
def initialize(call, metadata = nil)
|
55
|
+
@call, @metadata = call, metadata || {}
|
56
|
+
setup
|
57
|
+
end
|
58
|
+
|
59
|
+
def setup
|
60
|
+
Plugin.add_dialplan_methods self if Plugin
|
61
|
+
end
|
62
|
+
|
63
|
+
def execute!(*options)
|
64
|
+
execute_callbacks :before_call
|
65
|
+
accept if auto_accept?
|
66
|
+
run
|
67
|
+
rescue Hangup
|
68
|
+
logger.info "Call was hung up"
|
69
|
+
rescue SyntaxError, StandardError => e
|
70
|
+
Events.trigger :exception, e
|
71
|
+
ensure
|
72
|
+
after_call
|
73
|
+
end
|
74
|
+
|
75
|
+
def run
|
76
|
+
end
|
77
|
+
|
78
|
+
def invoke(controller_class, metadata = nil)
|
79
|
+
controller = controller_class.new call, metadata
|
80
|
+
controller.run
|
81
|
+
end
|
82
|
+
|
83
|
+
def pass(controller_class, metadata = nil)
|
84
|
+
throw :pass_controller, controller_class.new(call, metadata)
|
85
|
+
end
|
86
|
+
|
87
|
+
def execute_callbacks(type)
|
88
|
+
self.class.callbacks[type].each do |callback|
|
89
|
+
catching_standard_errors do
|
90
|
+
instance_exec &callback
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def skip_accept!
|
96
|
+
@skip_accept = true
|
97
|
+
end
|
98
|
+
|
99
|
+
def skip_accept?
|
100
|
+
@skip_accept || false
|
101
|
+
end
|
102
|
+
|
103
|
+
def auto_accept?
|
104
|
+
Adhearsion.config.platform.automatically_accept_incoming_calls && !skip_accept?
|
105
|
+
end
|
106
|
+
|
107
|
+
def after_call
|
108
|
+
@after_call ||= execute_callbacks :after_call
|
109
|
+
end
|
110
|
+
|
111
|
+
def hangup(headers = nil)
|
112
|
+
hangup_response = call.hangup! headers
|
113
|
+
after_call unless hangup_response == false
|
114
|
+
end
|
115
|
+
|
116
|
+
def mute
|
117
|
+
write_and_await_response ::Punchblock::Command::Mute.new
|
118
|
+
end
|
119
|
+
|
120
|
+
def unmute
|
121
|
+
write_and_await_response ::Punchblock::Command::Unmute.new
|
122
|
+
end
|
123
|
+
|
124
|
+
def execute_component_and_await_completion(component)
|
125
|
+
write_and_await_response component
|
126
|
+
|
127
|
+
yield component if block_given?
|
128
|
+
|
129
|
+
complete_event = component.complete_event
|
130
|
+
raise StandardError, complete_event.reason.details if complete_event.reason.is_a? Punchblock::Event::Complete::Error
|
131
|
+
component
|
132
|
+
end
|
133
|
+
end#class
|
134
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Adhearsion
|
2
|
+
class CallController
|
3
|
+
module Dial
|
4
|
+
#
|
5
|
+
# Dial a third party and join to this call
|
6
|
+
#
|
7
|
+
# @param [String|Array<String>] number represents the extension or "number" that asterisk should dial.
|
8
|
+
# Be careful to not just specify a number like 5001, 9095551001
|
9
|
+
# You must specify a properly formatted string as Asterisk would expect to use in order to understand
|
10
|
+
# whether the call should be dialed using SIP, IAX, or some other means.
|
11
|
+
# You can also specify an array of destinations: each will be called with the same options simultaneously.
|
12
|
+
# The first call answered is joined, the others are hung up.
|
13
|
+
#
|
14
|
+
# @param [Hash] options
|
15
|
+
#
|
16
|
+
# +:caller_id+ - the caller id number to be used when the call is placed. It is advised you properly adhere to the
|
17
|
+
# policy of VoIP termination providers with respect to caller id values.
|
18
|
+
#
|
19
|
+
# +:name+ - this is the name which should be passed with the caller ID information
|
20
|
+
# if :name=>"John Doe" and :caller_id => "444-333-1000" then the compelete CID and name would be "John Doe" <4443331000>
|
21
|
+
# support for caller id information varies from country to country and from one VoIP termination provider to another.
|
22
|
+
#
|
23
|
+
# +:for+ - this option can be thought of best as a timeout. i.e. timeout after :for if no one answers the call
|
24
|
+
# For example, dial("SIP/jay-desk-650&SIP/jay-desk-601&SIP/jay-desk-601-2", :for => 15.seconds, :caller_id => callerid)
|
25
|
+
# this call will timeout after 15 seconds if 1 of the 3 extensions being dialed do not pick prior to the 15 second time limit
|
26
|
+
#
|
27
|
+
# +:options+ - This is a string of options like "Tr" which are supported by the asterisk DIAL application.
|
28
|
+
# for a complete list of these options and their usage please check the link below.
|
29
|
+
#
|
30
|
+
# +:confirm+ - ?
|
31
|
+
#
|
32
|
+
# @example Make a call to the PSTN using my SIP provider for VoIP termination
|
33
|
+
# dial "SIP/19095551001@my.sip.voip.terminator.us"
|
34
|
+
#
|
35
|
+
# @example Make 3 Simulataneous calls to the SIP extensions, try for 15 seconds and use the callerid
|
36
|
+
# for this call specified by the variable my_callerid
|
37
|
+
# dial ["SIP/jay-desk-650", "SIP/jay-desk-601", "SIP/jay-desk-601-2"], :for => 15.seconds, :caller_id => my_callerid
|
38
|
+
#
|
39
|
+
# @example Make a call using the IAX provider to the PSTN
|
40
|
+
# dial "IAX2/my.id@voipjet/19095551234", :name => "John Doe", :caller_id => "9095551234"
|
41
|
+
#
|
42
|
+
def dial(to, options = {})
|
43
|
+
latch = CountDownLatch.new 1
|
44
|
+
calls = Array(to).map do |target|
|
45
|
+
new_call = OutboundCall.new options
|
46
|
+
|
47
|
+
new_call.on_answer do |event|
|
48
|
+
calls.each do |call_to_hangup, target|
|
49
|
+
call_to_hangup.hangup! unless call_to_hangup.id == new_call.id
|
50
|
+
end
|
51
|
+
new_call.join call.id
|
52
|
+
end
|
53
|
+
|
54
|
+
new_call.on_end do |event|
|
55
|
+
latch.countdown!
|
56
|
+
end
|
57
|
+
|
58
|
+
[new_call, target]
|
59
|
+
end
|
60
|
+
|
61
|
+
calls.each do |call, target|
|
62
|
+
call.dial target, options
|
63
|
+
end
|
64
|
+
|
65
|
+
latch.wait
|
66
|
+
end
|
67
|
+
|
68
|
+
end#module Dial
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
module Adhearsion
|
2
|
+
class CallController
|
3
|
+
module Input
|
4
|
+
#
|
5
|
+
# Waits for a single digit and returns it, or returns nil if nothing was pressed
|
6
|
+
#
|
7
|
+
# @param [Integer] the timeout to wait before returning, in seconds. nil or -1 mean no timeout.
|
8
|
+
# @return [String|nil] the pressed key, or nil if timeout was reached.
|
9
|
+
#
|
10
|
+
def wait_for_digit(timeout = 1)
|
11
|
+
timeout = nil if timeout == -1
|
12
|
+
timeout *= 1_000 if timeout
|
13
|
+
input_component = execute_component_and_await_completion ::Punchblock::Component::Input.new :mode => :dtmf,
|
14
|
+
:initial_timeout => timeout,
|
15
|
+
:inter_digit_timeout => timeout,
|
16
|
+
:grammar => {
|
17
|
+
:value => grammar_accept.to_s
|
18
|
+
}
|
19
|
+
|
20
|
+
reason = input_component.complete_event.reason
|
21
|
+
result = reason.respond_to?(:interpretation) ? reason.interpretation : nil
|
22
|
+
parse_single_dtmf result
|
23
|
+
end
|
24
|
+
|
25
|
+
# Used to receive keypad input from the user. Digits are collected
|
26
|
+
# via DTMF (keypad) input until one of three things happens:
|
27
|
+
#
|
28
|
+
# 1. The number of digits you specify as the first argument is collected
|
29
|
+
# 2. The timeout you specify with the :timeout option elapses, in seconds.
|
30
|
+
# 3. The "#" key (or the key you specify with :accept_key) is pressed
|
31
|
+
#
|
32
|
+
# Usage examples
|
33
|
+
#
|
34
|
+
# input # Receives digits until the caller presses the "#" key
|
35
|
+
# input 3 # Receives three digits. Can be 0-9, * or #
|
36
|
+
# input 5, :accept_key => "*" # Receive at most 5 digits, stopping if '*' is pressed
|
37
|
+
# input 1, :timeout => 60000 # Receive a single digit, returning an empty
|
38
|
+
# string if the timeout is encountered
|
39
|
+
# input 9, :timeout => 7000, :accept_key => "0" # Receives nine digits, returning
|
40
|
+
# # when the timeout is encountered
|
41
|
+
# # or when the "0" key is pressed.
|
42
|
+
# input 3, :play => "you-sound-cute"
|
43
|
+
# input :play => ["if-this-is-correct-press", 1, "otherwise-press", 2]
|
44
|
+
# input :interruptible => false, :play => ["you-cannot-interrupt-this-message"] # Disallow DTMF (keypad) interruption
|
45
|
+
# # until after all files are played.
|
46
|
+
#
|
47
|
+
# When specifying outputs to play, the playback of the sequence of files will stop
|
48
|
+
# immediately when the user presses the first digit.
|
49
|
+
#
|
50
|
+
# Accepted output types are:
|
51
|
+
# 1. Any object supported by detect_type (@see detect_type)
|
52
|
+
# 2. Any valid SSML document
|
53
|
+
# 3. An Hash with at least the :value key set to a supported object type, and other keys as options to the specific output
|
54
|
+
#
|
55
|
+
# :play usage examples
|
56
|
+
# input 1, :play => RubySpeech::SSML.draw { string "hello there" } # 1 digit, SSML document
|
57
|
+
# input 2, :play => "hello there" # 2 digits, string
|
58
|
+
# input 2, :play => {:value => Time.now, :strftime => "%H:%M"} # 2 digits, Hash with :value
|
59
|
+
# input :play => [ "the time is", {:value => Time.now, :strftime => "%H:%M"} ] # no digit limit, two mixed outputs
|
60
|
+
#
|
61
|
+
# The :timeout option works like a digit timeout, therefore each digit pressed
|
62
|
+
# causes the timer to reset. This is a much more user-friendly approach than an
|
63
|
+
# absolute timeout.
|
64
|
+
#
|
65
|
+
# Note that when the digit limit is not specified the :accept_key becomes "#".
|
66
|
+
# Otherwise there would be no way to end the collection of digits. You can
|
67
|
+
# obviously override this by passing in a new key with :accept_key.
|
68
|
+
#
|
69
|
+
# @return [String] The keypad input received. An empty string is returned in the
|
70
|
+
# absense of input. If the :accept_key argument was pressed, it
|
71
|
+
# will not appear in the output.
|
72
|
+
def input(*args, &block)
|
73
|
+
begin
|
74
|
+
input! *args, &block
|
75
|
+
rescue PlaybackError => e
|
76
|
+
logger.warn { e }
|
77
|
+
retry # If sound playback fails, play the remaining sound files and wait for digits
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Same as {#input}, but immediately raises an exception if sound playback fails
|
82
|
+
#
|
83
|
+
# @return (see #input)
|
84
|
+
# @raise [Adhearsion::PlaybackError] If a sound file cannot be played
|
85
|
+
def input!(*args, &block)
|
86
|
+
options = args.last.kind_of?(Hash) ? args.pop : {}
|
87
|
+
number_of_digits = args.shift
|
88
|
+
|
89
|
+
options[:play] = Array(case options[:play]
|
90
|
+
when String
|
91
|
+
options[:play]
|
92
|
+
when Array
|
93
|
+
options[:play].compact
|
94
|
+
when NilClass
|
95
|
+
[]
|
96
|
+
else
|
97
|
+
[options[:play]]
|
98
|
+
end)
|
99
|
+
|
100
|
+
play_command = if options.has_key?(:interruptible) && options[:interruptible] == false
|
101
|
+
:play!
|
102
|
+
else
|
103
|
+
options[:interruptible] = true
|
104
|
+
:interruptible_play!
|
105
|
+
end
|
106
|
+
|
107
|
+
if options.has_key? :speak
|
108
|
+
raise ArgumentError, ':speak must be a Hash' unless options[:speak].is_a? Hash
|
109
|
+
raise ArgumentError, 'Must include a text string when requesting TTS fallback' unless options[:speak].has_key?(:text)
|
110
|
+
if options.has_key?(:speak) && options.has_key?(:play) && options[:play].size > 0
|
111
|
+
raise ArgumentError, 'Must specify only one of :play or :speak'
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
timeout = options[:timeout]
|
116
|
+
terminator = options[:terminator]
|
117
|
+
|
118
|
+
terminator = if terminator
|
119
|
+
terminator.to_s
|
120
|
+
elsif number_of_digits.nil? && !terminator.equal?(false)
|
121
|
+
'#'
|
122
|
+
end
|
123
|
+
|
124
|
+
if number_of_digits && number_of_digits < 0
|
125
|
+
logger.warn "Giving -1 to #input is now deprecated. Do not specify a first " +
|
126
|
+
"argument to allow unlimited digits." if number_of_digits == -1
|
127
|
+
raise ArgumentError, "The number of digits must be positive!"
|
128
|
+
end
|
129
|
+
|
130
|
+
buffer = ''
|
131
|
+
if options[:play].any?
|
132
|
+
# Consume the sound files one at a time. In the event of playback
|
133
|
+
# failure, this tells us which files remain unplayed.
|
134
|
+
while output = options[:play].shift
|
135
|
+
if output.class == Hash
|
136
|
+
argument = output.delete(:value)
|
137
|
+
raise ArgumentError, ':value has to be specified for each :play argument that is a Hash' if argument.nil?
|
138
|
+
output = [argument, output]
|
139
|
+
end
|
140
|
+
key = send play_command, output
|
141
|
+
key = nil if play_command == :play!
|
142
|
+
break if key
|
143
|
+
end
|
144
|
+
key ||= ''
|
145
|
+
# instead use a normal play command, :speak is basically an alias
|
146
|
+
elsif options[:speak]
|
147
|
+
speak_output = options[:speak].delete(:text)
|
148
|
+
key = send play_command, speak_output, options[:speak]
|
149
|
+
key = nil if play_command == :play!
|
150
|
+
else
|
151
|
+
key = wait_for_digit timeout
|
152
|
+
end
|
153
|
+
|
154
|
+
loop do
|
155
|
+
return buffer if key.nil?
|
156
|
+
if terminator
|
157
|
+
if key == terminator
|
158
|
+
return buffer
|
159
|
+
else
|
160
|
+
buffer << key
|
161
|
+
return buffer if number_of_digits && number_of_digits == buffer.length
|
162
|
+
end
|
163
|
+
else
|
164
|
+
buffer << key
|
165
|
+
return buffer if number_of_digits && number_of_digits == buffer.length
|
166
|
+
end
|
167
|
+
return buffer if block_given? && yield(buffer)
|
168
|
+
key = wait_for_digit timeout
|
169
|
+
end
|
170
|
+
end # #input!
|
171
|
+
end # Input
|
172
|
+
end
|
173
|
+
end
|