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,124 @@
|
|
1
|
+
module Adhearsion
|
2
|
+
class CallController
|
3
|
+
module Menu
|
4
|
+
|
5
|
+
# Creates and manages a multiple choice menu driven by DTMF, handling playback of prompts,
|
6
|
+
# invalid input, retries and timeouts, and final failures.
|
7
|
+
#
|
8
|
+
# @example A complete example of the method is as follows:
|
9
|
+
# menu "Welcome, ", "/opt/sounds/menu-prompt.mp3", :tries => 2, :timeout => 10 do
|
10
|
+
# match 1, OperatorController
|
11
|
+
#
|
12
|
+
# match 10..19 do
|
13
|
+
# pass DirectController
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# match 5, 6, 9 do |exten|
|
17
|
+
# play "The #{exten} extension is currently not active"
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# match '7', OfficeController
|
21
|
+
#
|
22
|
+
# invalid { play "Please choose a valid extension" }
|
23
|
+
# timeout { play "Input timed out, try again." }
|
24
|
+
# failure { pass OperatorController }
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# The first arguments to #menu will be a list of sounds to play, as accepted by #play, including strings for TTS, Date and Time objects, and file paths.
|
28
|
+
# :tries and :timeout options respectively specify the number of tries before going into failure, and the timeout in seconds allowed on each digit input.
|
29
|
+
# The most important part is the following block, which specifies how the menu will be constructed and handled.
|
30
|
+
#
|
31
|
+
# #match handles connecting an input pattern to a payload.
|
32
|
+
# The pattern can be one or more of: an integer, a Range, a string, an Array of the possible single types.
|
33
|
+
# Input is matched against patterns, and the first exact match has it's payload executed.
|
34
|
+
# Matched input is passed in to the associated block, or to the controller through #options.
|
35
|
+
#
|
36
|
+
# Allowed payloads are the name of a controller class, in which case it is executed through its #run method, or a block.
|
37
|
+
#
|
38
|
+
# #invalid has its associated block executed when the input does not possibly match any pattern.
|
39
|
+
# #timeout's block is run when time expires before or between input digits.
|
40
|
+
# #failure runs its block when the maximum number of tries is reached without an input match.
|
41
|
+
#
|
42
|
+
# Execution of the current context resumes after #menu finishes. If you wish to jump to an entirely different controller, use #pass.
|
43
|
+
# Menu will return :failed if failure was reached, or :done if a match was executed.
|
44
|
+
#
|
45
|
+
# @param [Object] A list of outputs to play, as accepted by #play
|
46
|
+
# @param [Hash] options Options to use for the menu
|
47
|
+
# @option options [Integer] :tries Number of tries allowed before failure
|
48
|
+
# @option options [Integer] :timeout Timeout in seconds before the first and between each input digit
|
49
|
+
#
|
50
|
+
# @return [Symbol] :failure on failure, :done if a match is reached and executed. Will only return if control is not passed.
|
51
|
+
#
|
52
|
+
# @raise [ArgumentError] Raised if no block is passed in
|
53
|
+
#
|
54
|
+
# @see play
|
55
|
+
# @see pass
|
56
|
+
#
|
57
|
+
def menu(*args, &block)
|
58
|
+
raise ArgumentError, "You must provide a block to the #menu method." unless block_given?
|
59
|
+
|
60
|
+
options = args.last.kind_of?(Hash) ? args.pop : {}
|
61
|
+
sound_files = args.flatten
|
62
|
+
|
63
|
+
menu_instance = MenuDSL::Menu.new options, &block
|
64
|
+
result_of_menu = nil
|
65
|
+
|
66
|
+
until MenuDSL::Menu::MenuResultDone === result_of_menu
|
67
|
+
if menu_instance.should_continue?
|
68
|
+
result_of_menu = menu_instance.continue
|
69
|
+
else
|
70
|
+
logger.debug "Menu failed to get valid input. Executing failure hook."
|
71
|
+
menu_instance.execute_failure_hook
|
72
|
+
return :failed
|
73
|
+
end
|
74
|
+
|
75
|
+
case result_of_menu
|
76
|
+
when MenuDSL::Menu::MenuResultInvalid
|
77
|
+
logger.debug "Menu get invalid input. Executing invalid hook and restarting."
|
78
|
+
menu_instance.execute_invalid_hook
|
79
|
+
menu_instance.restart!
|
80
|
+
result_of_menu = nil
|
81
|
+
when MenuDSL::Menu::MenuGetAnotherDigit
|
82
|
+
next_digit = play_sound_files_for_menu menu_instance, sound_files
|
83
|
+
if next_digit
|
84
|
+
menu_instance << next_digit
|
85
|
+
else
|
86
|
+
case result_of_menu
|
87
|
+
when MenuDSL::Menu::MenuGetAnotherDigitOrFinish
|
88
|
+
jump_to result_of_menu.match_object, :extension => result_of_menu.new_extension
|
89
|
+
return true
|
90
|
+
when MenuDSL::Menu::MenuGetAnotherDigitOrTimeout
|
91
|
+
logger.debug "Menu timed out. Executing timeout hook and restarting."
|
92
|
+
menu_instance.execute_timeout_hook
|
93
|
+
menu_instance.restart!
|
94
|
+
result_of_menu = nil
|
95
|
+
end
|
96
|
+
end
|
97
|
+
when MenuDSL::Menu::MenuResultFound
|
98
|
+
logger.debug "Menu got a valid input (#{result_of_menu.new_extension}). Executing the match."
|
99
|
+
jump_to result_of_menu.match_object, :extension => result_of_menu.new_extension
|
100
|
+
return true
|
101
|
+
end # case
|
102
|
+
end # while
|
103
|
+
return :done
|
104
|
+
end
|
105
|
+
|
106
|
+
def play_sound_files_for_menu(menu_instance, sound_files)
|
107
|
+
digit = nil
|
108
|
+
if sound_files.any? && menu_instance.digit_buffer_empty?
|
109
|
+
digit = interruptible_play *sound_files
|
110
|
+
end
|
111
|
+
digit || wait_for_digit(menu_instance.timeout)
|
112
|
+
end
|
113
|
+
|
114
|
+
def jump_to(match_object, overrides = nil)
|
115
|
+
if match_object.block
|
116
|
+
instance_exec overrides[:extension], &match_object.block
|
117
|
+
else
|
118
|
+
invoke match_object.match_payload, overrides
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
end # module
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,267 @@
|
|
1
|
+
module Adhearsion
|
2
|
+
class CallController
|
3
|
+
module Output
|
4
|
+
def speak(text, options = {})
|
5
|
+
play_ssml(text, options) || output(:text, text, options)
|
6
|
+
end
|
7
|
+
|
8
|
+
#
|
9
|
+
# Plays the specified sound file names. This method will handle Time/DateTime objects (e.g. Time.now),
|
10
|
+
# Fixnums (e.g. 1000), Strings which are valid Fixnums (e.g "123"), and direct sound files. To specify how the Date/Time objects are said
|
11
|
+
# pass in as an array with the first parameter as the Date/Time/DateTime object along with a hash with the
|
12
|
+
# additional options. See play_time for more information.
|
13
|
+
#
|
14
|
+
# @example Play file hello-world
|
15
|
+
# play 'http://www.example.com/hello-world.mp3'
|
16
|
+
# play '/path/on/disk/hello-world.wav'
|
17
|
+
# @example Speak current time
|
18
|
+
# play Time.now
|
19
|
+
# @example Speak today's date
|
20
|
+
# play Date.today
|
21
|
+
# @example Speak today's date in a specific format
|
22
|
+
# play Date.today, :strftime => "%d/%m/%Y", :format => "dmy"
|
23
|
+
# @example Play sound file, speak number, play two more sound files
|
24
|
+
# play %w"http://www.example.com/a-connect-charge-of.wav 22 /path/to/cents-per-minute.wav /path/to/will-apply.mp3"
|
25
|
+
# @example Play two sound files
|
26
|
+
# play "/path/to/you-sound-cute.mp3", "/path/to/what-are-you-wearing.wav"
|
27
|
+
#
|
28
|
+
# @return [Boolean] true is returned if everything was successful. Otherwise, false indicates that
|
29
|
+
# some sound file(s) could not be played.
|
30
|
+
#
|
31
|
+
# @see play_time
|
32
|
+
# @see play_numeric
|
33
|
+
# @see play_audio
|
34
|
+
#
|
35
|
+
def play(*arguments)
|
36
|
+
arguments.inject(true) do |value, argument|
|
37
|
+
value = case argument
|
38
|
+
when Hash
|
39
|
+
play_ssml_for argument.delete(:value), argument
|
40
|
+
when RubySpeech::SSML::Speak
|
41
|
+
play_ssml argument
|
42
|
+
else
|
43
|
+
play_ssml_for argument
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# Plays the specified input arguments, raising an exception if any can't be played.
|
50
|
+
# @see play
|
51
|
+
#
|
52
|
+
def play!(*arguments)
|
53
|
+
play *arguments or raise Adhearsion::PlaybackError, "One of the passed outputs is invalid"
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Plays the given Date, Time, or Integer (seconds since epoch)
|
58
|
+
# using the given timezone and format.
|
59
|
+
#
|
60
|
+
# @param [Date|Time|DateTime] Time to be said.
|
61
|
+
# @param [Hash] Additional options to specify how exactly to say time specified.
|
62
|
+
#
|
63
|
+
# +:format+ - This format is used only to disambiguate times that could be interpreted in different ways.
|
64
|
+
# For example, 01/06/2011 could mean either the 1st of June or the 6th of January.
|
65
|
+
# Please refer to the SSML specification.
|
66
|
+
# @see http://www.w3.org/TR/ssml-sayas/#S3.1
|
67
|
+
# +:strftime+ - This format is what defines the string that is sent to the Speech Synthesis Engine.
|
68
|
+
# It uses Time::strftime symbols.
|
69
|
+
#
|
70
|
+
# @return [Boolean] true if successful, false if the given argument could not be played.
|
71
|
+
#
|
72
|
+
def play_time(*args)
|
73
|
+
argument, options = args.flatten
|
74
|
+
return false unless [Date, Time, DateTime].include? argument.class
|
75
|
+
|
76
|
+
options ||= {}
|
77
|
+
return false unless options.is_a? Hash
|
78
|
+
play_ssml ssml_for_time(argument, options)
|
79
|
+
end
|
80
|
+
|
81
|
+
#
|
82
|
+
# Plays the given Numeric argument or string representing a decimal number.
|
83
|
+
# When playing numbers, Adhearsion assumes you're saying the number, not the digits. For example, play("100")
|
84
|
+
# is pronounced as "one hundred" instead of "one zero zero".
|
85
|
+
#
|
86
|
+
# @param [Numeric|String] Numeric or String containing a valid Numeric, like "321".
|
87
|
+
#
|
88
|
+
# @return [Boolean] true if successful, false if the given argument could not be played.
|
89
|
+
#
|
90
|
+
def play_numeric(*args)
|
91
|
+
argument, options = args.flatten
|
92
|
+
if argument.kind_of?(Numeric) || argument =~ /^\d+$/
|
93
|
+
play_ssml ssml_for_numeric(argument, options)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
#
|
98
|
+
# Plays the given audio file.
|
99
|
+
# SSML supports http:// paths and full disk paths.
|
100
|
+
# The Punchblock backend will have to handle cases like Asterisk where there is a fixed sounds directory.
|
101
|
+
#
|
102
|
+
# @param [String] http:// URL or full disk path to the sound file
|
103
|
+
# @param [Hash] Additional options to specify how exactly to say time specified.
|
104
|
+
# +:fallback+ - The text to play if the file is not available
|
105
|
+
#
|
106
|
+
# @return [Boolean] true on correct play of the file, false on file missing or not playable
|
107
|
+
#
|
108
|
+
def play_audio(*args)
|
109
|
+
argument, options = args.flatten
|
110
|
+
play_ssml ssml_for_audio(argument, options)
|
111
|
+
end
|
112
|
+
|
113
|
+
def play_ssml(ssml, options = {})
|
114
|
+
if [RubySpeech::SSML::Speak, Nokogiri::XML::Document].include? ssml.class
|
115
|
+
output :ssml, ssml.to_s, options
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def output(type, content, options = {})
|
120
|
+
options.merge! type => content
|
121
|
+
execute_component_and_await_completion ::Punchblock::Component::Output.new(options)
|
122
|
+
end
|
123
|
+
|
124
|
+
def output!(type, content, options = {})
|
125
|
+
options.merge! type => content
|
126
|
+
execute_component_and_await_completion ::Punchblock::Component::Output.new(options)
|
127
|
+
end
|
128
|
+
|
129
|
+
#
|
130
|
+
# Same as interruptible_play, but throws an error if unable to play the output
|
131
|
+
# @see interruptible_play
|
132
|
+
#
|
133
|
+
def interruptible_play!(*outputs)
|
134
|
+
result = nil
|
135
|
+
outputs.each do |output|
|
136
|
+
result = stream_file output
|
137
|
+
break unless result.nil?
|
138
|
+
end
|
139
|
+
result
|
140
|
+
end
|
141
|
+
|
142
|
+
#
|
143
|
+
# Plays the given output, allowing for DTMF input of a single digit from the user
|
144
|
+
# At the end of the played file it returns nil
|
145
|
+
#
|
146
|
+
# @example Ask the user for a number, then play it back
|
147
|
+
# ssml = RubySpeech::SSML.draw do
|
148
|
+
# "Please press a button"
|
149
|
+
# end
|
150
|
+
# input = interruptible_play ssml
|
151
|
+
# play input unless input.nil?
|
152
|
+
#
|
153
|
+
# @param [String|Numeric|Date|Time|RubySpeech::SSML::Speak|Array|Hash] The argument to play to the user, or an array of arguments.
|
154
|
+
# @param [Hash] Additional options.
|
155
|
+
#
|
156
|
+
# @return [String|Nil] The single DTMF character entered by the user, or nil if nothing was entered
|
157
|
+
#
|
158
|
+
def interruptible_play(*outputs)
|
159
|
+
result = nil
|
160
|
+
outputs.each do |output|
|
161
|
+
begin
|
162
|
+
result = interruptible_play! output
|
163
|
+
rescue PlaybackError => e
|
164
|
+
# Ignore this exception and play the next output
|
165
|
+
logger.warn e.message
|
166
|
+
ensure
|
167
|
+
break if result
|
168
|
+
end
|
169
|
+
end
|
170
|
+
result
|
171
|
+
end
|
172
|
+
|
173
|
+
def detect_type(output)
|
174
|
+
result = nil
|
175
|
+
result = :time if [Date, Time, DateTime].include? output.class
|
176
|
+
result = :numeric if output.kind_of?(Numeric) || output =~ /^\d+$/
|
177
|
+
result = :audio if !result && (/\//.match(output.to_s) || URI::regexp(%w(http https)).match(output.to_s))
|
178
|
+
result ||= :text
|
179
|
+
end
|
180
|
+
|
181
|
+
def play_ssml_for(*args)
|
182
|
+
play_ssml ssml_for(args)
|
183
|
+
end
|
184
|
+
|
185
|
+
#
|
186
|
+
# Generates SSML for the argument and options passed, using automatic detection
|
187
|
+
# Directly returns the argument if it is already an SSML document
|
188
|
+
#
|
189
|
+
# @param [String|Hash|RubySpeech::SSML::Speak] the argument with options as accepted by the play_ methods, or an SSML document
|
190
|
+
# @return [RubySpeech::SSML::Speak] an SSML document
|
191
|
+
#
|
192
|
+
def ssml_for(*args)
|
193
|
+
return args[0] if args.size == 1 && args[0].is_a?(RubySpeech::SSML::Speak)
|
194
|
+
argument, options = args.flatten
|
195
|
+
options ||= {}
|
196
|
+
type = detect_type argument
|
197
|
+
send "ssml_for_#{type}", argument, options
|
198
|
+
end
|
199
|
+
|
200
|
+
def ssml_for_text(argument, options = {})
|
201
|
+
RubySpeech::SSML.draw { argument }
|
202
|
+
end
|
203
|
+
|
204
|
+
def ssml_for_time(argument, options = {})
|
205
|
+
interpretation = case argument
|
206
|
+
when Date then 'date'
|
207
|
+
when Time then 'time'
|
208
|
+
end
|
209
|
+
|
210
|
+
format = options.delete :format
|
211
|
+
strftime = options.delete :strftime
|
212
|
+
|
213
|
+
time_to_say = strftime ? argument.strftime(strftime) : argument.to_s
|
214
|
+
|
215
|
+
RubySpeech::SSML.draw do
|
216
|
+
say_as(:interpret_as => interpretation, :format => format) { time_to_say }
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def ssml_for_numeric(argument, options = {})
|
221
|
+
RubySpeech::SSML.draw do
|
222
|
+
say_as(:interpret_as => 'cardinal') { argument.to_s }
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def ssml_for_audio(argument, options = {})
|
227
|
+
fallback = (options || {}).delete :fallback
|
228
|
+
RubySpeech::SSML.draw do
|
229
|
+
audio(:src => argument) { fallback }
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
#
|
234
|
+
# Plays a single output, not only files, accepting interruption by one of the digits specified
|
235
|
+
# Currently still stops execution, will be fixed soon in Punchblock
|
236
|
+
#
|
237
|
+
# @param [Object] String or Hash specifying output and options
|
238
|
+
# @param [String] String with the digits that are allowed to interrupt output
|
239
|
+
# @return [String|nil] The pressed digit, or nil if nothing was pressed
|
240
|
+
#
|
241
|
+
def stream_file(argument, digits = '0123456789#*')
|
242
|
+
result = nil
|
243
|
+
ssml = ssml_for argument
|
244
|
+
output_component = ::Punchblock::Component::Output.new :ssml => ssml.to_s
|
245
|
+
input_stopper_component = ::Punchblock::Component::Input.new :mode => :dtmf,
|
246
|
+
:grammar => {
|
247
|
+
:value => grammar_accept(digits).to_s
|
248
|
+
}
|
249
|
+
input_stopper_component.register_event_handler ::Punchblock::Event::Complete do |event|
|
250
|
+
logger.warn "#stream_file Handling input complete event."
|
251
|
+
output_component.stop! unless output_component.complete?
|
252
|
+
end
|
253
|
+
write_and_await_response input_stopper_component
|
254
|
+
begin
|
255
|
+
execute_component_and_await_completion output_component
|
256
|
+
rescue StandardError => e
|
257
|
+
raise Adhearsion::PlaybackError, "Output failed for argument #{argument.inspect}"
|
258
|
+
end
|
259
|
+
input_stopper_component.stop! if input_stopper_component.executing?
|
260
|
+
reason = input_stopper_component.complete_event.reason
|
261
|
+
result = reason.interpretation if reason.respond_to? :interpretation
|
262
|
+
return parse_single_dtmf result unless result.nil?
|
263
|
+
result
|
264
|
+
end
|
265
|
+
end # Output
|
266
|
+
end # CallController
|
267
|
+
end # Adhearsion
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Adhearsion
|
2
|
+
class CallController
|
3
|
+
module Record
|
4
|
+
#
|
5
|
+
# Start a recording
|
6
|
+
#
|
7
|
+
# @param [Hash] options
|
8
|
+
# @param [Block] &block to process result of the record method
|
9
|
+
# @option options [Boolean, Optional] :async Execute asynchronously. Defaults to false
|
10
|
+
# @option options [Proc, Optional] :on_complete Block to be executed on completion when this method is invoked asynchronously
|
11
|
+
# @option options [Boolean, Optional] :start_beep Indicates whether subsequent record will be preceded with a beep. Default is true.
|
12
|
+
# @option options [Boolean, Optional] :start_paused Whether subsequent record will start in PAUSE mode. Default is false.
|
13
|
+
# @option options [String, Optional] :max_duration Indicates the maximum duration (milliseconds) for a recording.
|
14
|
+
# @option options [String, Optional] :format File format used during recording.
|
15
|
+
# @option options [String, Optional] :format File format used during recording.
|
16
|
+
# @option options [String, Optional] :initial_timeout Controls how long (milliseconds) the recognizer should wait after the end of the prompt for the caller to speak before sending a Recorder event.
|
17
|
+
# @option options [String, Optional] :final_timeout Controls the length (milliseconds) of a period of silence after callers have spoken to conclude they finished.
|
18
|
+
#
|
19
|
+
# @return recording object
|
20
|
+
|
21
|
+
def record(options = {}, &block)
|
22
|
+
async = options.delete(:async) ? true : false
|
23
|
+
on_complete = options.delete :on_complete
|
24
|
+
|
25
|
+
component = ::Punchblock::Component::Record.new options
|
26
|
+
|
27
|
+
if async
|
28
|
+
execute_component(component).tap do |result|
|
29
|
+
# FIXME: Setting the callback after we begin execution constitutes a race condition. We need to mock the component creation here, since the tests assume we're using whatever is returned by component exection, and that's what we pass the complete event to
|
30
|
+
result.register_event_handler Punchblock::Event::Complete do |event|
|
31
|
+
catching_standard_errors { on_complete.call event }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
else
|
35
|
+
execute_component_and_await_completion(component).tap do |result|
|
36
|
+
yield result.complete_event if block_given?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Adhearsion
|
2
|
+
class CallController
|
3
|
+
module Utility
|
4
|
+
|
5
|
+
# Utility method for DTMF GRXML grammars
|
6
|
+
#
|
7
|
+
# @param [Integer] Number of digits to accept in the grammar.
|
8
|
+
# @return [RubySpeech::GRXML::Grammar] A grammar suitable for use in SSML prompts
|
9
|
+
#
|
10
|
+
def grammar_digits(digits = 1)
|
11
|
+
RubySpeech::GRXML.draw :mode => 'dtmf', :root => 'inputdigits' do
|
12
|
+
rule id: 'inputdigits', scope: 'public' do
|
13
|
+
item repeat: digits.to_s do
|
14
|
+
one_of do
|
15
|
+
0.upto(9) { |d| item { d.to_s } }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end # grammar_digits
|
21
|
+
|
22
|
+
# Utility method to create a single-digit grammar to accept only some digits
|
23
|
+
#
|
24
|
+
# @param [String] String representing the digits to accept
|
25
|
+
# @return [RubySpeech::GRXML::Grammar] A grammar suitable for use in SSML prompts
|
26
|
+
#
|
27
|
+
def grammar_accept(digits = '0123456789#*')
|
28
|
+
allowed_digits = '0123456789#*'
|
29
|
+
gram_digits = digits.chars.select { |x| allowed_digits.include? x }
|
30
|
+
|
31
|
+
RubySpeech::GRXML.draw :mode => 'dtmf', :root => 'inputdigits' do
|
32
|
+
rule id: 'inputdigits', scope: 'public' do
|
33
|
+
one_of do
|
34
|
+
gram_digits.each { |d| item { d.to_s } }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# Parses a single DTMF tone in the format dtmf-*
|
42
|
+
#
|
43
|
+
# @param [String] the tone string to be parsed
|
44
|
+
# @return [String] the digit in case input was 0-9, * or # if star or pound respectively
|
45
|
+
#
|
46
|
+
def parse_single_dtmf(result)
|
47
|
+
return if result.nil?
|
48
|
+
case tone = result.split('-')[1]
|
49
|
+
when 'star'
|
50
|
+
'*'
|
51
|
+
when 'pound'
|
52
|
+
'#'
|
53
|
+
else
|
54
|
+
tone
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end#module
|
59
|
+
end
|
60
|
+
end
|