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.
Files changed (236) hide show
  1. data/.gitignore +17 -10
  2. data/CHANGELOG.md +273 -0
  3. data/Gemfile +1 -1
  4. data/Guardfile +17 -0
  5. data/README.markdown +61 -9
  6. data/Rakefile +16 -48
  7. data/adhearsion.gemspec +21 -7
  8. data/bin/ahn +3 -1
  9. data/cucumber.yml +4 -0
  10. data/features/app_generator.feature +42 -0
  11. data/features/cli.feature +108 -0
  12. data/features/step_definitions/app_generator_steps.rb +6 -0
  13. data/features/step_definitions/cli_steps.rb +74 -0
  14. data/features/support/aruba_helper.rb +22 -0
  15. data/features/support/env.rb +37 -0
  16. data/features/support/utils.rb +8 -0
  17. data/lib/adhearsion.rb +85 -41
  18. data/lib/adhearsion/call.rb +176 -0
  19. data/lib/adhearsion/call_controller.rb +134 -0
  20. data/lib/adhearsion/call_controller/dial.rb +70 -0
  21. data/lib/adhearsion/call_controller/input.rb +173 -0
  22. data/lib/adhearsion/call_controller/menu.rb +124 -0
  23. data/lib/adhearsion/call_controller/output.rb +267 -0
  24. data/lib/adhearsion/call_controller/record.rb +42 -0
  25. data/lib/adhearsion/call_controller/utility.rb +60 -0
  26. data/lib/adhearsion/calls.rb +81 -0
  27. data/lib/adhearsion/cli.rb +1 -3
  28. data/lib/adhearsion/cli_commands.rb +142 -0
  29. data/lib/adhearsion/configuration.rb +149 -0
  30. data/lib/adhearsion/console.rb +19 -8
  31. data/lib/adhearsion/dialplan_controller.rb +9 -0
  32. data/lib/adhearsion/events.rb +84 -0
  33. data/lib/adhearsion/foundation/all.rb +0 -7
  34. data/lib/adhearsion/foundation/custom_daemonizer.rb +4 -6
  35. data/lib/adhearsion/foundation/exception_handler.rb +9 -0
  36. data/lib/adhearsion/foundation/object.rb +26 -8
  37. data/lib/adhearsion/foundation/synchronized_hash.rb +3 -6
  38. data/lib/adhearsion/foundation/thread_safety.rb +17 -1
  39. data/lib/adhearsion/generators/app/app_generator.rb +4 -13
  40. data/lib/adhearsion/generators/app/templates/Gemfile +10 -5
  41. data/lib/adhearsion/generators/app/templates/Procfile +1 -0
  42. data/lib/adhearsion/generators/app/templates/README.md +28 -0
  43. data/lib/adhearsion/generators/app/templates/config/adhearsion.rb +41 -0
  44. data/lib/adhearsion/generators/app/templates/{components/simon_game → lib}/simon_game.rb +6 -18
  45. data/lib/adhearsion/generators/app/templates/script/ahn +2 -2
  46. data/lib/adhearsion/initializer.rb +151 -293
  47. data/lib/adhearsion/initializer/logging.rb +33 -0
  48. data/lib/adhearsion/logging.rb +65 -69
  49. data/lib/adhearsion/menu_dsl.rb +15 -0
  50. data/lib/adhearsion/menu_dsl/calculated_match.rb +39 -0
  51. data/lib/adhearsion/menu_dsl/calculated_match_collection.rb +41 -0
  52. data/lib/adhearsion/menu_dsl/fixnum_match_calculator.rb +18 -0
  53. data/lib/adhearsion/menu_dsl/match_calculator.rb +36 -0
  54. data/lib/adhearsion/{voip/menu_state_machine/menu_class.rb → menu_dsl/menu.rb} +38 -40
  55. data/lib/adhearsion/menu_dsl/menu_builder.rb +69 -0
  56. data/lib/adhearsion/menu_dsl/range_match_calculator.rb +55 -0
  57. data/lib/adhearsion/menu_dsl/string_match_calculator.rb +21 -0
  58. data/lib/adhearsion/outbound_call.rb +64 -0
  59. data/lib/adhearsion/plugin.rb +319 -0
  60. data/lib/adhearsion/plugin/collection.rb +19 -0
  61. data/lib/adhearsion/plugin/initializer.rb +37 -0
  62. data/lib/adhearsion/plugin/methods_container.rb +6 -0
  63. data/lib/adhearsion/process.rb +94 -0
  64. data/lib/adhearsion/punchblock_plugin.rb +29 -0
  65. data/lib/adhearsion/punchblock_plugin/initializer.rb +137 -0
  66. data/lib/adhearsion/router.rb +30 -0
  67. data/lib/adhearsion/router/route.rb +42 -0
  68. data/lib/adhearsion/script_ahn_loader.rb +2 -2
  69. data/lib/adhearsion/tasks.rb +14 -9
  70. data/lib/adhearsion/tasks/configuration.rb +26 -0
  71. data/lib/adhearsion/tasks/plugins.rb +17 -0
  72. data/lib/adhearsion/version.rb +8 -14
  73. data/spec/adhearsion/call_controller/dial_spec.rb +138 -0
  74. data/spec/adhearsion/call_controller/input_spec.rb +278 -0
  75. data/spec/adhearsion/call_controller/menu_spec.rb +120 -0
  76. data/spec/adhearsion/call_controller/output_spec.rb +466 -0
  77. data/spec/adhearsion/call_controller/record_spec.rb +125 -0
  78. data/spec/adhearsion/call_controller_spec.rb +395 -0
  79. data/spec/adhearsion/call_spec.rb +438 -0
  80. data/spec/adhearsion/calls_spec.rb +47 -0
  81. data/spec/adhearsion/configuration_spec.rb +308 -0
  82. data/spec/adhearsion/dialplan_controller_spec.rb +26 -0
  83. data/spec/adhearsion/events_spec.rb +112 -0
  84. data/spec/adhearsion/initializer/logging_spec.rb +58 -0
  85. data/spec/adhearsion/initializer_spec.rb +209 -122
  86. data/spec/adhearsion/logging_spec.rb +58 -47
  87. data/spec/adhearsion/menu_dsl/calculated_match_collection_spec.rb +56 -0
  88. data/spec/adhearsion/menu_dsl/calculated_match_spec.rb +57 -0
  89. data/spec/adhearsion/menu_dsl/fixnum_match_calculator_spec.rb +33 -0
  90. data/spec/adhearsion/menu_dsl/match_calculator_spec.rb +13 -0
  91. data/spec/adhearsion/menu_dsl/menu_builder_spec.rb +118 -0
  92. data/spec/adhearsion/menu_dsl/menu_spec.rb +210 -0
  93. data/spec/adhearsion/menu_dsl/range_match_calculator_spec.rb +28 -0
  94. data/spec/adhearsion/menu_dsl/string_match_calculator_spec.rb +36 -0
  95. data/spec/adhearsion/menu_dsl_spec.rb +12 -0
  96. data/spec/adhearsion/outbound_call_spec.rb +174 -0
  97. data/spec/adhearsion/plugin_spec.rb +489 -0
  98. data/spec/adhearsion/process_spec.rb +34 -0
  99. data/spec/adhearsion/punchblock_plugin/initializer_spec.rb +294 -0
  100. data/spec/adhearsion/router/route_spec.rb +99 -0
  101. data/spec/adhearsion/router_spec.rb +106 -0
  102. data/spec/adhearsion_spec.rb +46 -0
  103. data/spec/spec_helper.rb +14 -14
  104. data/spec/support/call_controller_test_helpers.rb +48 -0
  105. data/spec/support/initializer_stubs.rb +8 -13
  106. data/spec/support/punchblock_mocks.rb +6 -0
  107. metadata +255 -253
  108. data/CHANGELOG +0 -174
  109. data/bin/ahnctl +0 -68
  110. data/bin/jahn +0 -43
  111. data/examples/asterisk_manager_interface/standalone.rb +0 -51
  112. data/lib/adhearsion/commands.rb +0 -302
  113. data/lib/adhearsion/component_manager.rb +0 -278
  114. data/lib/adhearsion/component_manager/component_tester.rb +0 -54
  115. data/lib/adhearsion/component_manager/spec_framework.rb +0 -18
  116. data/lib/adhearsion/events_support.rb +0 -65
  117. data/lib/adhearsion/foundation/blank_slate.rb +0 -3
  118. data/lib/adhearsion/foundation/event_socket.rb +0 -205
  119. data/lib/adhearsion/foundation/future_resource.rb +0 -36
  120. data/lib/adhearsion/foundation/metaprogramming.rb +0 -17
  121. data/lib/adhearsion/foundation/numeric.rb +0 -13
  122. data/lib/adhearsion/foundation/pseudo_guid.rb +0 -10
  123. data/lib/adhearsion/foundation/relationship_properties.rb +0 -42
  124. data/lib/adhearsion/foundation/string.rb +0 -26
  125. data/lib/adhearsion/generators/app/templates/.ahnrc +0 -34
  126. data/lib/adhearsion/generators/app/templates/README +0 -8
  127. data/lib/adhearsion/generators/app/templates/components/ami_remote/ami_remote.rb +0 -15
  128. data/lib/adhearsion/generators/app/templates/components/disabled/HOW_TO_ENABLE +0 -7
  129. data/lib/adhearsion/generators/app/templates/components/disabled/stomp_gateway/README.markdown +0 -47
  130. data/lib/adhearsion/generators/app/templates/components/disabled/stomp_gateway/stomp_gateway.rb +0 -34
  131. data/lib/adhearsion/generators/app/templates/components/disabled/stomp_gateway/stomp_gateway.yml +0 -12
  132. data/lib/adhearsion/generators/app/templates/components/disabled/xmpp_gateway/README.markdown +0 -3
  133. data/lib/adhearsion/generators/app/templates/components/disabled/xmpp_gateway/xmpp_gateway.rb +0 -11
  134. data/lib/adhearsion/generators/app/templates/components/disabled/xmpp_gateway/xmpp_gateway.yml +0 -0
  135. data/lib/adhearsion/generators/app/templates/config/startup.rb +0 -81
  136. data/lib/adhearsion/generators/app/templates/dialplan.rb +0 -3
  137. data/lib/adhearsion/generators/app/templates/events.rb +0 -33
  138. data/lib/adhearsion/host_definitions.rb +0 -67
  139. data/lib/adhearsion/initializer/asterisk.rb +0 -86
  140. data/lib/adhearsion/initializer/configuration.rb +0 -324
  141. data/lib/adhearsion/initializer/database.rb +0 -60
  142. data/lib/adhearsion/initializer/drb.rb +0 -31
  143. data/lib/adhearsion/initializer/freeswitch.rb +0 -22
  144. data/lib/adhearsion/initializer/ldap.rb +0 -57
  145. data/lib/adhearsion/initializer/rails.rb +0 -41
  146. data/lib/adhearsion/initializer/xmpp.rb +0 -42
  147. data/lib/adhearsion/tasks/components.rb +0 -32
  148. data/lib/adhearsion/tasks/database.rb +0 -5
  149. data/lib/adhearsion/tasks/deprecations.rb +0 -59
  150. data/lib/adhearsion/tasks/generating.rb +0 -20
  151. data/lib/adhearsion/tasks/lint.rb +0 -4
  152. data/lib/adhearsion/voip/asterisk.rb +0 -4
  153. data/lib/adhearsion/voip/asterisk/agi_server.rb +0 -121
  154. data/lib/adhearsion/voip/asterisk/commands.rb +0 -1966
  155. data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +0 -140
  156. data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +0 -102
  157. data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +0 -250
  158. data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +0 -240
  159. data/lib/adhearsion/voip/asterisk/config_manager.rb +0 -64
  160. data/lib/adhearsion/voip/asterisk/manager_interface.rb +0 -697
  161. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rb +0 -1681
  162. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb +0 -341
  163. data/lib/adhearsion/voip/asterisk/manager_interface/ami_messages.rb +0 -78
  164. data/lib/adhearsion/voip/asterisk/manager_interface/ami_protocol_lexer_machine.rl +0 -87
  165. data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +0 -80
  166. data/lib/adhearsion/voip/call.rb +0 -521
  167. data/lib/adhearsion/voip/call_routing.rb +0 -64
  168. data/lib/adhearsion/voip/commands.rb +0 -17
  169. data/lib/adhearsion/voip/constants.rb +0 -39
  170. data/lib/adhearsion/voip/conveniences.rb +0 -18
  171. data/lib/adhearsion/voip/dial_plan.rb +0 -252
  172. data/lib/adhearsion/voip/dsl/dialing_dsl.rb +0 -151
  173. data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +0 -37
  174. data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +0 -27
  175. data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +0 -124
  176. data/lib/adhearsion/voip/dsl/dialplan/parser.rb +0 -69
  177. data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +0 -16
  178. data/lib/adhearsion/voip/dsl/numerical_string.rb +0 -128
  179. data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +0 -48
  180. data/lib/adhearsion/voip/freeswitch/event_handler.rb +0 -58
  181. data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +0 -129
  182. data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +0 -38
  183. data/lib/adhearsion/voip/freeswitch/oes_server.rb +0 -195
  184. data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +0 -80
  185. data/lib/adhearsion/voip/menu_state_machine/matchers.rb +0 -123
  186. data/lib/adhearsion/voip/menu_state_machine/menu_builder.rb +0 -57
  187. data/lib/adhearsion/xmpp/connection.rb +0 -61
  188. data/lib/theatre.rb +0 -147
  189. data/lib/theatre/README.markdown +0 -64
  190. data/lib/theatre/callback_definition_loader.rb +0 -86
  191. data/lib/theatre/guid.rb +0 -23
  192. data/lib/theatre/invocation.rb +0 -131
  193. data/lib/theatre/namespace_manager.rb +0 -153
  194. data/lib/theatre/version.rb +0 -2
  195. data/spec/adhearsion/cli_spec.rb +0 -306
  196. data/spec/adhearsion/component_manager_spec.rb +0 -292
  197. data/spec/adhearsion/constants_spec.rb +0 -8
  198. data/spec/adhearsion/drb_spec.rb +0 -65
  199. data/spec/adhearsion/fixtures/dialplan.rb +0 -3
  200. data/spec/adhearsion/foundation/event_socket_spec.rb +0 -168
  201. data/spec/adhearsion/host_definitions_spec.rb +0 -79
  202. data/spec/adhearsion/initializer/configuration_spec.rb +0 -291
  203. data/spec/adhearsion/initializer/loading_spec.rb +0 -154
  204. data/spec/adhearsion/initializer/paths_spec.rb +0 -74
  205. data/spec/adhearsion/relationship_properties_spec.rb +0 -54
  206. data/spec/adhearsion/voip/asterisk/agi_server_spec.rb +0 -473
  207. data/spec/adhearsion/voip/asterisk/ami/ami_spec.rb +0 -550
  208. data/spec/adhearsion/voip/asterisk/ami/lexer/ami_fixtures.yml +0 -30
  209. data/spec/adhearsion/voip/asterisk/ami/lexer/lexer_story +0 -291
  210. data/spec/adhearsion/voip/asterisk/ami/lexer/lexer_story.rb +0 -241
  211. data/spec/adhearsion/voip/asterisk/ami/lexer/story_helper.rb +0 -124
  212. data/spec/adhearsion/voip/asterisk/commands_spec.rb +0 -3241
  213. data/spec/adhearsion/voip/asterisk/config_file_generators/agents_spec.rb +0 -251
  214. data/spec/adhearsion/voip/asterisk/config_file_generators/queues_spec.rb +0 -323
  215. data/spec/adhearsion/voip/asterisk/config_file_generators/voicemail_spec.rb +0 -306
  216. data/spec/adhearsion/voip/asterisk/config_manager_spec.rb +0 -127
  217. data/spec/adhearsion/voip/asterisk/menu_command/calculated_match_spec.rb +0 -109
  218. data/spec/adhearsion/voip/asterisk/menu_command/matchers_spec.rb +0 -97
  219. data/spec/adhearsion/voip/call_routing_spec.rb +0 -125
  220. data/spec/adhearsion/voip/dialplan_manager_spec.rb +0 -468
  221. data/spec/adhearsion/voip/dsl/dialing_dsl_spec.rb +0 -270
  222. data/spec/adhearsion/voip/dsl/dispatcher_spec.rb +0 -82
  223. data/spec/adhearsion/voip/dsl/dispatcher_spec_helper.rb +0 -45
  224. data/spec/adhearsion/voip/dsl/parser_spec.rb +0 -69
  225. data/spec/adhearsion/voip/freeswitch/basic_connection_manager_spec.rb +0 -39
  226. data/spec/adhearsion/voip/freeswitch/inbound_connection_manager_spec.rb +0 -39
  227. data/spec/adhearsion/voip/freeswitch/oes_server_spec.rb +0 -9
  228. data/spec/adhearsion/voip/numerical_string_spec.rb +0 -61
  229. data/spec/adhearsion/voip/phone_number_spec.rb +0 -45
  230. data/spec/support/the_following_code.rb +0 -3
  231. data/spec/theatre/dsl_examples/simple_before_call.rb +0 -7
  232. data/spec/theatre/dsl_spec.rb +0 -69
  233. data/spec/theatre/invocation_spec.rb +0 -182
  234. data/spec/theatre/namespace_spec.rb +0 -125
  235. data/spec/theatre/spec_helper_spec.rb +0 -28
  236. 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