adhearsion 1.2.6 → 2.0.0.alpha1
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.
- 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
|