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
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
require File.join(File.dirname(__FILE__), 'config_generator')
|
|
2
|
-
|
|
3
|
-
module Adhearsion
|
|
4
|
-
module VoIP
|
|
5
|
-
module Asterisk
|
|
6
|
-
module ConfigFileGenerators
|
|
7
|
-
class Voicemail < AsteriskConfigGenerator
|
|
8
|
-
|
|
9
|
-
DEFAULT_GENERAL_SECTION = {
|
|
10
|
-
:format => :wav
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
# Don't worry. These will be overridable soon.
|
|
14
|
-
STATIC_ZONEMESSAGES_CONTEXT = %{
|
|
15
|
-
[zonemessages]
|
|
16
|
-
eastern=America/New_York|'vm-received' Q 'digits/at' IMp
|
|
17
|
-
central=America/Chicago|'vm-received' Q 'digits/at' IMp
|
|
18
|
-
central24=America/Chicago|'vm-received' q 'digits/at' H N 'hours'
|
|
19
|
-
military=Zulu|'vm-received' q 'digits/at' H N 'hours' 'phonetic/z_p'
|
|
20
|
-
european=Europe/Copenhagen|'vm-received' a d b 'digits/at' HM
|
|
21
|
-
}.unindent
|
|
22
|
-
|
|
23
|
-
attr_reader :properties, :context_definitions
|
|
24
|
-
def initialize
|
|
25
|
-
@properties = DEFAULT_GENERAL_SECTION.clone
|
|
26
|
-
@mailboxes = {}
|
|
27
|
-
@context_definitions = []
|
|
28
|
-
super
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def context(name)
|
|
32
|
-
raise ArgumentError, "Name cannot be 'general'!" if name.to_s.downcase == 'general'
|
|
33
|
-
raise ArgumentError, "A name can only be characters, numbers, and underscores!" if name.to_s !~ /^[\w_]+$/
|
|
34
|
-
|
|
35
|
-
ContextDefinition.new(name).tap do |context_definition|
|
|
36
|
-
yield context_definition
|
|
37
|
-
context_definitions << context_definition
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def greeting_maximum(seconds)
|
|
42
|
-
int "maxgreet" => seconds
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def execute_on_pin_change(command)
|
|
46
|
-
string "externpass" => command
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def recordings
|
|
50
|
-
@recordings ||= RecordingDefinition.new
|
|
51
|
-
yield @recordings if block_given?
|
|
52
|
-
@recordings
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def emails
|
|
56
|
-
@emails ||= EmailDefinition.new
|
|
57
|
-
if block_given?
|
|
58
|
-
yield @emails
|
|
59
|
-
else
|
|
60
|
-
@emails
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def to_s
|
|
65
|
-
email_properties = @emails ? @emails.properties : {}
|
|
66
|
-
AsteriskConfigGenerator.warning_message +
|
|
67
|
-
"[general]\n" +
|
|
68
|
-
properties.merge(email_properties).map { |(key,value)| "#{key}=#{value}" }.sort.join("\n") + "\n\n" +
|
|
69
|
-
STATIC_ZONEMESSAGES_CONTEXT +
|
|
70
|
-
context_definitions.map(&:to_s).join("\n\n")
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
private
|
|
74
|
-
|
|
75
|
-
class ContextDefinition < AsteriskConfigGenerator
|
|
76
|
-
|
|
77
|
-
attr_reader :mailboxes
|
|
78
|
-
def initialize(name)
|
|
79
|
-
@name = name
|
|
80
|
-
@mailboxes = []
|
|
81
|
-
super()
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# TODO: This will hold a lot of the methods from the [general] section!
|
|
85
|
-
|
|
86
|
-
def to_s
|
|
87
|
-
(%W[[#@name]] + mailboxes.map(&:to_s)).join "\n"
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def mailbox(mailbox_number)
|
|
91
|
-
box = MailboxDefinition.new(mailbox_number)
|
|
92
|
-
yield box
|
|
93
|
-
mailboxes << box
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
private
|
|
97
|
-
|
|
98
|
-
def mailbox_entry(options)
|
|
99
|
-
MailboxDefinition.new.tap do |mailbox|
|
|
100
|
-
yield mailbox if block_given?
|
|
101
|
-
mailboxes << definition
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
class MailboxDefinition
|
|
106
|
-
|
|
107
|
-
attr_reader :mailbox_number
|
|
108
|
-
def initialize(mailbox_number)
|
|
109
|
-
check_numeric mailbox_number
|
|
110
|
-
@mailbox_number = mailbox_number
|
|
111
|
-
@definition = {}
|
|
112
|
-
super()
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def pin_number(number)
|
|
116
|
-
check_numeric number
|
|
117
|
-
@definition[:pin_number] = number
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
def name(str)
|
|
121
|
-
@definition[:name] = str
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def email(str)
|
|
125
|
-
@definition[:email] = str
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
def to_hash
|
|
129
|
-
@definition
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
def to_s
|
|
133
|
-
%(#{mailbox_number} => #{@definition[:pin_number]},#{@definition[:name]},#{@definition[:email]})[/^(.+?),*$/,1]
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
private
|
|
137
|
-
|
|
138
|
-
def check_numeric(number)
|
|
139
|
-
raise ArgumentError, number.inspect + " is not numeric!" unless number.to_s =~ /^\d+$/
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
class EmailDefinition < AsteriskConfigGenerator
|
|
146
|
-
EMAIL_VARIABLE_CONVENIENCES = {
|
|
147
|
-
:name => '${VM_NAME}',
|
|
148
|
-
:duration => '${VM_DUR}',
|
|
149
|
-
:message_number => '${VM_MSGNUM}',
|
|
150
|
-
:mailbox => '${VM_MAILBOX}',
|
|
151
|
-
:caller_id => '${VM_CALLERID}',
|
|
152
|
-
:date => '${VM_DATE}',
|
|
153
|
-
:caller_id_number => '${VM_CIDNUM}',
|
|
154
|
-
:caller_id_name => '${VM_CIDNAME}'
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
attr_reader :properties
|
|
158
|
-
def initialize
|
|
159
|
-
@properties = {}
|
|
160
|
-
super
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
def [](email_variable)
|
|
164
|
-
if EMAIL_VARIABLE_CONVENIENCES.has_key? email_variable
|
|
165
|
-
EMAIL_VARIABLE_CONVENIENCES[email_variable]
|
|
166
|
-
else
|
|
167
|
-
raise ArgumentError, "Unrecognized variable #{email_variable.inspect}"
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
def disable!
|
|
172
|
-
raise NotImpementedError
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
def from(options)
|
|
176
|
-
name, email = options.values_at :name, :email
|
|
177
|
-
string :serveremail => email
|
|
178
|
-
string :fromstring => name
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def attach_recordings(true_or_false)
|
|
182
|
-
boolean :attach => true_or_false
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
def attach_recordings?
|
|
186
|
-
properties[:attach] == 'yes'
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
def body(str)
|
|
190
|
-
str = str.gsub("\r", '').gsub("\n", '\n')
|
|
191
|
-
if str.length > 512
|
|
192
|
-
raise ArgumentError, "Asterisk has an email body limit of 512 characters! Your body is too long!\n" +
|
|
193
|
-
("-" * 10) + "\n" + str
|
|
194
|
-
end
|
|
195
|
-
string :emailbody => str
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
def subject(str)
|
|
199
|
-
string :emailsubject => str
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
def command(cmd)
|
|
203
|
-
string :mailcmd => cmd
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
class RecordingDefinition < AsteriskConfigGenerator
|
|
209
|
-
|
|
210
|
-
attr_reader :properties
|
|
211
|
-
def initialize
|
|
212
|
-
@properties = {}
|
|
213
|
-
super
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
def format(symbol)
|
|
217
|
-
one_of [:gsm, :wav49, :wav], :format => symbol
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
def allowed_length(seconds)
|
|
221
|
-
case seconds
|
|
222
|
-
when Fixnum
|
|
223
|
-
int :maxmessage => "value"
|
|
224
|
-
when Range
|
|
225
|
-
int :minmessage => seconds.first
|
|
226
|
-
int :maxmessage => seconds.last
|
|
227
|
-
else
|
|
228
|
-
raise ArgumentError, "Argument must be a Fixnum or Range!"
|
|
229
|
-
end
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
def maximum_silence(seconds)
|
|
233
|
-
int :maxsilence => seconds
|
|
234
|
-
end
|
|
235
|
-
end
|
|
236
|
-
end
|
|
237
|
-
end
|
|
238
|
-
end
|
|
239
|
-
end
|
|
240
|
-
end
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
require 'enumerator'
|
|
2
|
-
module Adhearsion
|
|
3
|
-
module VoIP
|
|
4
|
-
module Asterisk
|
|
5
|
-
class ConfigurationManager
|
|
6
|
-
|
|
7
|
-
class << self
|
|
8
|
-
def normalize_configuration(file_contents)
|
|
9
|
-
# cat sip.conf | sed -e 's/\s*;.*$//g' | sed -e '/^;.*$/d' | sed -e '/^\s*$/d'
|
|
10
|
-
file_contents.split(/\n+/).map do |line|
|
|
11
|
-
line.sub(/;.+$/, '').strip
|
|
12
|
-
end.join("\n").squeeze("\n")
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
attr_reader :filename
|
|
17
|
-
|
|
18
|
-
def initialize(filename)
|
|
19
|
-
@filename = filename
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def sections
|
|
23
|
-
@sections ||= read_configuration
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def [](section_name)
|
|
27
|
-
result = sections.find { |(name, *rest)| section_name == name }
|
|
28
|
-
result.last if result
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def delete_section(section_name)
|
|
32
|
-
sections.reject! { |(name, *rest)| section_name == name }
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def new_section(name, properties={})
|
|
36
|
-
sections << [name, properties]
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
private
|
|
40
|
-
|
|
41
|
-
def read_configuration
|
|
42
|
-
normalized_file = self.class.normalize_configuration File.open(@filename, 'r'){|f| f.read}
|
|
43
|
-
sections = normalized_file.split(/^\[([-_\w]+)\]$/)[1..-1]
|
|
44
|
-
return [] if sections.nil?
|
|
45
|
-
sections.each_slice(2).map do |(name,properties)|
|
|
46
|
-
[name, hash_from_properties(properties)]
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def hash_from_properties(properties)
|
|
51
|
-
properties.split(/\n+/).inject({}) do |property_hash,property|
|
|
52
|
-
all, name, value = *property.match(/^\s*([^=]+?)\s*=\s*(.+)\s*$/)
|
|
53
|
-
next property_hash unless name && value
|
|
54
|
-
property_hash[name] = value
|
|
55
|
-
property_hash
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# Read a file: cat a file
|
|
64
|
-
# Parse a file: separate into a two dimensional hash
|
|
@@ -1,697 +0,0 @@
|
|
|
1
|
-
require 'adhearsion/voip/asterisk/manager_interface/ami_lexer'
|
|
2
|
-
|
|
3
|
-
module Adhearsion
|
|
4
|
-
module VoIP
|
|
5
|
-
module Asterisk
|
|
6
|
-
|
|
7
|
-
##
|
|
8
|
-
# Sorry, this AMI class has been deprecated. Please see http://docs.adhearsion.com/Asterisk_Manager_Interface for
|
|
9
|
-
# documentation on the new way of handling AMI. This new version is much better and should not require an enormous
|
|
10
|
-
# migration on your part.
|
|
11
|
-
#
|
|
12
|
-
class AMI
|
|
13
|
-
def initialize
|
|
14
|
-
raise "Sorry, this AMI class has been deprecated. Please see http://docs.adhearsion.com/display/adhearsion/Asterisk+Manager+Interface for documentation on the new way of handling AMI. This new version is much better and should not require an enormous migration on your part."
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
mattr_accessor :manager_interface
|
|
19
|
-
|
|
20
|
-
module Manager
|
|
21
|
-
|
|
22
|
-
##
|
|
23
|
-
# This class abstracts a connection to the Asterisk Manager Interface. Its purpose is, first and foremost, to make
|
|
24
|
-
# the protocol consistent. Though the classes employed to assist this class (ManagerInterfaceAction,
|
|
25
|
-
# ManagerInterfaceResponse, ManagerInterfaceError, etc.) are relatively user-friendly, they're designed to be a
|
|
26
|
-
# building block on which to build higher-level abstractions of the Asterisk Manager Interface.
|
|
27
|
-
#
|
|
28
|
-
class ManagerInterface
|
|
29
|
-
|
|
30
|
-
CAUSAL_EVENT_NAMES = %w[queuestatus sippeers iaxpeers parkedcalls
|
|
31
|
-
dahdishowchannels coreshowchannels dbget
|
|
32
|
-
status agents konferencelist] unless defined? CAUSAL_EVENT_NAMES
|
|
33
|
-
|
|
34
|
-
RETRY_SLEEP = 5
|
|
35
|
-
|
|
36
|
-
class << self
|
|
37
|
-
|
|
38
|
-
def connect(*args)
|
|
39
|
-
new(*args).tap do |connection|
|
|
40
|
-
connection.connect!
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def replies_with_action_id?(name, headers={})
|
|
45
|
-
name = name.to_s.downcase
|
|
46
|
-
!UnsupportedActionName::UNSUPPORTED_ACTION_NAMES.include? name
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
##
|
|
50
|
-
# When sending an action with "causal events" (i.e. events which must be collected to form a proper
|
|
51
|
-
# response), AMI should send a particular event which instructs us that no more events will be sent.
|
|
52
|
-
# This event is called the "causal event terminator".
|
|
53
|
-
#
|
|
54
|
-
# Note: you must supply both the name of the event and any headers because it's possible that some uses of an
|
|
55
|
-
# action (i.e. same name, different headers) have causal events while other uses don't.
|
|
56
|
-
#
|
|
57
|
-
# @param [String] name the name of the event
|
|
58
|
-
# @param [Hash] the headers associated with this event
|
|
59
|
-
# @return [String] the downcase()'d name of the event name for which to wait
|
|
60
|
-
#
|
|
61
|
-
def has_causal_events?(name, headers={})
|
|
62
|
-
CAUSAL_EVENT_NAMES.include? name.to_s.downcase
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
##
|
|
66
|
-
# Used to determine the event name for an action which has causal events.
|
|
67
|
-
#
|
|
68
|
-
# @param [String] action_name
|
|
69
|
-
# @return [String] The corresponding event name which signals the completion of the causal event sequence.
|
|
70
|
-
#
|
|
71
|
-
def causal_event_terminator_name_for(action_name)
|
|
72
|
-
return nil unless has_causal_events?(action_name)
|
|
73
|
-
action_name = action_name.to_s.downcase
|
|
74
|
-
case action_name
|
|
75
|
-
when "sippeers", "iaxpeers"
|
|
76
|
-
"peerlistcomplete"
|
|
77
|
-
when "dbget"
|
|
78
|
-
"dbgetresponse"
|
|
79
|
-
when "konferencelist"
|
|
80
|
-
"conferencelistcomplete"
|
|
81
|
-
else
|
|
82
|
-
action_name + "complete"
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
DEFAULT_SETTINGS = {
|
|
89
|
-
:host => "localhost",
|
|
90
|
-
:port => 5038,
|
|
91
|
-
:username => "admin",
|
|
92
|
-
:password => "secret",
|
|
93
|
-
:events => true,
|
|
94
|
-
:auto_reconnect => true,
|
|
95
|
-
:event_callback => proc { |event| Events.trigger(%w[asterisk manager_interface], event) }
|
|
96
|
-
}.freeze unless defined? DEFAULT_SETTINGS
|
|
97
|
-
|
|
98
|
-
attr_reader *DEFAULT_SETTINGS.keys
|
|
99
|
-
|
|
100
|
-
##
|
|
101
|
-
# Creates a new Asterisk Manager Interface connection and exposes certain methods to control it. The constructor
|
|
102
|
-
# takes named parameters as Symbols. Note: if the :events option is given, this library will establish a separate
|
|
103
|
-
# socket for just events. Two sockets are used because some actions actually respond with events, making it very
|
|
104
|
-
# complicated to differentiate between response-type events and normal events.
|
|
105
|
-
#
|
|
106
|
-
# @param [Hash] options Available options are :host, :port, :username, :password, and :events
|
|
107
|
-
#
|
|
108
|
-
def initialize(options={})
|
|
109
|
-
options = parse_options options
|
|
110
|
-
@host = options[:host]
|
|
111
|
-
@username = options[:username]
|
|
112
|
-
@password = options[:password]
|
|
113
|
-
@port = options[:port]
|
|
114
|
-
@events = options[:events]
|
|
115
|
-
@auto_reconnect = options[:auto_reconnect]
|
|
116
|
-
@event_callback = options[:event_callback]
|
|
117
|
-
|
|
118
|
-
@sent_messages = {}
|
|
119
|
-
@sent_messages_lock = Mutex.new
|
|
120
|
-
|
|
121
|
-
@actions_lexer = DelegatingAsteriskManagerInterfaceLexer.new self, \
|
|
122
|
-
:message_received => :action_message_received,
|
|
123
|
-
:error_received => :action_error_received
|
|
124
|
-
|
|
125
|
-
@write_queue = Queue.new
|
|
126
|
-
|
|
127
|
-
if @events
|
|
128
|
-
@events_lexer = DelegatingAsteriskManagerInterfaceLexer.new self, \
|
|
129
|
-
:message_received => :event_message_received,
|
|
130
|
-
:error_received => :event_error_received
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def action_message_received(message)
|
|
135
|
-
if message.kind_of? Manager::ManagerInterfaceEvent
|
|
136
|
-
# Trigger the return value of the waiting action id...
|
|
137
|
-
corresponding_action = @current_action_with_causal_events
|
|
138
|
-
event_collection = @event_collection_for_current_action
|
|
139
|
-
|
|
140
|
-
if corresponding_action
|
|
141
|
-
|
|
142
|
-
# The "DBGet" command is causal, meaning it has an separate
|
|
143
|
-
# event that contains the data for command's response. However,
|
|
144
|
-
# unlike other causal commands, AMI does not send a
|
|
145
|
-
# "DBGetComplete" action indicating the causal event is
|
|
146
|
-
# finished. This is fixed starting in Asterisk 1.8.
|
|
147
|
-
if message.name.downcase == "dbgetresponse"
|
|
148
|
-
event_collection << message
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
# If this is the meta-event which signals no more events will follow and the response is complete.
|
|
152
|
-
if message.name.downcase == corresponding_action.causal_event_terminator_name
|
|
153
|
-
# Wake up the waiting Thread
|
|
154
|
-
corresponding_action.future_resource.resource = event_collection.freeze
|
|
155
|
-
|
|
156
|
-
# Clear the stored action and event collection
|
|
157
|
-
@current_action_with_causal_events = nil
|
|
158
|
-
@event_collection_for_current_action = nil
|
|
159
|
-
else
|
|
160
|
-
event_collection << message
|
|
161
|
-
# We have more causal events coming.
|
|
162
|
-
end
|
|
163
|
-
else
|
|
164
|
-
ahn_log.ami.error "Got an unexpected event on actions socket! This AMI command may have a multi-message response. Try making Adhearsion treat it as CAUSAL_EVENT #{message.inspect}"
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
elsif message["ActionID"].nil?
|
|
168
|
-
# No ActionID! Release the write lock and wake up the waiter
|
|
169
|
-
else
|
|
170
|
-
action_id = message["ActionID"]
|
|
171
|
-
corresponding_action = data_for_message_received_with_action_id action_id
|
|
172
|
-
if corresponding_action
|
|
173
|
-
message.action = corresponding_action
|
|
174
|
-
|
|
175
|
-
if corresponding_action.has_causal_events?
|
|
176
|
-
# By this point the write loop will already have started blocking by calling the response() method on the
|
|
177
|
-
# action. Because we must collect more events before we wake the write loop up again, let's create these
|
|
178
|
-
# instance variable which will needed when the subsequent causal events come in.
|
|
179
|
-
@current_action_with_causal_events = corresponding_action
|
|
180
|
-
@event_collection_for_current_action = []
|
|
181
|
-
else
|
|
182
|
-
# Wake any Threads waiting on the response.
|
|
183
|
-
corresponding_action.future_resource.resource = message
|
|
184
|
-
end
|
|
185
|
-
else
|
|
186
|
-
ahn_log.ami.error "Received an AMI message with an unrecognized ActionID!! This may be an bug! #{message.inspect}"
|
|
187
|
-
end
|
|
188
|
-
end
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def action_error_received(ami_error)
|
|
192
|
-
action_id = ami_error["ActionID"]
|
|
193
|
-
|
|
194
|
-
corresponding_action = data_for_message_received_with_action_id action_id
|
|
195
|
-
|
|
196
|
-
if corresponding_action
|
|
197
|
-
corresponding_action.future_resource.resource = ami_error
|
|
198
|
-
else
|
|
199
|
-
ahn_log.ami.error "Received an AMI error with an unrecognized ActionID!! This may be an bug! #{ami_error.inspect}"
|
|
200
|
-
end
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
##
|
|
204
|
-
# Called only when this ManagerInterface is instantiated with events enabled.
|
|
205
|
-
#
|
|
206
|
-
def event_message_received(event)
|
|
207
|
-
return if event.kind_of?(ManagerInterfaceResponse) && event["Message"] == "Authentication accepted"
|
|
208
|
-
# TODO: convert the event name to a certain namespace.
|
|
209
|
-
@event_callback.call(event)
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
def event_error_received(message)
|
|
213
|
-
# Does this ever even occur?
|
|
214
|
-
ahn_log.ami.error "Hmmm, got an error on the AMI events-only socket! This must be a bug! #{message.inspect}"
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
##
|
|
218
|
-
# Called when our Ragel parser encounters some unexpected syntax from Asterisk. Anytime this is called, it should
|
|
219
|
-
# be considered a bug in Adhearsion. Note: this same method is called regardless of whether the syntax error
|
|
220
|
-
# happened on the actions socket or on the events socket.
|
|
221
|
-
#
|
|
222
|
-
def syntax_error_encountered(ignored_chunk)
|
|
223
|
-
ahn_log.ami.error "ADHEARSION'S AMI PARSER ENCOUNTERED A SYNTAX ERROR! " +
|
|
224
|
-
"PLEASE REPORT THIS ON http://bugs.adhearsion.com! OFFENDING TEXT:\n#{ignored_chunk.inspect}"
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
##
|
|
228
|
-
# Must be called after instantiation. Also see ManagerInterface::connect().
|
|
229
|
-
#
|
|
230
|
-
# @raise [AuthenticationFailedException] if username or password are rejected
|
|
231
|
-
#
|
|
232
|
-
def connect!
|
|
233
|
-
establish_actions_connection
|
|
234
|
-
establish_events_connection if @events
|
|
235
|
-
self
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
def actions_connection_established
|
|
239
|
-
@actions_state = :connected
|
|
240
|
-
start_actions_writer_loop
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
def actions_connection_disconnected
|
|
244
|
-
@actions_state = :disconnected
|
|
245
|
-
ahn_log.ami.error "AMI connection for ACTION disconnected !!!"
|
|
246
|
-
clear_actions_connection
|
|
247
|
-
establish_actions_connection if @auto_reconnect
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
def events_connection_established
|
|
251
|
-
@events_state = :connected
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
def events_connection_disconnected
|
|
255
|
-
@events_state = :disconnected
|
|
256
|
-
ahn_log.ami.error "AMI connection for EVENT disconnected !!!"
|
|
257
|
-
clear_events_connection
|
|
258
|
-
establish_events_connection if @auto_reconnect
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
def clear_actions_connection
|
|
262
|
-
stop_actions_writer_loop
|
|
263
|
-
clear_actions_connection_resources
|
|
264
|
-
disconnect_actions_connection if @actions_state.equal? :connected
|
|
265
|
-
end
|
|
266
|
-
|
|
267
|
-
def clear_events_connection
|
|
268
|
-
disconnect_events_connection if @events_state.equal? :connected
|
|
269
|
-
end
|
|
270
|
-
|
|
271
|
-
def disconnect!
|
|
272
|
-
clear_actions_connection
|
|
273
|
-
clear_events_connection
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
def dynamic
|
|
277
|
-
# TODO: Return an object which responds to method_missing
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
##
|
|
281
|
-
# Used to directly send a new action to Asterisk. Note: NEVER supply an ActionID; these are handled internally.
|
|
282
|
-
#
|
|
283
|
-
# @param [String, Symbol] action_name The name of the action (e.g. Originate)
|
|
284
|
-
# @param [Hash] headers Other key/value pairs to send in this action. Note: don't provide an ActionID
|
|
285
|
-
# @return [FutureResource] Call resource() on this object if you wish to access the response (optional). Note: if the response has not come in yet, your Thread will wait until it does.
|
|
286
|
-
# @deprecated Async AMI is deprecated
|
|
287
|
-
#
|
|
288
|
-
def send_action_asynchronously(action_name, headers={})
|
|
289
|
-
ahn_log.ami.deprecation.warn <<WARN
|
|
290
|
-
\n
|
|
291
|
-
Asterisk only supports one outstanding action ID at a time, making the
|
|
292
|
-
asynchronous AMI interface dangerous to use. This interface is now
|
|
293
|
-
deprecated and will disappear in future versions of Adhearsion. If you need
|
|
294
|
-
to do background processing while Asterisk processes a long-running action
|
|
295
|
-
(such as a synchronous Originate) then you will need to handle this manually
|
|
296
|
-
with Ruby threads. Note that Originate specifically has an :Async header
|
|
297
|
-
which tells Asterisk to process the Originate event asynchronously.
|
|
298
|
-
WARN
|
|
299
|
-
_send_action_asynchronously(action_name, headers)
|
|
300
|
-
end
|
|
301
|
-
|
|
302
|
-
##
|
|
303
|
-
# Sends an action over the AMI connection and blocks your Thread until the response comes in. If there was an error
|
|
304
|
-
# for some reason, the error will be raised as an ManagerInterfaceError.
|
|
305
|
-
#
|
|
306
|
-
# @param [String, Symbol] action_name The name of the action (e.g. Originate)
|
|
307
|
-
# @param [Hash] headers Other key/value pairs to send in this action. Note: don't provide an ActionID
|
|
308
|
-
# @raise [ManagerInterfaceError] When Asterisk can't execute this action, it sends back an Error which is converted into an ManagerInterfaceError object and raised. Access ManagerInterfaceError#message for the reported message from Asterisk.
|
|
309
|
-
# @return [ManagerInterfaceResponse, ImmediateResponse] Contains the response from Asterisk and all headers
|
|
310
|
-
#
|
|
311
|
-
def send_action_synchronously(*args)
|
|
312
|
-
_send_action_asynchronously(*args).response.tap do |response|
|
|
313
|
-
raise response if response.kind_of?(ManagerInterfaceError)
|
|
314
|
-
end
|
|
315
|
-
end
|
|
316
|
-
|
|
317
|
-
alias send_action send_action_synchronously
|
|
318
|
-
|
|
319
|
-
# ping sends an action to the Asterisk Manager Interface that returns a pong
|
|
320
|
-
# more details here: http://www.voip-info.org/wiki/index.php?page=Asterisk+Manager+API+Action+Ping
|
|
321
|
-
def ping
|
|
322
|
-
send_action "Ping"
|
|
323
|
-
true
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
# The originate method launches a call to Asterisk, full details here:
|
|
327
|
-
# http://www.voip-info.org/tiki-index.php?page=Asterisk+Manager+API+Action+Originate
|
|
328
|
-
# Takes these arguments as a hash:
|
|
329
|
-
#
|
|
330
|
-
# Channel: Channel on which to originate the call (The same as you specify in the Dial application command)
|
|
331
|
-
# Context: Context to use on connect (must use Exten & Priority with it)
|
|
332
|
-
# Exten: Extension to use on connect (must use Context & Priority with it)
|
|
333
|
-
# Priority: Priority to use on connect (must use Context & Exten with it)
|
|
334
|
-
# Timeout: Timeout (in milliseconds) for the originating connection to happen(defaults to 30000 milliseconds)
|
|
335
|
-
# CallerID: CallerID to use for the call
|
|
336
|
-
# Variable: Channels variables to set (max 32). Variables will be set for both channels (local and connected).
|
|
337
|
-
# Account: Account code for the call
|
|
338
|
-
# Application: Application to use on connect (use Data for parameters)
|
|
339
|
-
# Data : Data if Application parameter is used
|
|
340
|
-
# Async: For the origination to be asynchronous (allows multiple calls to be generated without waiting for a response)
|
|
341
|
-
# ActionID: The request identifier. It allows you to identify the response to this request.
|
|
342
|
-
# You may use a number or a string. Useful when you make several simultaneous requests.
|
|
343
|
-
#
|
|
344
|
-
# For example:
|
|
345
|
-
# originate { :channel => 'SIP/1000@sipnetworks.com',
|
|
346
|
-
# :context => 'my_context',
|
|
347
|
-
# :exten => 's',
|
|
348
|
-
# :priority => '1' }
|
|
349
|
-
def originate(options={})
|
|
350
|
-
options = options.clone
|
|
351
|
-
options[:callerid] = options.delete :caller_id if options.has_key? :caller_id
|
|
352
|
-
options[:exten] = options.delete :extension if options.has_key? :extension
|
|
353
|
-
if options.has_key?(:variables) && options[:variables].kind_of?(Hash)
|
|
354
|
-
options[:variable] = options.delete(:variables).map {|pair| pair.join('=')}.join(@coreSettings["ArgumentDelimiter"])
|
|
355
|
-
end
|
|
356
|
-
send_action "Originate", options
|
|
357
|
-
end
|
|
358
|
-
|
|
359
|
-
# An introduction connects two endpoints together. The first argument is
|
|
360
|
-
# the first person the PBX will call. When she's picked up, Asterisk will
|
|
361
|
-
# play ringing while the second person is being dialed.
|
|
362
|
-
#
|
|
363
|
-
# The first argument is the person called first. Pass this as a canonical
|
|
364
|
-
# IAX2/server/user type argument. Destination takes the same format, but
|
|
365
|
-
# comma-separated Dial() arguments can be optionally passed after the
|
|
366
|
-
# technology.
|
|
367
|
-
#
|
|
368
|
-
# TODO: Provide an example when this works.
|
|
369
|
-
#
|
|
370
|
-
def introduce(caller, callee, opts={})
|
|
371
|
-
dial_args = callee
|
|
372
|
-
dial_args += "|#{opts[:options]}" if opts[:options]
|
|
373
|
-
call_and_exec caller, "Dial", :args => dial_args, :caller_id => opts[:caller_id]
|
|
374
|
-
end
|
|
375
|
-
|
|
376
|
-
# hangup terminates a call accepts a channel as the argument
|
|
377
|
-
# full details here: http://www.voip-info.org/wiki/index.php?page=Asterisk+Manager+API+Action+Hangup
|
|
378
|
-
def hangup(channel)
|
|
379
|
-
send_action "Hangup", :channel => channel
|
|
380
|
-
end
|
|
381
|
-
|
|
382
|
-
# call_and_exec allows you to make a call to a channel and then execute an Astersik application
|
|
383
|
-
# on that call
|
|
384
|
-
def call_and_exec(channel, app, opts={})
|
|
385
|
-
args = { :channel => channel, :application => app }
|
|
386
|
-
args[:caller_id] = opts[:caller_id] if opts[:caller_id]
|
|
387
|
-
args[:data] = opts[:args] if opts[:args]
|
|
388
|
-
args[:variables] = opts[:variables] if opts[:variables]
|
|
389
|
-
originate args
|
|
390
|
-
end
|
|
391
|
-
|
|
392
|
-
# call_into_context is syntactic sugar for the Asterisk originate command that allows you to
|
|
393
|
-
# launch a call into a particular context. For example:
|
|
394
|
-
#
|
|
395
|
-
# call_into_context('SIP/1000@sipnetworks.com', 'my_context', { :variables => { :session_guid => new_guid }})
|
|
396
|
-
def call_into_context(channel, context, options={})
|
|
397
|
-
args = {:channel => channel, :context => context}
|
|
398
|
-
args[:priority] = options[:priority] || 1
|
|
399
|
-
args[:exten] = options[:extension] if options[:extension]
|
|
400
|
-
args[:caller_id] = options[:caller_id] if options[:caller_id]
|
|
401
|
-
args[:variables] = options[:variables] if options[:variables]
|
|
402
|
-
originate args
|
|
403
|
-
end
|
|
404
|
-
|
|
405
|
-
private
|
|
406
|
-
def _send_action_asynchronously(action_name, headers={})
|
|
407
|
-
check_action_name action_name
|
|
408
|
-
action = ManagerInterfaceAction.new(action_name, headers)
|
|
409
|
-
if action.replies_with_action_id?
|
|
410
|
-
@write_queue << action
|
|
411
|
-
action
|
|
412
|
-
else
|
|
413
|
-
raise NotImplementedError
|
|
414
|
-
end
|
|
415
|
-
end
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
protected
|
|
420
|
-
|
|
421
|
-
##
|
|
422
|
-
# This class will be removed once this AMI library fully supports all known protocol anomalies.
|
|
423
|
-
#
|
|
424
|
-
class UnsupportedActionName < ArgumentError
|
|
425
|
-
UNSUPPORTED_ACTION_NAMES = %w[
|
|
426
|
-
queues
|
|
427
|
-
] unless defined? UNSUPPORTED_ACTION_NAMES
|
|
428
|
-
|
|
429
|
-
# Blacklist some actions depends on the Asterisk version
|
|
430
|
-
def self.preinitialize(version)
|
|
431
|
-
if version < 1.8
|
|
432
|
-
%w[iaxpeers muteaudio mixmonitormute aocmessage].each do |action|
|
|
433
|
-
UNSUPPORTED_ACTION_NAMES << action
|
|
434
|
-
end
|
|
435
|
-
end
|
|
436
|
-
|
|
437
|
-
if version < 1.6
|
|
438
|
-
%w[skinnydevices skinnyshowdevice skinnylines skinnyshowline coreshowchannels
|
|
439
|
-
sipshowregistry getconfigjson bridge listallvoicemailusers dbdel dbdeltree
|
|
440
|
-
insert jitterbufstats atxfer iaxregistry queuereload queuereset].each do |action|
|
|
441
|
-
UNSUPPORTED_ACTION_NAMES << action
|
|
442
|
-
end
|
|
443
|
-
end
|
|
444
|
-
end
|
|
445
|
-
|
|
446
|
-
def initialize(name)
|
|
447
|
-
super "At the moment this AMI library doesn't support the #{name.inspect} action because it causes a protocol anomaly. Support for it will be coming shortly."
|
|
448
|
-
end
|
|
449
|
-
|
|
450
|
-
end
|
|
451
|
-
|
|
452
|
-
def check_action_name(name)
|
|
453
|
-
name = name.to_s.downcase
|
|
454
|
-
raise UnsupportedActionName.new(name) if UnsupportedActionName::UNSUPPORTED_ACTION_NAMES.include? name
|
|
455
|
-
true
|
|
456
|
-
end
|
|
457
|
-
|
|
458
|
-
def start_actions_writer_loop
|
|
459
|
-
@actions_writer_thread = Thread.new do
|
|
460
|
-
begin
|
|
461
|
-
actions_writer_loop
|
|
462
|
-
rescue => e
|
|
463
|
-
Events.trigger(['exception'], e)
|
|
464
|
-
end
|
|
465
|
-
end
|
|
466
|
-
end
|
|
467
|
-
|
|
468
|
-
def stop_actions_writer_loop
|
|
469
|
-
if @actions_writer_thread
|
|
470
|
-
@write_queue << :STOP!
|
|
471
|
-
@actions_writer_thread.join
|
|
472
|
-
@actions_writer_thread = nil
|
|
473
|
-
end
|
|
474
|
-
end
|
|
475
|
-
|
|
476
|
-
def actions_writer_loop
|
|
477
|
-
loop do
|
|
478
|
-
begin
|
|
479
|
-
next_action = @write_queue.shift
|
|
480
|
-
return :stopped if next_action.equal? :STOP!
|
|
481
|
-
register_action_with_metadata next_action
|
|
482
|
-
|
|
483
|
-
ahn_log.ami.debug "Sending AMI action: #{"\n>>> " + next_action.to_s.gsub(/(\r\n)+/, "\n>>> ")}"
|
|
484
|
-
@actions_connection.send_data next_action.to_s
|
|
485
|
-
# If it's "causal event" action, we must wait here until it's fully responded
|
|
486
|
-
next_action.response if next_action.has_causal_events?
|
|
487
|
-
rescue Object => e
|
|
488
|
-
ahn_log.ami.debug "Error in AMI writer loop: #{e.class}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
|
|
489
|
-
end
|
|
490
|
-
end
|
|
491
|
-
end
|
|
492
|
-
|
|
493
|
-
##
|
|
494
|
-
# When we send out an AMI action, we need to track the ActionID and have the other Thread handling the socket IO
|
|
495
|
-
# notify the sending Thread that a response has been received. This method instantiates a new FutureResource and
|
|
496
|
-
# keeps it around in a synchronized Hash for the IO-handling Thread to notify when a response with a matching
|
|
497
|
-
# ActionID is seen again. See also data_for_message_received_with_action_id() which is how the IO-handling Thread
|
|
498
|
-
# gets the metadata registered in the method back later.
|
|
499
|
-
#
|
|
500
|
-
# @param [ManagerInterfaceAction] action The ManagerInterfaceAction to send
|
|
501
|
-
# @param [Hash] headers The other key/value pairs being sent with this message
|
|
502
|
-
#
|
|
503
|
-
def register_action_with_metadata(action)
|
|
504
|
-
raise ArgumentError, "Must supply an action!" if action.nil?
|
|
505
|
-
@sent_messages_lock.synchronize do
|
|
506
|
-
@sent_messages[action.action_id] = action
|
|
507
|
-
end
|
|
508
|
-
end
|
|
509
|
-
|
|
510
|
-
def data_for_message_received_with_action_id(action_id)
|
|
511
|
-
@sent_messages_lock.synchronize do
|
|
512
|
-
@sent_messages.delete action_id
|
|
513
|
-
end
|
|
514
|
-
end
|
|
515
|
-
|
|
516
|
-
# Give an error response to any outstanding messages -- they
|
|
517
|
-
# won't be completed now
|
|
518
|
-
def clear_actions_connection_resources
|
|
519
|
-
# Fail all outstanding messages and reset the message list
|
|
520
|
-
@sent_messages_lock.synchronize do
|
|
521
|
-
@sent_messages.each do |action_id, action|
|
|
522
|
-
error = ManagerInterfaceError.new
|
|
523
|
-
error.message = "Connection terminated to AMI server"
|
|
524
|
-
|
|
525
|
-
action.future_resource.resource = error
|
|
526
|
-
end
|
|
527
|
-
|
|
528
|
-
@sent_messages = {}
|
|
529
|
-
end
|
|
530
|
-
end
|
|
531
|
-
|
|
532
|
-
##
|
|
533
|
-
# Instantiates a new ManagerInterfaceActionsConnection and assigns it to @actions_connection.
|
|
534
|
-
#
|
|
535
|
-
# @return [EventSocket]
|
|
536
|
-
#
|
|
537
|
-
def establish_actions_connection
|
|
538
|
-
@actions_connection = EventSocket.connect(@host, @port) do |handler|
|
|
539
|
-
handler.receive_data { |data| @actions_lexer << data }
|
|
540
|
-
handler.connected { actions_connection_established }
|
|
541
|
-
handler.disconnected { actions_connection_disconnected }
|
|
542
|
-
end
|
|
543
|
-
login_actions
|
|
544
|
-
rescue Errno::ECONNREFUSED => e
|
|
545
|
-
ahn_log.ami.warn "ACTIONS thread connection refused! Retrying in #{RETRY_SLEEP} seconds..."
|
|
546
|
-
sleep RETRY_SLEEP
|
|
547
|
-
retry
|
|
548
|
-
end
|
|
549
|
-
|
|
550
|
-
def disconnect_actions_connection
|
|
551
|
-
# Clean up the EventSocket we may have
|
|
552
|
-
if @actions_connection
|
|
553
|
-
@actions_connection.disconnect!
|
|
554
|
-
@actions_connection.join
|
|
555
|
-
@actions_connection = nil
|
|
556
|
-
end
|
|
557
|
-
end
|
|
558
|
-
|
|
559
|
-
##
|
|
560
|
-
# Instantiates a new ManagerInterfaceEventsConnection and assigns it to @events_connection.
|
|
561
|
-
#
|
|
562
|
-
# @return [EventSocket]
|
|
563
|
-
#
|
|
564
|
-
def establish_events_connection
|
|
565
|
-
|
|
566
|
-
# Note: the @events_connection instance variable is set in login()
|
|
567
|
-
@events_connection = EventSocket.connect(@host, @port) do |handler|
|
|
568
|
-
handler.receive_data { |data| @events_lexer << data }
|
|
569
|
-
handler.connected { events_connection_established }
|
|
570
|
-
handler.disconnected { events_connection_disconnected }
|
|
571
|
-
end
|
|
572
|
-
login_events
|
|
573
|
-
ahn_log.ami "Successful AMI events-only connection into #{@username}@#{@host}"
|
|
574
|
-
rescue Errno::ECONNREFUSED => e
|
|
575
|
-
ahn_log.ami.warn "EVENTS thread connection refused! Retrying in #{RETRY_SLEEP} seconds..."
|
|
576
|
-
sleep RETRY_SLEEP
|
|
577
|
-
retry
|
|
578
|
-
end
|
|
579
|
-
|
|
580
|
-
def login_actions
|
|
581
|
-
response = send_action "Login", "Username" => @username, "Secret" => @password, "Events" => "Off"
|
|
582
|
-
ahn_log.ami "Successful AMI actions-only connection into #{@username}@#{@host}"
|
|
583
|
-
if @actions_lexer.ami_version < 1.1
|
|
584
|
-
@coreSettings = Hash.new
|
|
585
|
-
@coreSettings["AsteriskVersion"] = "1.4.0"
|
|
586
|
-
@coreSettings["AMIversion"] = "1.0"
|
|
587
|
-
@coreSettings["ArgumentDelimiter"] = "|"
|
|
588
|
-
else
|
|
589
|
-
@coreSettings = send_action_synchronously("CoreSettings").headers
|
|
590
|
-
@coreSettings["ArgumentDelimiter"] = ","
|
|
591
|
-
end
|
|
592
|
-
UnsupportedActionName::preinitialize(@coreSettings["AsteriskVersion"].to_f)
|
|
593
|
-
response
|
|
594
|
-
rescue ManagerInterfaceError => e
|
|
595
|
-
raise AuthenticationFailedException, "Incorrect username and password! #{e.message}"
|
|
596
|
-
end
|
|
597
|
-
|
|
598
|
-
def disconnect_events_connection
|
|
599
|
-
# Clean up the EventSocket we may have
|
|
600
|
-
if @events_connection
|
|
601
|
-
@events_connection.disconnect!
|
|
602
|
-
@events_connection.join
|
|
603
|
-
@events_connection = nil
|
|
604
|
-
end
|
|
605
|
-
end
|
|
606
|
-
|
|
607
|
-
##
|
|
608
|
-
# Since this method is always called after the login_actions method, an AuthenticationFailedException would have already
|
|
609
|
-
# been raised if the username/password were off. Because this is the only action we ever need to send on this socket,
|
|
610
|
-
# it goes straight to the EventSocket connection (bypassing the @write_queue).
|
|
611
|
-
#
|
|
612
|
-
def login_events
|
|
613
|
-
login_action = ManagerInterfaceAction.new "Login", "Username" => @username, "Secret" => @password, "Events" => "On"
|
|
614
|
-
@events_connection.send_data login_action.to_s
|
|
615
|
-
end
|
|
616
|
-
|
|
617
|
-
def parse_options(options)
|
|
618
|
-
unrecognized_keys = options.keys.map { |key| key.to_sym } - DEFAULT_SETTINGS.keys
|
|
619
|
-
if unrecognized_keys.any?
|
|
620
|
-
raise ArgumentError, "Unrecognized named argument(s): #{unrecognized_keys.to_sentence}"
|
|
621
|
-
end
|
|
622
|
-
DEFAULT_SETTINGS.merge options
|
|
623
|
-
end
|
|
624
|
-
|
|
625
|
-
##
|
|
626
|
-
# Raised when calling ManagerInterface#connect!() and the server responds with an error after logging in.
|
|
627
|
-
#
|
|
628
|
-
class AuthenticationFailedException < StandardError; end
|
|
629
|
-
|
|
630
|
-
class NotConnectedError < StandardError; end
|
|
631
|
-
|
|
632
|
-
##
|
|
633
|
-
# Each time ManagerInterface#send_action is invoked, a new ManagerInterfaceAction is instantiated.
|
|
634
|
-
#
|
|
635
|
-
class ManagerInterfaceAction
|
|
636
|
-
|
|
637
|
-
attr_reader :name, :headers, :future_resource, :action_id, :causal_event_terminator_name
|
|
638
|
-
def initialize(name, headers={})
|
|
639
|
-
@name = name.to_s.downcase.freeze
|
|
640
|
-
@headers = headers.stringify_keys.freeze
|
|
641
|
-
@action_id = new_action_id.freeze
|
|
642
|
-
@future_resource = FutureResource.new
|
|
643
|
-
@causal_event_terminator_name = ManagerInterface.causal_event_terminator_name_for name
|
|
644
|
-
end
|
|
645
|
-
|
|
646
|
-
##
|
|
647
|
-
# Used internally by ManagerInterface for the actions in AMI which break the protocol's definition and do not
|
|
648
|
-
# reply with an ActionID.
|
|
649
|
-
#
|
|
650
|
-
def replies_with_action_id?
|
|
651
|
-
ManagerInterface.replies_with_action_id?(@name, @headers)
|
|
652
|
-
end
|
|
653
|
-
|
|
654
|
-
##
|
|
655
|
-
# Some AMI actions effectively respond with many events which collectively constitute the actual response. These
|
|
656
|
-
# Must be handled specially by the protocol parser, so this method helps inform the parser.
|
|
657
|
-
#
|
|
658
|
-
def has_causal_events?
|
|
659
|
-
ManagerInterface.has_causal_events?(@name, @headers)
|
|
660
|
-
end
|
|
661
|
-
|
|
662
|
-
##
|
|
663
|
-
# Abstracts the generation of new ActionIDs. This could be implemented virutally any way, provided each
|
|
664
|
-
# invocation returns something unique, so this will generate a GUID and return it.
|
|
665
|
-
#
|
|
666
|
-
# @return [String] characters in GUID format (e.g. "4C5F4E1C-A0F1-4D13-8751-C62F2F783062")
|
|
667
|
-
#
|
|
668
|
-
def new_action_id
|
|
669
|
-
new_guid # Implemented in lib/adhearsion/foundation/pseudo_guid.rb
|
|
670
|
-
end
|
|
671
|
-
|
|
672
|
-
##
|
|
673
|
-
# Converts this action into a protocol-valid String, ready to be sent over a socket.
|
|
674
|
-
#
|
|
675
|
-
def to_s
|
|
676
|
-
@textual_representation ||= (
|
|
677
|
-
"Action: #{@name}\r\nActionID: #{@action_id}\r\n" +
|
|
678
|
-
@headers.map { |(key,value)| "#{key}: #{value}" }.join("\r\n") +
|
|
679
|
-
(@headers.any? ? "\r\n\r\n" : "\r\n")
|
|
680
|
-
)
|
|
681
|
-
end
|
|
682
|
-
|
|
683
|
-
##
|
|
684
|
-
# If the response has simply not been received yet from Asterisk, the calling Thread will block until it comes
|
|
685
|
-
# in. Once the response comes in, subsequent calls immediately return a reference to the ManagerInterfaceResponse
|
|
686
|
-
# object.
|
|
687
|
-
#
|
|
688
|
-
def response
|
|
689
|
-
future_resource.resource
|
|
690
|
-
end
|
|
691
|
-
|
|
692
|
-
end
|
|
693
|
-
end
|
|
694
|
-
end
|
|
695
|
-
end
|
|
696
|
-
end
|
|
697
|
-
end
|