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.
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
@@ -1,1966 +0,0 @@
1
- require 'adhearsion/voip/menu_state_machine/menu_class'
2
- require 'json'
3
-
4
- module Adhearsion
5
- module VoIP
6
- module Asterisk
7
- class AGIProtocolError < StandardError; end
8
-
9
- module Commands
10
-
11
- RESPONSE_PREFIX = "200 result=" unless defined? RESPONSE_PREFIX
12
- AGI_SUCCESSFUL_RESPONSE = RESPONSE_PREFIX + "1"
13
-
14
- # These are the status messages that asterisk will issue after a dial command is executed.
15
- #
16
- # Here is a current list of dial status messages which are not all necessarily supported by adhearsion:
17
- #
18
- # ANSWER: Call is answered. A successful dial. The caller reached the callee.
19
- # BUSY: Busy signal. The dial command reached its number but the number is busy.
20
- # NOANSWER: No answer. The dial command reached its number, the number rang for too long, then the dial timed out.
21
- # CANCEL: Call is cancelled. The dial command reached its number but the caller hung up before the callee picked up.
22
- # CONGESTION: Congestion. This status is usually a sign that the dialled number is not recognised.
23
- # CHANUNAVAIL: Channel unavailable. On SIP, peer may not be registered.
24
- # DONTCALL: Privacy mode, callee rejected the call
25
- # TORTURE: Privacy mode, callee chose to send caller to torture menu
26
- # INVALIDARGS: Error parsing Dial command arguments (added for Asterisk 1.4.1, SVN r53135-53136)
27
- #
28
- # @see http://www.voip-info.org/wiki/index.php?page=Asterisk+variable+DIALSTATUS Asterisk Variable DIALSTATUS
29
- DIAL_STATUSES = Hash.new(:unknown).merge(:answer => :answered, #:doc:
30
- :congestion => :congested,
31
- :busy => :busy,
32
- :cancel => :cancelled,
33
- :noanswer => :unanswered,
34
- :cancelled => :cancelled,
35
- :chanunavail => :channel_unavailable) unless defined? DIAL_STATUSES
36
-
37
- DYNAMIC_FEATURE_EXTENSIONS = {
38
- :attended_transfer => lambda do |options|
39
- variable "TRANSFER_CONTEXT" => options[:context] if options && options.has_key?(:context)
40
- extend_dynamic_features_with "atxfer"
41
- end,
42
- :blind_transfer => lambda do |options|
43
- variable "TRANSFER_CONTEXT" => options[:context] if options && options.has_key?(:context)
44
- extend_dynamic_features_with 'blindxfer'
45
- end
46
- } unless defined? DYNAMIC_FEATURE_EXTENSIONS
47
-
48
- PLAYBACK_SUCCESS = 'SUCCESS' unless defined? PLAYBACK_SUCCESS
49
-
50
- # Utility method to write to pbx.
51
- # @param [String] message raw message
52
- def write(message)
53
- to_pbx.print message + "\n"
54
- end
55
-
56
- # Utility method to read from pbx. Hangup if nil.
57
- def read
58
- begin
59
- from_pbx.gets.tap do |message|
60
- # AGI has many conditions that might indicate a hangup
61
- raise Hangup if message.nil?
62
-
63
- ahn_log.agi.debug "<<< #{message}"
64
-
65
- code, rest = *message.split(' ', 2)
66
-
67
- case code.to_i
68
- when 510
69
- # This error is non-fatal for the call
70
- ahn_log.agi.warn "510: Invalid or unknown AGI command"
71
- when 511
72
- # 511 Command Not Permitted on a dead channel
73
- ahn_log.agi.debug "511: Dead channel. Raising Hangup"
74
- raise Hangup
75
- when 520
76
- # This error is non-fatal for the call
77
- ahn_log.agi.warn "520: Invalid command syntax"
78
- when (500..599)
79
- # Assume this error is non-fatal for the call and try to keep running
80
- ahn_log.agi.warn "#{code}: Unknown AGI protocol error."
81
- end
82
-
83
- # If the message starts with HANGUP it's a silly 1.6 OOB message
84
- case message
85
- when /^HANGUP/, /^HANGUP\n?$/i, /^HANGUP\s?\d{3}/i
86
- ahn_log.agi.debug "AGI HANGUP. Raising hangup"
87
- raise Hangup
88
- end
89
- end
90
- rescue Errno::ECONNRESET
91
- raise Hangup
92
- end
93
- end
94
-
95
- # The underlying method executed by nearly all the command methods in this module.
96
- # Used to send the plaintext commands in the proper AGI format over TCP/IP back to an Asterisk server via the
97
- # FAGI protocol.
98
- #
99
- # It is not recommended that you call this method directly unless you plan to write a new command method
100
- # in which case use this to communicate directly with an Asterisk server via the FAGI protocol.
101
- #
102
- # @param [String] message
103
- #
104
- # @see http://www.voip-info.org/wiki/view/Asterisk+FastAGI More information about FAGI
105
- def raw_response(message = nil)
106
- message.squish!
107
- @call.with_command_lock do
108
- raise ArgumentError.new("illegal NUL in message #{message.inspect}") if message =~ /\0/
109
- ahn_log.agi.debug ">>> #{message}"
110
- write message if message
111
- read
112
- end
113
- end
114
-
115
- def response(command, *arguments)
116
- if arguments.empty?
117
- raw_response("#{command}")
118
- else
119
- raw_response("#{command} " + arguments.map{ |arg| quote_arg(arg) }.join(' '))
120
- end
121
- end
122
-
123
- # Arguments surrounded by quotes; quotes backslash-escaped.
124
- # See parse_args in asterisk/res/res_agi.c (Asterisk 1.4.21.1)
125
- def quote_arg(arg)
126
- '"' + arg.to_s.gsub(/["\\]/) { |m| "\\#{m}" } + '"'
127
- end
128
-
129
- # Parses a response in the form of "200 result=some_value"
130
- def inline_return_value(result)
131
- return nil unless result
132
- case result.chomp
133
- when "200 result=0" then nil
134
- when /^200 result=(.*)$/ then $LAST_PAREN_MATCH
135
- else raise AGIProtocolError, "Invalid AGI response: #{result}"
136
- end
137
- end
138
-
139
- # Parses a response in the form of "200 result=0 (some_value)"
140
- def inline_result_with_return_value(result)
141
- return nil unless result
142
- case result.chomp
143
- when "200 result=0" then nil
144
- when /^#{AGI_SUCCESSFUL_RESPONSE} \((.*)\)$/ then $LAST_PAREN_MATCH
145
- else raise AGIProtocolError, "Invalid AGI response: #{result}"
146
- end
147
- end
148
-
149
-
150
- # This must be called first before any other commands can be issued.
151
- # In typical Adhearsion applications this is called by default as soon as a call is
152
- # transfered to a valid context in dialplan.rb.
153
- # If you do not want your Adhearsion application to automatically issue an answer command,
154
- # then you must edit your startup.rb file and configure this setting.
155
- # Keep in mind that you should not need to issue another answer command after one has already
156
- # been issued either explicitly by your code or implicitly by the standard adhearsion configuration.
157
- def answer
158
- response "ANSWER"
159
- true
160
- end
161
-
162
- # This asterisk dialplan command allows you to instruct Asterisk to start applications
163
- # which are typically run from extensions.conf.
164
- #
165
- # The most common commands are already made available through the FAGI interface provided
166
- # by this code base. For commands that do not fall into this category, then exec is what you
167
- # should use.
168
- #
169
- # For example, if there are specific asterisk modules you have loaded that will not be
170
- # available through the standard commands provided through FAGI - then you can used EXEC.
171
- #
172
- # @example Using execute in this way will add a header to an existing SIP call.
173
- # execute 'SIPAddHeader', '"Call-Info: answer-after=0"
174
- #
175
- # @see http://www.voip-info.org/wiki/view/Asterisk+-+documentation+of+application+commands Asterisk Dialplan Commands
176
- def execute(application, *arguments)
177
- command = "EXEC #{application}"
178
- arguments = arguments.map { |arg| quote_arg(arg) }.join(AHN_CONFIG.asterisk.argument_delimiter)
179
- result = raw_response("#{command} #{arguments}")
180
- return false if error?(result)
181
- result
182
- end
183
-
184
- # Sends a message to the console via the verbose message system.
185
- #
186
- # @param [String] message
187
- # @param [Integer] level
188
- #
189
- # @return the result of the command
190
- #
191
- # @example Use this command to inform someone watching the Asterisk console
192
- # of actions happening within Adhearsion.
193
- # verbose 'Processing call with Adhearsion' 3
194
- #
195
- # @see http://www.voip-info.org/wiki/view/verbose
196
- def verbose(message, level = nil)
197
- result = response('VERBOSE', message, level)
198
- return false if error?(result)
199
- result
200
- end
201
-
202
- # Hangs up the current channel. After this command is issued, you will not be able to send any more AGI
203
- # commands but the dialplan Thread will still continue, allowing you to do any post-call work.
204
- #
205
- def hangup
206
- response 'HANGUP'
207
- end
208
-
209
- # Plays the specified sound file names. This method will handle Time/DateTime objects (e.g. Time.now),
210
- # Fixnums (e.g. 1000), Strings which are valid Fixnums (e.g "123"), and direct sound files. When playing
211
- # numbers, Adhearsion assumes you're saying the number, not the digits. For example, play("100")
212
- # is pronounced as "one hundred" instead of "one zero zero". To specify how the Date/Time objects are said
213
- # pass in as an array with the first parameter as the Date/Time/DateTime object along with a hash with the
214
- # additional options. See play_time for more information.
215
- #
216
- # Note: it is not necessary to supply a sound file extension; Asterisk will try to find a sound
217
- # file encoded using the current channel's codec, if one exists. If not, it will transcode from
218
- # the default codec (GSM). Asterisk stores its sound files in /var/lib/asterisk/sounds.
219
- #
220
- # @example Play file hello-world.???
221
- # play 'hello-world'
222
- # @example Speak current time
223
- # play Time.now
224
- # @example Speak today's date
225
- # play Date.today
226
- # @example Speak today's date in a specific format
227
- # play [Date.today, {:format => 'BdY'}]
228
- # @example Play sound file, speak number, play two more sound files
229
- # play %w"a-connect-charge-of 22 cents-per-minute will-apply"
230
- # @example Play two sound files
231
- # play "you-sound-cute", "what-are-you-wearing"
232
- #
233
- # @return [Boolean] true is returned if everything was successful. Otherwise, false indicates that
234
- # some sound file(s) could not be played.
235
- def play(*arguments)
236
- result = true
237
- unless play_time(arguments)
238
- arguments.flatten.each do |argument|
239
- # result starts off as true. But if the following command ever returns false, then result
240
- # remains false.
241
- result &= play_numeric(argument) || play_soundfile(argument)
242
- end
243
- end
244
- result
245
- end
246
-
247
- # Same as {#play}, but immediately raises an exception if a sound file cannot be played.
248
- #
249
- # @return [true]
250
- # @raise [Adhearsion::VoIP::PlaybackError] If a sound file cannot be played
251
- def play!(*arguments)
252
- unless play_time(arguments)
253
- arguments.flatten.each do |argument|
254
- play_numeric(argument) || play_soundfile!(argument)
255
- end
256
- end
257
- true
258
- end
259
-
260
- # Attempts to play a sound prompt. If the prompt is unplayable, for
261
- # example, if the file is not present, then attempt to speak the prompt
262
- # using Text-To-Speech.
263
- #
264
- # @param [Hash] Map of prompts and fallback TTS options
265
- # @return [true]
266
- # @raise [ArgumentError] If prompt cannot be found and TTS text is not specified
267
- #
268
- # @example Play "tt-monkeys" or say "Ooh ooh eee eee eee"
269
- # play_or_speak 'tt-monkeys' => {:text => "Ooh ooh eee eee eee"}
270
- #
271
- # @example Play "pbx-invalid" or say "I'm sorry, that is not a valid extension. Please try again." and allowing the user to interrupt the TTS with "#"
272
- # play_or_speak 'pbx-invalid' => {:text => "I'm sorry, that is not a valid extension. Please try again", :engine => :unimrcp}
273
- def play_or_speak(prompts)
274
- interrupted = nil
275
- unless interrupted
276
- prompts.each do |filename, options|
277
- if filename && !filename.empty?
278
- begin
279
- if options[:interruptible]
280
- interrupted = interruptible_play! filename
281
- else
282
- play! filename
283
- end
284
- rescue PlaybackError
285
- raise ArgumentError, "Must supply TTS text as fallback" unless options[:text]
286
- interrupted = speak options.delete(:text), options
287
- end
288
- else
289
- interrupted = speak options.delete(:text), options
290
- end
291
- end
292
- end
293
- interrupted
294
- end
295
-
296
- # Records a sound file with the given name. If no filename is specified a file named by Asterisk
297
- # will be created and returned. Else the given filename will be returned. If a relative path is
298
- # given, the file will be saved in the default Asterisk sound directory, /var/lib/spool/asterisk
299
- # by default.
300
- #
301
- # @param [string] file name to record to. Full path information is optional. If you want to change the
302
- # format of the file you will want to add a .<valid extention> to the end of the file name specifying the
303
- # filetype you want to record in. Alternately you can pass it is as :format in the options
304
- #
305
- # @param [hash] options
306
- #
307
- # +:silence+ - silence in seconds
308
- #
309
- # +:maxduration+ - maximum duration in seconds
310
- #
311
- # +:escapedigits+ - digits to be used to excape from recording
312
- #
313
- # +:beep+ - soundfile to use as a beep before recording. if not specifed defaults to system generated beep, set to nil for no beep.
314
- #
315
- # +:format+ - the format of the file to be recorded
316
- #
317
- # Silence and maxduration is specified in seconds.
318
- #
319
- # @return [String] The filename of the recorded file.
320
- #
321
- # @example Asterisk generated filename
322
- # filename = record
323
- # @example Specified filename
324
- # record '/path/to/my-file.gsm'
325
- # @example All options specified
326
- # record 'my-file.gsm', :silence => 5, :maxduration => 120
327
- #
328
- # @deprecated please use {#record_to_file} instead
329
- def record(*args)
330
- options = args.last.kind_of?(Hash) ? args.pop : {}
331
- filename = args.shift || "/tmp/recording_%d"
332
- filename = increment_filename_counter filename
333
-
334
- # Work around Ruby 1.8 limitation with *args
335
- newargs = []
336
- newargs << filename
337
- newargs += args
338
- newargs << options
339
- record_to_file *newargs
340
-
341
- # Reconstruct the logic that took place in #base_record_to_file, since it does not return the
342
- # constructed filename
343
- if (options.has_key?(:format))
344
- format = options[:format]
345
- else
346
- filename, format = format_from_filename filename
347
- end
348
- "#{filename}.#{format}"
349
- end
350
-
351
- # Records a sound file with the given name. If no filename is specified a file named by Asterisk
352
- # will be created and returned. Else the given filename will be returned. If a relative path is
353
- # given, the file will be saved in the default Asterisk sound directory, /var/lib/spool/asterisk
354
- # by default.
355
- #
356
- # @param [string] file name to record to. Full path information is optional. If you want to change the
357
- # format of the file you will want to add a .<valid extention> to the end of the file name specifying the
358
- # filetype you want to record in. If you don't specify a valid extension it will default to gsm and a
359
- # .gsm will be added to the file. If you don't specify a filename it will write one in /tmp/recording_%d
360
- # with %d being a counter that increments from 0 onward for the particular call you are making.
361
- #
362
- # @param [hash] options
363
- #
364
- # +:silence+ - silence in seconds
365
- #
366
- # +:maxduration+ - maximum duration in seconds
367
- #
368
- # +:escapedigits+ - digits to be used to excape from recording
369
- #
370
- # +:beep+ - soundfile to use as a beep before recording. if not specifed defaults to system generated beep, set to nil for no beep.
371
- #
372
- # +:format+ - the format of the file to be recorded. This will over-ride a implicit format in a file extension and append a .<format> to the end of the file.
373
- #
374
- # Silence and maxduration is specified in seconds.
375
- #
376
- # @return [Symbol] One of the follwing..... :hangup, :write_error, :success_dtmf, :success_timeout
377
- #
378
- # A sound file will be recorded to the specifed file unless a :write_error is returned. A :success_dtmf is
379
- # for when a call was ended with a DTMF tone. A :success_timeout is returned when a call times out due to
380
- # a silence longer than the specified silence or if the recording reaches the maxduration.
381
- #
382
- # @example Asterisk generated filename
383
- # filename = record
384
- # @example Specified filename
385
- # record '/path/to/my-file.gsm'
386
- # @example All options specified
387
- # record 'my-file.gsm', :silence => 5, :maxduration => 120
388
- #
389
- def record_to_file(*args)
390
- base_record_to_file(*args).last
391
- end
392
-
393
- # This works the same record_to_file except is throws an exception if a playback or write error occurs.
394
- #
395
- def record_to_file!(*args)
396
- # raise PlaybackError, "Playback failed with PLAYBACKSTATUS: #{playback.inspect}. The raw response was #{response.inspect}."
397
- return_values = base_record_to_file(*args)
398
- if return_values.first == :playback_error
399
- raise PlaybackError, "Playback failed with PLAYBACKSTATUS: #{return_values.second.inspect}."
400
- elsif return_values.first == :write_error
401
- raise RecordError, "Record failed on write."
402
- end
403
- return_values.first
404
- end
405
-
406
- def format_from_filename(filename)
407
- filename, format = filename.match(/(^.*)\.([^\.]+)$/).captures rescue [filename, nil]
408
- if (format.nil?)
409
- ahn_log.agi.warn "Format not specified and not detected. Defaulting to \"gsm\""
410
- format = "gsm"
411
- end
412
- [filename, format]
413
- end
414
-
415
- def increment_filename_counter(filename)
416
- if filename.index("%d")
417
- if @call.variables.has_key?(:recording_counter)
418
- @call.variables[:recording_counter] += 1
419
- else
420
- @call.variables[:recording_counter] = 0
421
- end
422
- filename = filename % @call.variables[:recording_counter]
423
- end
424
- filename
425
- end
426
-
427
- # this is a base methor record_to_file and record_to_file! and should only be used via those methods
428
- #
429
- def base_record_to_file(*args)
430
- options = args.last.kind_of?(Hash) ? args.pop : {}
431
- filename = args.shift || "/tmp/recording_#{new_guid}_%d"
432
-
433
- filename = increment_filename_counter filename
434
-
435
- if (options.has_key?(:format))
436
- format = options[:format]
437
- else
438
- filename, format = format_from_filename filename
439
- end
440
-
441
- # maxduration must be in milliseconds when using RECORD FILE
442
- maxduration = options.delete(:maxduration) || -1
443
- maxduration = maxduration * 1000 if maxduration > 0
444
-
445
- escapedigits = options.delete(:escapedigits) || "#"
446
- silence = options.delete(:silence) || 0
447
-
448
- response_params = filename, format, escapedigits, maxduration, 0
449
- response_values = []
450
-
451
- if !options.has_key? :beep
452
- response_params << 'BEEP'
453
- elsif options[:beep]
454
- play_soundfile options[:beep]
455
- playback_response = get_variable('PLAYBACKSTATUS')
456
- if playback_response != PLAYBACK_SUCCESS
457
- response_values << :playback_error
458
- response_values << playback_response
459
- end
460
- end
461
-
462
- if silence > 0
463
- response_params << "s=#{silence}"
464
- end
465
-
466
- resp = response 'RECORD FILE', *response_params
467
- # If the user hangs up before the recording is entered, -1 is returned by asterisk and RECORDED_FILE
468
- # will not contain the name of the file, even though it IS in fact recorded.
469
- if resp.match /hangup/
470
- response_values << :hangup
471
- elsif resp.match /writefile/
472
- response_values << :write_error
473
- elsif resp.match /dtmf/
474
- response_values << :success_dtmf
475
- elsif resp.match /timeout/
476
- response_values << :success_timeout
477
- end
478
-
479
- response_values
480
- end
481
-
482
- # Simulates pressing the specified digits over the current channel. Can be used to
483
- # traverse a phone menu.
484
- def dtmf(digits)
485
- execute "SendDTMF", digits.to_s
486
- end
487
-
488
- # The with_next_message method...
489
- def with_next_message(&block)
490
- raise LocalJumpError, "Must supply a block" unless block_given?
491
- block.call(next_message)
492
- end
493
-
494
- # This command should be used to advance to the next message in the Asterisk Comedian Voicemail application
495
- def next_message
496
- @call.inbox.pop
497
- end
498
-
499
- # This command should be used to check if a message is waiting on the Asterisk Comedian Voicemail application.
500
- def messages_waiting?
501
- not @call.inbox.empty?
502
- end
503
-
504
- # Creates an interactive menu for the caller.
505
- #
506
- # The following documentation was derived from a post on Jay Phillips' blog (see below).
507
- #
508
- # The menu() command solves the problem of building enormous input-fetching state machines in Ruby without first-class
509
- # message passing facilities or an external DSL.
510
- #
511
- # Here is an example dialplan which uses the menu() command effectively.
512
- #
513
- # from_pstn {
514
- # menu 'welcome', 'for-spanish-press-8', 'main-ivr',
515
- # :timeout => 8.seconds, :tries => 3 do |link|
516
- # link.shipment_status 1
517
- # link.ordering 2
518
- # link.representative 4
519
- # link.spanish 8
520
- # link.employee 900..999
521
- #
522
- # link.on_invalid { play 'invalid' }
523
- #
524
- # link.on_premature_timeout do |str|
525
- # play 'sorry'
526
- # end
527
- #
528
- # link.on_failure do
529
- # play 'goodbye'
530
- # hangup
531
- # end
532
- # end
533
- # }
534
- #
535
- # shipment_status {
536
- # # Fetch a tracking number and pass it to a web service.
537
- # }
538
- #
539
- # ordering {
540
- # # Enter another menu that lets them enter credit card
541
- # # information and place their order over the phone.
542
- # }
543
- #
544
- # representative {
545
- # # Place the caller into a queue
546
- # }
547
- #
548
- # spanish {
549
- # # Special options for the spanish menu.
550
- # }
551
- #
552
- # employee {
553
- # dial "SIP/#{extension}" # Overly simplistic
554
- # }
555
- #
556
- # The main detail to note is the declarations within the menu() command’s block. Each line seems to refer to a link object
557
- # executing a seemingly arbitrary method with an argument that’s either a number or a Range of numbers. The +link+ object
558
- # collects these arbitrary method invocations and assembles a set of rules. The seemingly arbitrary method name is the name
559
- # of the context to which the menu should jump in case its argument (the pattern) is found to be a match.
560
- #
561
- # With these context names and patterns defined, the +menu()+ command plays in sequence the sound files you supply as
562
- # arguments, stopping playback abruptly if the user enters a digit. If no digits were pressed when the files finish playing,
563
- # it waits +:timeout+ seconds. If no digits are pressed after the timeout, it executes the +on_premature_timeout+ hook you
564
- # define (if any) and then tries again a maximum of +:tries+ times. If digits are pressed that result in no possible match,
565
- # it executes the +on_invalid+ hook. When/if all tries are exhausted with no positive match, it executes the +on_failure+
566
- # hook after the other hook (e.g. +on_invalid+, then +on_failure+).
567
- #
568
- # When the +menu()+ state machine runs through the defined rules, it must distinguish between exact and potential matches.
569
- # It's important to understand the differences between these and how they affect the overall outcome:
570
- #
571
- # |---------------|-------------------|------------------------------------------------------|
572
- # | exact matches | potential matches | result |
573
- # |---------------|-------------------|------------------------------------------------------|
574
- # | 0 | 0 | Fail and start over |
575
- # | 1 | 0 | Match found! |
576
- # | 0 | >0 | Get another digit |
577
- # | >1 | 0 | Go with the first exact match |
578
- # | 1 | >0 | Get another digit. If timeout, use exact match |
579
- # | >1 | >0 | Get another digit. If timeout, use first exact match |
580
- # |---------------|-------------------|------------------------------------------------------|
581
- #
582
- # == Database integration
583
- #
584
- # To do database integration, I recommend programatically executing methods on the link object within the block. For example:
585
- #
586
- # menu do |link|
587
- # for employee in Employee.find(:all)
588
- # link.internal employee.extension
589
- # end
590
- # end
591
- #
592
- # or this more efficient and Rubyish way
593
- #
594
- # menu do |link|
595
- # link.internal *Employee.find(:all).map(&:extension)
596
- # end
597
- #
598
- # If this second example seems like too much Ruby magic, let me explain — +Employee.find(:all)+ effectively does a “SELECT *
599
- # FROM employees” on the database with ActiveRecord, returning (what you’d think is) an Array. The +map(&:extension)+ is
600
- # fanciness that means “replace every instance in this Array with the result of calling extension on that object”. Now we
601
- # have an Array of every extension in the database. The splat operator (*) before the argument changes the argument from
602
- # being one argument (an Array) into a sequence of n arguments, where n is the number of items in the Array it’s “splatting”.
603
- # Lastly, these arguments are passed to the internal method, the name of a context which will handle dialing this user if one
604
- # of the supplied patterns matches.
605
- #
606
- # == Handling a successful pattern match
607
- #
608
- # Which brings me to another important note. Let’s say that the user’s input successfully matched one of the patterns
609
- # returned by that Employe.find... magic. When it jumps to the internal context, that context can access the variable entered
610
- # through the extension variable. This was a tricky design decision that I think, overall, works great. It makes the +menu()+
611
- # command feel much more first-class in the Adhearsion dialplan grammar and decouples the receiving context from the menu
612
- # that caused the jump. After all, the context doesn’t necessary need to be the endpoint from a menu; it can be its own entry
613
- # point, making menu() effectively a pipeline of re-creating the call.
614
- #
615
- # @see http://jicksta.com/articles/2008/02/11/menu-command Original Blog Post
616
- def menu(*args, &block)
617
- options = args.last.kind_of?(Hash) ? args.pop : {}
618
- sound_files = args.flatten
619
-
620
- menu_instance = Menu.new(options, &block)
621
-
622
- initial_digit_prompt = sound_files.any?
623
-
624
- # This method is basically one big begin/rescue block. When we start the Menu state machine by continue()ing, the state
625
- # machine will pass messages back to this method in the form of Exceptions. This decoupling allows the menu system to
626
- # work on, say, Freeswitch and Asterisk both.
627
- begin
628
- if menu_instance.should_continue?
629
- menu_instance.continue
630
- else
631
- menu_instance.execute_failure_hook
632
- return :failed
633
- end
634
- rescue Menu::MenuResult => result_of_menu
635
- case result_of_menu
636
- when Menu::MenuResultInvalid
637
- menu_instance.execute_invalid_hook
638
- menu_instance.restart!
639
- when Menu::MenuGetAnotherDigit
640
-
641
- next_digit = play_sound_files_for_menu(menu_instance, sound_files)
642
- if next_digit
643
- menu_instance << next_digit
644
- else
645
- # The user timed out entering another digit!
646
- case result_of_menu
647
- when Menu::MenuGetAnotherDigitOrFinish
648
- # This raises a ControlPassingException
649
- jump_to result_of_menu.match_payload, :extension => result_of_menu.new_extension
650
- when Menu::MenuGetAnotherDigitOrTimeout
651
- # This should execute premature_timeout AND reset if the number of retries
652
- # has not been exhausted.
653
- menu_instance.execute_timeout_hook
654
- menu_instance.restart!
655
- end
656
- end
657
- when Menu::MenuResultFound
658
- jump_to result_of_menu.match_payload, :extension => result_of_menu.new_extension
659
- else
660
- raise "Unrecognized MenuResult! This may be a bug!"
661
- end
662
-
663
- # Retry will re-execute the begin block, preserving our changes to the menu_instance object.
664
- retry
665
-
666
- end
667
- end
668
-
669
- # Used to receive keypad input from the user. Digits are collected
670
- # via DTMF (keypad) input until one of four things happens:
671
- #
672
- # 1. The number of digits you specify as the first argument is collected
673
- # 2. The timeout elapses. You can specify the timeout by either:
674
- # * Providing the :timeout option which applies for each awaited
675
- # digit
676
- # * Providing the :initial_timeout for 1st digit and
677
- # :interdigit_timeout for subsequent digits.
678
- # 3. The "#" key (or the key you specify with :accept_key) is pressed
679
- # 4. You return true from a block you pass in (see "Currency Example" below)
680
- #
681
- # Usage examples
682
- #
683
- # input # Receives digits until the caller presses the "#" key
684
- # input 3 # Receives three digits. Can be 0-9, * or #
685
- # input 5, :accept_key => "*" # Receive at most 5 digits, stopping if '*' is pressed
686
- # input 1, :timeout => 1.minute # Receive a single digit, returning an empty
687
- # # string if the timeout is encountered
688
- # input 3, :initial_timeout => 10.seconds, :interdigit_timeout => 5.seconds # Accept up to 3 digits,
689
- # # waiting 10 seconds for the 1st digit
690
- # # and 5 seconds for each subsequent digit.
691
- # input 9, :timeout => 7, :accept_key => "0" # Receives nine digits, returning
692
- # # when the timeout is encountered
693
- # # or when the "0" key is pressed.
694
- # input 3, :play => "you-sound-cute"
695
- # input :play => ["if-this-is-correct-press", 1, "otherwise-press", 2]
696
- # input :interruptible => false, :play => ["you-cannot-interrupt-this-message"] # Disallow DTMF (keypad) interruption
697
- # # until after all files are played.
698
- # input 3, :speak => {:text => 'How much wood could a woodchuck chuck?'} # Say an interruptible TTS phrase
699
- # # before waiting for input
700
- # input 5, :play => 'this-sound-file-might-not-exist', :speak => {:text => "Here's the TTS I say in case I can't find the sound file"}
701
- #
702
- # Currency Example
703
- # # Use a block to describe what kind of data will satisfy our input. Your block will be invoked each time a new digit is
704
- # # received. When your block returns true, it signals #input not to wait for digits anymore. In the example below, we are
705
- # # expecting a "currency" value, like 10*99. If the data ends with 2 decimal places, we immediately stop waiting for input.
706
- # input 7, :speak => {:text => "How much is that doggie in the window?"} { |value| value =~ /\*\d\d/ }
707
- #
708
- # When specifying files to play, the playback of the sequence of files will stop
709
- # immediately when the user presses the first digit unless you set :interruptible
710
- # to false.
711
- #
712
- # The :speak option takes a Hash. The Hash should include a :text key with a
713
- # TTS phrase to say. All other keys accepted by {#speak} are also supported.
714
- #
715
- # The :timeout option works like a digit timeout, therefore each digit pressed
716
- # causes the timer to reset. This is a much more user-friendly approach than an
717
- # absolute timeout.
718
- #
719
- # Note that when the digit limit is not specified the :accept_key becomes "#".
720
- # Otherwise there would be no way to end the collection of digits. You can
721
- # obviously override this by passing in a new key with :accept_key.
722
- #
723
- # @return [String] The keypad input received. An empty string is returned in the
724
- # absense of input. If the :accept_key argument was pressed, it
725
- # will not appear in the output.
726
- #
727
- # @see http://mojolingo.com/blog/2011/getting_ready_for_adhearsion_1_2/ More information on :speak, and &block parameters
728
- def input(*args, &block)
729
- begin
730
- input! *args, &block
731
- rescue PlaybackError => e
732
- ahn_log.agi.warn { e }
733
- retry # If sound playback fails, play the remaining sound files and wait for digits
734
- end
735
- end
736
-
737
- # Same as {#input}, but immediately raises an exception if sound playback fails
738
- #
739
- # @return (see #input)
740
- # @raise [Adhearsion::VoIP::PlaybackError] If a sound file cannot be played
741
- def input!(*args, &block)
742
- options = args.last.kind_of?(Hash) ? args.pop : {}
743
- number_of_digits = args.shift
744
-
745
- options[:play] = [*options[:play]].compact
746
-
747
- if options.has_key?(:interruptible) && options[:interruptible] == false
748
- play_command = :play!
749
- else
750
- options[:interruptible] = true
751
- play_command = :interruptible_play!
752
- end
753
-
754
- if options.has_key? :speak
755
- raise ArgumentError unless options[:speak].is_a? Hash
756
- raise ArgumentError, 'Must include a text string when requesting TTS fallback' unless options[:speak].has_key?(:text)
757
- options[:speak][:interruptible] = options[:interruptible]
758
- end
759
-
760
- timeout = options[:timeout] || -1
761
- initial_timeout = options[:initial_timeout] || timeout
762
- interdigit_timeout = options[:interdigit_timeout] || timeout
763
- terminating_key = options[:accept_key]
764
- raise ArgumentError, ":accept_key must not be empty" if terminating_key == ""
765
- terminating_key = if terminating_key
766
- terminating_key.to_s
767
- elsif number_of_digits.nil? && !terminating_key.equal?(false)
768
- '#'
769
- end
770
-
771
- if number_of_digits && number_of_digits < 0
772
- ahn_log.agi.warn "Giving -1 to #input is now deprecated. Do not specify a first " +
773
- "argument to allow unlimited digits." if number_of_digits == -1
774
- raise ArgumentError, "The number of digits must be positive!"
775
- end
776
-
777
- buffer = ''
778
- if options[:play].any?
779
- begin
780
- # Consume the sound files one at a time. In the event of playback
781
- # failure, this tells us which files remain unplayed.
782
- while file = options[:play].shift
783
- key = send play_command, file
784
- key = nil if play_command == :play!
785
- break if key
786
- end
787
- rescue PlaybackError
788
- raise unless options[:speak]
789
- key = speak options[:speak].delete(:text), options[:speak]
790
- end
791
- key ||= ''
792
- elsif options[:speak]
793
- key = speak(options[:speak].delete(:text), options[:speak]) || ''
794
- else
795
- key = ''
796
- end
797
- initial_timeout = nil if key.present?
798
-
799
- loop do
800
- return buffer if key.nil?
801
- if terminating_key
802
- if key == terminating_key
803
- return buffer
804
- else
805
- buffer << key
806
- return buffer if number_of_digits && number_of_digits == buffer.length
807
- end
808
- else
809
- buffer << key
810
- return buffer if number_of_digits && number_of_digits == buffer.length
811
- end
812
- return buffer if block_given? && yield(buffer)
813
- key = wait_for_digit(initial_timeout || interdigit_timeout)
814
- initial_timeout = nil
815
- end
816
- end
817
-
818
- # Jumps to a context. An alternative to DialplanContextProc#+@. When jumping to a context, it will *not* resume executing
819
- # the former context when the jumped-to context has finished executing. Make sure you don't have any
820
- # +ensure+ closures which you expect to execute when the call has finished, as they will run when
821
- # this method is called.
822
- #
823
- # You can optionally override certain dialplan variables when jumping to the context. A popular use of
824
- # this is to redefine +extension+ (which this method automatically boxes with a PhoneNumber object) so
825
- # you can effectively "restart" a call (from the perspective of the jumped-to context). When you override
826
- # variables here, you're effectively blowing away the old variables. If you need them for some reason,
827
- # you should assign the important ones to an instance variable first before calling this method.
828
- def jump_to(context, overrides={})
829
- context = lookup_context_with_name(context) if context.kind_of?(Symbol) || (context.kind_of?(String) && context =~ /^[\w_]+$/)
830
-
831
- # JRuby has a bug that prevents us from correctly determining the class name.
832
- # See: http://jira.codehaus.org/browse/JRUBY-5026
833
- if !(context.kind_of?(Adhearsion::DialPlan::DialplanContextProc) || context.kind_of?(Proc))
834
- raise Adhearsion::VoIP::DSL::Dialplan::ContextNotFoundException
835
- end
836
-
837
- if overrides.any?
838
- overrides = overrides.symbolize_keys
839
- if overrides.has_key?(:extension) && !overrides[:extension].kind_of?(Adhearsion::VoIP::DSL::PhoneNumber)
840
- overrides[:extension] = Adhearsion::VoIP::DSL::PhoneNumber.new overrides[:extension]
841
- end
842
-
843
- overrides.each_pair do |key, value|
844
- meta_def(key) { value }
845
- end
846
- end
847
-
848
- raise Adhearsion::VoIP::DSL::Dialplan::ControlPassingException.new(context)
849
- end
850
-
851
- # Place a call in a queue to be answered by a registered agent. You must then call join!()
852
- #
853
- # @param [String] queue_name the queue name to place the caller in
854
- # @return [Adhearsion::VoIP::Asterisk::Commands::QueueProxy] a queue proxy object
855
- #
856
- # @see http://www.voip-info.org/wiki-Asterisk+cmd+Queue Full information on the Asterisk Queue
857
- # @see Adhearsion::VoIP::Asterisk::Commands::QueueProxy#join! join!() for further details
858
- def queue(queue_name)
859
- queue_name = queue_name.to_s
860
-
861
- @queue_proxy_hash_lock = Mutex.new unless defined? @queue_proxy_hash_lock
862
- @queue_proxy_hash_lock.synchronize do
863
- @queue_proxy_hash ||= {}
864
- if @queue_proxy_hash.has_key? queue_name
865
- return @queue_proxy_hash[queue_name]
866
- else
867
- proxy = @queue_proxy_hash[queue_name] = QueueProxy.new(queue_name, self)
868
- return proxy
869
- end
870
- end
871
- end
872
-
873
- # Get the status of the last dial(). Possible dial statuses include :answer,
874
- # :busy, :no_answer, :cancelled, :congested, and :channel_unavailable.
875
- # If :cancel is returned, the caller hung up before the callee picked up.
876
- # If :congestion is returned, the dialed extension probably doesn't exist.
877
- # If :channel_unavailable, the callee phone may not be registered.
878
- def last_dial_status
879
- DIAL_STATUSES[get_dial_status]
880
- end
881
-
882
- # @return [Boolean] true if your last call to dial() finished with the ANSWER state,
883
- # as reported by Asterisk. false otherwise
884
- def last_dial_successful?
885
- last_dial_status == :answered
886
- end
887
-
888
- # Opposite of last_dial_successful?()
889
- def last_dial_unsuccessful?
890
- not last_dial_successful?
891
- end
892
-
893
- ##
894
- # @param [#to_s] text to speak using the TTS engine
895
- # @param [Hash] options
896
- # @param options [Symbol] :engine the engine to use. Currently supported engines are :cepstral and :unimrcp
897
- # @param options [String] :barge_in_digits digits to allow the TTS to be interrupted with
898
- #
899
- def speak(text, options = {})
900
- engine = options.delete(:engine) || AHN_CONFIG.asterisk.speech_engine || :none
901
- options[:interruptible] = false unless options.has_key?(:interruptible)
902
- SpeechEngines.send(engine, self, text.to_s, options)
903
- end
904
-
905
- module SpeechEngines
906
- class InvalidSpeechEngine < StandardError; end
907
-
908
- class << self
909
- def cepstral(call, text, options = {})
910
- # We need to aggressively escape commas so app_swift does not
911
- # think they are arguments.
912
- text.gsub! /,/, '\\\\,'
913
- command = ['Swift', text]
914
-
915
- if options[:interrupt_digits]
916
- ahn_log.agi.warn 'Cepstral does not support specifying interrupt digits'
917
- options[:interruptible] = true
918
- end
919
- # Wait for 1ms after speaking and collect no more than 1 digit
920
- command += [1, 1] if options[:interruptible]
921
- call.execute *command
922
- call.get_variable('SWIFT_DTMF')
923
- end
924
-
925
- def unimrcp(call, text, options = {})
926
- # app_unimrcp strips quotes, which will already be stripped by the AGI parser.
927
- # To work around this bug, we have to actually quote the arguments twice, once
928
- # in this method and again inside #execute.
929
- # Example from the logs:
930
- # AGI Input: EXEC MRCPSynth "<speak xmlns=\"http://www.w3.org/2001/10/synthesis\" version=\"1.0\" xml:lang=\"en-US\"> <voice name=\"Paul\"> <prosody rate=\"1.0\">Howdy, stranger. How are you today?</prosody> </voice> </speak>"
931
- # [Aug 3 13:39:02] VERBOSE[8495] logger.c: -- AGI Script Executing Application: (MRCPSynth) Options: (<speak xmlns="http://www.w3.org/2001/10/synthesis" version="1.0" xml:lang="en-US"> <voice name="Paul"> <prosody rate="1.0">Howdy, stranger. How are you today?</prosody> </voice> </speak>)
932
- # [Aug 3 13:39:02] NOTICE[8495] app_unimrcp.c: Text to synthesize is: <speak xmlns=http://www.w3.org/2001/10/synthesis version=1.0 xml:lang=en-US> <voice name=Paul> <prosody rate=1.0>Howdy, stranger. How are you today?</prosody> </voice> </speak>
933
- command = ['MRCPSynth', text.gsub(/["\\]/) { |m| "\\#{m}" }]
934
- args = []
935
- if options[:interrupt_digits]
936
- args << "i=#{options[:interrupt_digits]}"
937
- else
938
- args << "i=any" if options[:interruptible]
939
- end
940
- command << args.join('&') unless args.empty?
941
- value = call.inline_return_value(call.execute *command)
942
- value.to_i.chr unless value.nil?
943
- end
944
-
945
- def tropo(call, text, options = {})
946
- command = ['Ask', text]
947
- args = {}
948
- args[:terminator] = options[:interrupt_digits].split('').join(',') if options[:interrupt_digits]
949
- args[:bargein] = options[:interruptible] if options.has_key?(:interruptible)
950
- command << args.to_json unless args.empty?
951
- value = JSON.parse call.raw_response(*command).sub(/^200 result=/, '')
952
- value['interpretation']
953
- end
954
-
955
- def festival(text, call, options = {})
956
- raise NotImplementedError
957
- end
958
-
959
- def none(text, call, options = {})
960
- raise InvalidSpeechEngine, "No speech engine selected. You must specify one in your Adhearsion config file."
961
- end
962
-
963
- def method_missing(engine_name, text, options = {})
964
- raise InvalidSpeechEngine, "Unsupported speech engine #{engine_name} for speaking '#{text}'"
965
- end
966
- end
967
- end
968
-
969
- # A high-level way of enabling features you create/uncomment from features.conf.
970
- #
971
- # Certain Symbol features you enable (as defined in DYNAMIC_FEATURE_EXTENSIONS) have optional
972
- # arguments that you can also specify here. The usage examples show how to do this.
973
- #
974
- # Usage examples:
975
- #
976
- # enable_feature :attended_transfer # Enables "atxfer"
977
- #
978
- # enable_feature :attended_transfer, :context => "my_dial" # Enables "atxfer" and then
979
- # # sets "TRANSFER_CONTEXT" to :context's value
980
- #
981
- # enable_feature :blind_transfer, :context => 'my_dial' # Enables 'blindxfer' and sets TRANSFER_CONTEXT
982
- #
983
- # enable_feature "foobar" # Enables "foobar"
984
- #
985
- # enable_feature("dup"); enable_feature("dup") # Enables "dup" only once.
986
- def enable_feature(feature_name, optional_options=nil)
987
- if DYNAMIC_FEATURE_EXTENSIONS.has_key? feature_name
988
- instance_exec(optional_options, &DYNAMIC_FEATURE_EXTENSIONS[feature_name])
989
- else
990
- raise ArgumentError, "You cannot supply optional options when the feature name is " +
991
- "not internally recognized!" if optional_options
992
- extend_dynamic_features_with feature_name
993
- end
994
- end
995
-
996
- # Disables a feature name specified in features.conf. If you're disabling it, it was probably
997
- # set by enable_feature().
998
- #
999
- # @param [String] feature_name
1000
- def disable_feature(feature_name)
1001
- enabled_features_variable = variable 'DYNAMIC_FEATURES'
1002
- enabled_features = enabled_features_variable.split('#')
1003
- if enabled_features.include? feature_name
1004
- enabled_features.delete feature_name
1005
- variable 'DYNAMIC_FEATURES' => enabled_features.join('#')
1006
- end
1007
- end
1008
-
1009
- # Used to join a particular conference with the MeetMe application. To use MeetMe, be sure you
1010
- # have a proper timing device configured on your Asterisk box. MeetMe is Asterisk's built-in
1011
- # conferencing program.
1012
- #
1013
- # @param [String] conference_id
1014
- # @param [Hash] options
1015
- #
1016
- # @see http://www.voip-info.org/wiki-Asterisk+cmd+MeetMe Asterisk Meetme Application Information
1017
- def join(conference_id, options={})
1018
- conference_id = conference_id.to_s.scan(/\w/).join
1019
- command_flags = options[:options].to_s # This is a passthrough string straight to Asterisk
1020
- pin = options[:pin]
1021
- raise ArgumentError, "A conference PIN number must be numerical!" if pin && pin.to_s !~ /^\d+$/
1022
-
1023
- # To disable dynamic conference creation set :use_static_conf => true
1024
- use_static_conf = options.has_key?(:use_static_conf) ? options[:use_static_conf] : false
1025
-
1026
- # The 'd' option of MeetMe creates conferences dynamically.
1027
- command_flags += 'd' unless (command_flags.include?('d') or use_static_conf)
1028
-
1029
- execute "MeetMe", conference_id, command_flags, options[:pin]
1030
- end
1031
-
1032
- # Issue this command to access a channel variable that exists in the asterisk dialplan (i.e. extensions.conf)
1033
- # Use get_variable to pass information from other modules or high level configurations from the asterisk dialplan
1034
- # to the adhearsion dialplan.
1035
- #
1036
- # @param [String] variable_name
1037
- #
1038
- # @see: http://www.voip-info.org/wiki/view/get+variable Asterisk Get Variable
1039
- def get_variable(variable_name)
1040
- inline_result_with_return_value response "GET VARIABLE", variable_name
1041
- end
1042
-
1043
- # Pass information back to the asterisk dial plan.
1044
- #
1045
- # Keep in mind that the variables are not global variables. These variables only exist for the channel
1046
- # related to the call that is being serviced by the particular instance of your adhearsion application.
1047
- # You will not be able to pass information back to the asterisk dialplan for other instances of your adhearsion
1048
- # application to share. Once the channel is "hungup" then the variables are cleared and their information is gone.
1049
- #
1050
- # @param [String] variable_name
1051
- # @param [String] value
1052
- #
1053
- # @see http://www.voip-info.org/wiki/view/set+variable Asterisk Set Variable
1054
- def set_variable(variable_name, value)
1055
- response("SET VARIABLE", variable_name, value) == AGI_SUCCESSFUL_RESPONSE
1056
- end
1057
-
1058
- # Issue the command to add a custom SIP header to the current call channel
1059
- # example use: sip_add_header("x-ahn-test", "rubyrox")
1060
- #
1061
- # @param[String] the name of the SIP header
1062
- # @param[String] the value of the SIP header
1063
- #
1064
- # @return [String] the Asterisk response
1065
- #
1066
- # @see http://www.voip-info.org/wiki/index.php?page=Asterisk+cmd+SIPAddHeader Asterisk SIPAddHeader
1067
- def sip_add_header(header, value)
1068
- execute("SIPAddHeader", "#{header}: #{value}") == AGI_SUCCESSFUL_RESPONSE
1069
- end
1070
-
1071
- # Issue the command to fetch a SIP header from the current call channel
1072
- # example use: sip_get_header("x-ahn-test")
1073
- #
1074
- # @param[String] the name of the SIP header to get
1075
- #
1076
- # @return [String] the Asterisk response
1077
- #
1078
- # @see http://www.voip-info.org/wiki/index.php?page=Asterisk+cmd+SIPGetHeader Asterisk SIPGetHeader
1079
- def sip_get_header(header)
1080
- get_variable("SIP_HEADER(#{header})")
1081
- end
1082
- alias :sip_header :sip_get_header
1083
-
1084
- # Allows you to either set or get a channel variable from Asterisk.
1085
- # The method takes a hash key/value pair if you would like to set a variable
1086
- # Or a single string with the variable to get from Asterisk
1087
- def variable(*args)
1088
- if args.last.kind_of? Hash
1089
- assignments = args.pop
1090
- raise ArgumentError, "Can't mix variable setting and fetching!" if args.any?
1091
- assignments.each_pair do |key, value|
1092
- set_variable(key, value)
1093
- end
1094
- else
1095
- if args.size == 1
1096
- get_variable args.first
1097
- else
1098
- args.map { |var| get_variable(var) }
1099
- end
1100
- end
1101
- end
1102
-
1103
- # Send a caller to a voicemail box to leave a message.
1104
- #
1105
- # The method takes the mailbox_number of the user to leave a message for and a
1106
- # greeting_option that will determine which message gets played to the caller.
1107
- #
1108
- # @see http://www.voip-info.org/tiki-index.php?page=Asterisk+cmd+VoiceMail Asterisk Voicemail
1109
- def voicemail(*args)
1110
- options_hash = args.last.kind_of?(Hash) ? args.pop : {}
1111
- mailbox_number = args.shift
1112
- greeting_option = options_hash.delete(:greeting)
1113
- skip_option = options_hash.delete(:skip)
1114
- raise ArgumentError, 'You supplied too many arguments!' if mailbox_number && options_hash.any?
1115
- greeting_option = case greeting_option
1116
- when :busy then 'b'
1117
- when :unavailable then 'u'
1118
- when nil then nil
1119
- else raise ArgumentError, "Unrecognized greeting #{greeting_option}"
1120
- end
1121
- skip_option &&= 's'
1122
- options = "#{greeting_option}#{skip_option}"
1123
-
1124
- raise ArgumentError, "Mailbox cannot be blank!" if !mailbox_number.nil? && mailbox_number.blank?
1125
- number_with_context = if mailbox_number then mailbox_number else
1126
- raise ArgumentError, "You must supply ONE context name!" if options_hash.size != 1
1127
- context_name, mailboxes = options_hash.to_a.first
1128
- Array(mailboxes).map do |mailbox|
1129
- raise ArgumentError, "Mailbox numbers must be numerical!" unless mailbox.to_s =~ /^\d+$/
1130
- "#{mailbox}@#{context_name}"
1131
- end.join('&')
1132
- end
1133
- execute('voicemail', number_with_context, options)
1134
- case variable('VMSTATUS')
1135
- when 'SUCCESS' then true
1136
- when 'USEREXIT' then false
1137
- else nil
1138
- end
1139
- end
1140
-
1141
- # The voicemail_main method puts a caller into the voicemail system to fetch their voicemail
1142
- # or set options for their voicemail box.
1143
- #
1144
- # @param [Hash] options
1145
- #
1146
- # @see http://www.voip-info.org/wiki-Asterisk+cmd+VoiceMailMain Asterisk VoiceMailMain Command
1147
- def voicemail_main(options={})
1148
- mailbox, context, folder = options.values_at :mailbox, :context, :folder
1149
- authenticate = options.has_key?(:authenticate) ? options[:authenticate] : true
1150
-
1151
- folder = if folder
1152
- if folder.to_s =~ /^[\w_]+$/
1153
- "a(#{folder})"
1154
- else
1155
- raise ArgumentError, "Voicemail folder must be alphanumerical/underscore characters only!"
1156
- end
1157
- elsif folder == ''
1158
- raise "Folder name cannot be an empty String!"
1159
- else
1160
- nil
1161
- end
1162
-
1163
- real_mailbox = ""
1164
- real_mailbox << "#{mailbox}" unless mailbox.blank?
1165
- real_mailbox << "@#{context}" unless context.blank?
1166
-
1167
- real_options = ""
1168
- real_options << "s" if !authenticate
1169
- real_options << folder unless folder.blank?
1170
-
1171
- command_args = [real_mailbox]
1172
- command_args << real_options unless real_options.blank?
1173
- command_args.clear if command_args == [""]
1174
-
1175
- execute 'VoiceMailMain', *command_args
1176
- end
1177
-
1178
- def check_voicemail
1179
- ahn_log.agi.warn "THE check_voicemail() DIALPLAN METHOD WILL SOON BE DEPRECATED! CHANGE THIS TO voicemail_main() INSTEAD"
1180
- voicemail_main
1181
- end
1182
-
1183
- # Dial an extension or "phone number" in asterisk.
1184
- # Maps to the Asterisk DIAL command in the asterisk dialplan.
1185
- #
1186
- # @param [String] number represents the extension or "number" that asterisk should dial.
1187
- # Be careful to not just specify a number like 5001, 9095551001
1188
- # You must specify a properly formatted string as Asterisk would expect to use in order to understand
1189
- # whether the call should be dialed using SIP, IAX, or some other means.
1190
- #
1191
- # @param [Hash] options
1192
- #
1193
- # +:caller_id+ - the caller id number to be used when the call is placed. It is advised you properly adhere to the
1194
- # policy of VoIP termination providers with respect to caller id values.
1195
- #
1196
- # +:name+ - this is the name which should be passed with the caller ID information
1197
- # if :name=>"John Doe" and :caller_id => "444-333-1000" then the compelete CID and name would be "John Doe" <4443331000>
1198
- # support for caller id information varies from country to country and from one VoIP termination provider to another.
1199
- #
1200
- # +:for+ - this option can be thought of best as a timeout. i.e. timeout after :for if no one answers the call
1201
- # For example, dial("SIP/jay-desk-650&SIP/jay-desk-601&SIP/jay-desk-601-2", :for => 15.seconds, :caller_id => callerid)
1202
- # this call will timeout after 15 seconds if 1 of the 3 extensions being dialed do not pick prior to the 15 second time limit
1203
- #
1204
- # +:options+ - This is a string of options like "Tr" which are supported by the asterisk DIAL application.
1205
- # for a complete list of these options and their usage please check the link below.
1206
- #
1207
- # +:confirm+ - ?
1208
- #
1209
- # @example Make a call to the PSTN using my SIP provider for VoIP termination
1210
- # dial("SIP/19095551001@my.sip.voip.terminator.us")
1211
- #
1212
- # @example Make 3 Simulataneous calls to the SIP extensions separated by & symbols, try for 15 seconds and use the callerid
1213
- # for this call specified by the variable my_callerid
1214
- # dial "SIP/jay-desk-650&SIP/jay-desk-601&SIP/jay-desk-601-2", :for => 15.seconds, :caller_id => my_callerid
1215
- #
1216
- # @example Make a call using the IAX provider to the PSTN
1217
- # dial("IAX2/my.id@voipjet/19095551234", :name=>"John Doe", :caller_id=>"9095551234")
1218
- #
1219
- # @see http://www.voip-info.org/wiki-Asterisk+cmd+Dial Asterisk Dial Command
1220
- def dial(number, options={})
1221
- *recognized_options = :caller_id, :name, :for, :options, :confirm
1222
-
1223
- unrecognized_options = options.keys - recognized_options
1224
- raise ArgumentError, "Unknown dial options: #{unrecognized_options.to_sentence}" if unrecognized_options.any?
1225
- set_caller_id_name options[:name]
1226
- set_caller_id_number options[:caller_id]
1227
- confirm_option = dial_macro_option_compiler options[:confirm]
1228
- all_options = options[:options]
1229
- all_options = all_options ? all_options + confirm_option : confirm_option
1230
- execute "Dial", number, options[:for], all_options
1231
- end
1232
-
1233
-
1234
- # This implementation of dial() uses the experimental call routing DSL.
1235
- #
1236
- # def dial(number, options={})
1237
- # rules = callable_routes_for number
1238
- # return :no_route if rules.empty?
1239
- # call_attempt_status = nil
1240
- # rules.each do |provider|
1241
- #
1242
- # response = execute "Dial",
1243
- # provider.format_number_for_platform(number),
1244
- # timeout_from_dial_options(options),
1245
- # asterisk_options_from_dial_options(options)
1246
- #
1247
- # call_attempt_status = last_dial_status
1248
- # break if call_attempt_status == :answered
1249
- # end
1250
- # call_attempt_status
1251
- # end
1252
-
1253
-
1254
- # Speaks the digits given as an argument. For example, "123" is spoken as "one two three".
1255
- #
1256
- # @param [String] digits
1257
- def say_digits(digits)
1258
- execute "saydigits", validate_digits(digits)
1259
- end
1260
-
1261
- # Get the number of seconds the given block takes to execute. This
1262
- # is particularly useful in dialplans for tracking billable time. Note that
1263
- # if the call is hung up during the block, you will need to rescue the
1264
- # exception if you have some mission-critical logic after it with which
1265
- # you're recording this return-value.
1266
- #
1267
- # @return [Float] number of seconds taken for block to execute
1268
- def duration_of
1269
- start_time = Time.now
1270
- yield
1271
- Time.now - start_time
1272
- end
1273
-
1274
- #
1275
- # Play a sequence of files, stopping the playback if a digit is pressed.
1276
- #
1277
- # @return [String, nil] digit pressed, or nil if none
1278
- #
1279
- def interruptible_play(*files)
1280
- result = nil
1281
- files.flatten.each do |file|
1282
- begin
1283
- result = interruptible_play!(file)
1284
- rescue PlaybackError => e
1285
- # Ignore this exception and play the next file
1286
- ahn_log.agi.warn e.message
1287
- ensure
1288
- break if result
1289
- end
1290
- end
1291
- result
1292
- end
1293
-
1294
- #
1295
- # Same as {#interruptible_play}, but immediately raises an exception if a sound file cannot be played.
1296
- #
1297
- # @return (see #interruptible_play)
1298
- # @raise [Adhearsion::VoIP::PlaybackError] If a sound file cannot be played
1299
- def interruptible_play!(*files)
1300
- startpos = 0
1301
- files.flatten.each do |file|
1302
- result = stream_file_result_from response("STREAM FILE", file, "1234567890*#")
1303
- if result[:endpos].to_i <= startpos
1304
- raise Adhearsion::VoIP::PlaybackError, "The sound file could not opened to stream. The parsed response was #{result.inspect}"
1305
- end
1306
- return result[:digit] if result.has_key? :digit
1307
- end
1308
- nil
1309
- end
1310
-
1311
- ##
1312
- # Executes the SayPhonetic command. This command will read the text passed in
1313
- # out load using the NATO phonetic alphabet.
1314
- #
1315
- # @param [String] Passed in as the text to read aloud
1316
- #
1317
- # @see http://www.voip-info.org/wiki/view/Asterisk+cmd+SayPhonetic Asterisk SayPhonetic Command
1318
- def say_phonetic(text)
1319
- execute "sayphonetic", text
1320
- end
1321
-
1322
- ##
1323
- # Executes the SayAlpha command. This command will read the text passed in
1324
- # out loud, character-by-character.
1325
- #
1326
- # @param [String] Passed in as the text to read aloud
1327
- #
1328
- # @example Say "one a two dot pound"
1329
- # say_chars "1a2.#"
1330
- #
1331
- # @see http://www.voip-info.org/wiki/view/Asterisk+cmd+SayAlpha Asterisk SayPhonetic Command
1332
- def say_chars(text)
1333
- execute "sayalpha", text
1334
- end
1335
-
1336
- # Plays the given Date, Time, or Integer (seconds since epoch)
1337
- # using the given timezone and format.
1338
- #
1339
- # @param [Date|Time|DateTime] Time to be said.
1340
- # @param [Hash] Additional options to specify how exactly to say time specified.
1341
- #
1342
- # +:timezone+ - Sends a timezone to asterisk. See /usr/share/zoneinfo for a list. Defaults to the machine timezone.
1343
- # +:format+ - This is the format the time is to be said in. Defaults to "ABdY 'digits/at' IMp"
1344
- #
1345
- # @see http://www.voip-info.org/wiki/view/Asterisk+cmd+SayUnixTime
1346
- def play_time(*args)
1347
- argument, options = args.flatten
1348
- options ||= {}
1349
-
1350
- return false unless options.is_a? Hash
1351
-
1352
- timezone = options.delete(:timezone) || ''
1353
- format = options.delete(:format) || ''
1354
- epoch = case argument
1355
- when Time || DateTime
1356
- argument.to_i
1357
- when Date
1358
- format = 'BdY' unless format.present?
1359
- argument.to_time.to_i
1360
- end
1361
-
1362
- return false if epoch.nil?
1363
-
1364
- execute :sayunixtime, epoch, timezone, format
1365
- end
1366
-
1367
- protected
1368
-
1369
- # wait_for_digits waits for the input of digits based on the number of milliseconds
1370
- def wait_for_digit(timeout=-1)
1371
- timeout *= 1_000 if timeout != -1
1372
- result = result_digit_from response("WAIT FOR DIGIT", timeout.to_i)
1373
- (result == 0.chr) ? nil : result
1374
- end
1375
-
1376
- ##
1377
- # Deprecated name of interruptible_play(). This is a misspelling!
1378
- #
1379
- def interruptable_play(*files)
1380
- ahn_log.deprecation.warn 'Please change your code to use interruptible_play() instead. "interruptable" is a misspelling! interruptable_play() will work for now but will be deprecated in the future!'
1381
- interruptible_play(*files)
1382
- end
1383
-
1384
- # allows setting of the callerid number of the call
1385
- def set_caller_id_number(caller_id_num)
1386
- return unless caller_id_num
1387
- raise ArgumentError, "Caller ID must be numeric" if caller_id_num.to_s !~ /^\+?\d+$/
1388
- variable "CALLERID(num)" => caller_id_num
1389
- end
1390
-
1391
- # allows the setting of the callerid name of the call
1392
- def set_caller_id_name(caller_id_name)
1393
- return unless caller_id_name
1394
- variable "CALLERID(name)" => caller_id_name
1395
- end
1396
-
1397
- def timeout_from_dial_options(options)
1398
- options[:for] || options[:timeout]
1399
- end
1400
-
1401
- def asterisk_options_from_dial_options(options)
1402
- # TODO: Will become much more sophisticated soon to handle callerid, etc
1403
- options[:options]
1404
- end
1405
-
1406
- def dial_macro_option_compiler(confirm_argument_value)
1407
- defaults = { :macro => 'ahn_dial_confirmer',
1408
- :timeout => 20.seconds,
1409
- :play => "beep",
1410
- :key => '#' }
1411
-
1412
- case confirm_argument_value
1413
- when true
1414
- DialPlan::ConfirmationManager.encode_hash_for_dial_macro_argument(defaults)
1415
- when false, nil
1416
- ''
1417
- when Proc
1418
- raise NotImplementedError, "Coming in the future, you can do :confirm => my_context."
1419
-
1420
- when Hash
1421
- options = defaults.merge confirm_argument_value
1422
- if((confirm_argument_value.keys - defaults.keys).any?)
1423
- raise ArgumentError, "Known options: #{defaults.keys.to_sentence}"
1424
- end
1425
- raise ArgumentError, "Bad macro name!" unless options[:macro].to_s =~ /^[\w_]+$/
1426
- options[:timeout] = case options[:timeout]
1427
- when Fixnum, ActiveSupport::Duration
1428
- options[:timeout]
1429
- when String
1430
- raise ArgumentError, "Timeout must be numerical!" unless options[:timeout] =~ /^\d+$/
1431
- options[:timeout].to_i
1432
- when :none
1433
- 0
1434
- else
1435
- raise ArgumentError, "Unrecognized :timeout! #{options[:timeout].inspect}"
1436
- end
1437
- raise ArgumentError, "Unrecognized DTMF key: #{options[:key]}" unless options[:key].to_s =~ /^[\d#*]$/
1438
- options[:play] = Array(options[:play]).join('++')
1439
- DialPlan::ConfirmationManager.encode_hash_for_dial_macro_argument options
1440
-
1441
- else
1442
- raise ArgumentError, "Unrecognized :confirm option: #{confirm_argument_value.inspect}!"
1443
- end
1444
- end
1445
-
1446
- def result_digit_from(response_string)
1447
- raise ArgumentError, "Can't coerce nil into AGI response! This could be a bug!" unless response_string
1448
- digit = response_string[/^#{response_prefix}(-?\d+(\.\d+)?)/,1]
1449
- digit.to_i.chr if digit && digit.to_s != "-1"
1450
- end
1451
-
1452
- def stream_file_result_from(response_string)
1453
- raise ArgumentError, "Can't coerce nil into AGI response! This could be a bug!" unless response_string
1454
- params = {}
1455
- digit, endpos = response_string.match(/^#{response_prefix}(-?\d+) endpos=(\d+)/).values_at 1, 2
1456
- params[:digit] = digit.to_i.chr unless digit == "0" || digit.to_s == "-1"
1457
- params[:endpos] = endpos.to_i if endpos
1458
- params
1459
- end
1460
-
1461
- def extract_input_from(result)
1462
- return false if error?(result)
1463
- # return false if input_timed_out?(result)
1464
-
1465
- # This regexp doesn't match if there was a timeout with no
1466
- # inputted digits, therefore returning nil.
1467
-
1468
- result[/^#{response_prefix}([\d*]+)/, 1]
1469
- end
1470
-
1471
- def extract_variable_from(result)
1472
- return false if error?(result)
1473
- result[/^#{response_prefix}1 \((.+)\)/, 1]
1474
- end
1475
-
1476
- def get_dial_status
1477
- dial_status = variable('DIALSTATUS')
1478
- dial_status ? dial_status.downcase.to_sym : :cancelled
1479
- end
1480
-
1481
-
1482
- def play_numeric(argument)
1483
- if argument.kind_of?(Numeric) || argument =~ /^\d+$/
1484
- execute(:saynumber, argument)
1485
- end
1486
- end
1487
-
1488
- # Instruct Asterisk to play a sound file to the channel
1489
- def play_soundfile(argument)
1490
- execute(:playback, argument)
1491
- get_variable('PLAYBACKSTATUS') == PLAYBACK_SUCCESS
1492
- end
1493
- alias :play_string :play_soundfile
1494
-
1495
- # Like play_soundfile, but this will raise Exceptions if there's a problem.
1496
- #
1497
- # @return [true]
1498
- # @raise [Adhearsion::VoIP::PlaybackError] If a sound file cannot be played
1499
- # @see http://www.voip-info.org/wiki/view/Asterisk+cmd+Playback More information on the Asterisk Playback command
1500
- def play_soundfile!(argument)
1501
- response = execute :playback, argument
1502
- playback = get_variable 'PLAYBACKSTATUS'
1503
- return true if playback == PLAYBACK_SUCCESS
1504
- raise PlaybackError, "Playback failed with PLAYBACKSTATUS: #{playback.inspect}. The raw response was #{response.inspect}."
1505
- end
1506
- alias :play_string! :play_soundfile!
1507
-
1508
- def play_sound_files_for_menu(menu_instance, sound_files)
1509
- digit = nil
1510
- if sound_files.any? && menu_instance.digit_buffer_empty?
1511
- digit = interruptible_play(*sound_files)
1512
- end
1513
- digit || wait_for_digit(menu_instance.timeout)
1514
- end
1515
-
1516
- def extend_dynamic_features_with(feature_name)
1517
- current_variable = variable("DYNAMIC_FEATURES") || ''
1518
- enabled_features = current_variable.split '#'
1519
- unless enabled_features.include? feature_name
1520
- enabled_features << feature_name
1521
- variable "DYNAMIC_FEATURES" => enabled_features.join('#')
1522
- end
1523
- end
1524
-
1525
- def jump_to_context_with_name(context_name)
1526
- context_lambda = lookup_context_with_name context_name
1527
- raise Adhearsion::VoIP::DSL::Dialplan::ControlPassingException.new(context_lambda)
1528
- end
1529
-
1530
- def lookup_context_with_name(context_name)
1531
- begin
1532
- send context_name
1533
- rescue NameError
1534
- raise Adhearsion::VoIP::DSL::Dialplan::ContextNotFoundException
1535
- end
1536
- end
1537
-
1538
- def redefine_extension_to_be(new_extension)
1539
- new_extension = Adhearsion::VoIP::DSL::PhoneNumber.new new_extension
1540
- meta_def(:extension) { new_extension }
1541
- end
1542
-
1543
- def to_pbx
1544
- io
1545
- end
1546
-
1547
- def from_pbx
1548
- io
1549
- end
1550
-
1551
- def validate_digits(digits)
1552
- digits.to_s.tap do |digits_as_string|
1553
- raise ArgumentError, "Can only be called with valid digits!" unless digits_as_string =~ /^[0-9*#-]+$/
1554
- end
1555
- end
1556
-
1557
- def error?(result)
1558
- result.to_s[/^#{response_prefix}(?:-\d+)/]
1559
- end
1560
-
1561
- # timeout with pressed digits: 200 result=<digits> (timeout)
1562
- # timeout without pressed digits: 200 result= (timeout)
1563
- # @see http://www.voip-info.org/wiki/view/get+data AGI Get Data
1564
- def input_timed_out?(result)
1565
- result.starts_with?(response_prefix) && result.ends_with?('(timeout)')
1566
- end
1567
-
1568
- def io
1569
- call.io
1570
- end
1571
-
1572
- def response_prefix
1573
- RESPONSE_PREFIX
1574
- end
1575
-
1576
- class QueueProxy
1577
-
1578
- class << self
1579
-
1580
- def format_join_hash_key_arguments(options)
1581
-
1582
- bad_argument = lambda do |(key, value)|
1583
- raise ArgumentError, "Unrecognize value for #{key.inspect} -- #{value.inspect}"
1584
- end
1585
-
1586
- # Direct Queue() arguments:
1587
- timeout = options.delete :timeout
1588
- announcement = options.delete :announce
1589
-
1590
- # Terse single-character options
1591
- ring_style = options.delete :play
1592
- allow_hangup = options.delete :allow_hangup
1593
- allow_transfer = options.delete :allow_transfer
1594
- agi = options.delete :agi
1595
-
1596
- raise ArgumentError, "Unrecognized args to join!: #{options.inspect}" if options.any?
1597
-
1598
- ring_style = case ring_style
1599
- when :ringing then 'r'
1600
- when :music then ''
1601
- when nil
1602
- else bad_argument[:play => ring_style]
1603
- end.to_s
1604
-
1605
- allow_hangup = case allow_hangup
1606
- when :caller then 'H'
1607
- when :agent then 'h'
1608
- when :everyone then 'Hh'
1609
- when nil
1610
- else bad_argument[:allow_hangup => allow_hangup]
1611
- end.to_s
1612
-
1613
- allow_transfer = case allow_transfer
1614
- when :caller then 'T'
1615
- when :agent then 't'
1616
- when :everyone then 'Tt'
1617
- when nil
1618
- else bad_argument[:allow_transfer => allow_transfer]
1619
- end.to_s
1620
-
1621
- terse_character_options = ring_style + allow_transfer + allow_hangup
1622
-
1623
- [terse_character_options, '', announcement, timeout, agi].map(&:to_s)
1624
- end
1625
-
1626
- end
1627
-
1628
- attr_reader :name, :environment
1629
- def initialize(name, environment)
1630
- @name, @environment = name, environment
1631
- end
1632
-
1633
- # Makes the current channel join the queue.
1634
- #
1635
- # @param [Hash] options
1636
- #
1637
- # :timeout - The number of seconds to wait for an agent to answer
1638
- # :play - Can be :ringing or :music.
1639
- # :announce - A sound file to play instead of the normal queue announcement.
1640
- # :allow_transfer - Can be :caller, :agent, or :everyone. Allow someone to transfer the call.
1641
- # :allow_hangup - Can be :caller, :agent, or :everyone. Allow someone to hangup with the * key.
1642
- # :agi - An AGI script to be called on the calling parties channel just before being connected.
1643
- #
1644
- # @example
1645
- # queue('sales').join!
1646
- # @example
1647
- # queue('sales').join! :timeout => 1.minute
1648
- # @example
1649
- # queue('sales').join! :play => :music
1650
- # @example
1651
- # queue('sales').join! :play => :ringing
1652
- # @example
1653
- # queue('sales').join! :announce => "custom/special-queue-announcement"
1654
- # @example
1655
- # queue('sales').join! :allow_transfer => :caller
1656
- # @example
1657
- # queue('sales').join! :allow_transfer => :agent
1658
- # @example
1659
- # queue('sales').join! :allow_hangup => :caller
1660
- # @example
1661
- # queue('sales').join! :allow_hangup => :agent
1662
- # @example
1663
- # queue('sales').join! :allow_hangup => :everyone
1664
- # @example
1665
- # queue('sales').join! :agi => 'agi://localhost/sales_queue_callback'
1666
- # @example
1667
- # queue('sales').join! :allow_transfer => :agent, :timeout => 30.seconds,
1668
- def join!(options={})
1669
- environment.execute("queue", name, *self.class.format_join_hash_key_arguments(options))
1670
- normalize_queue_status_variable environment.variable("QUEUESTATUS")
1671
- end
1672
-
1673
- # Get the agents associated with a queue
1674
- #
1675
- # @param [Hash] options
1676
- # @return [QueueAgentsListProxy]
1677
- def agents(options={})
1678
- cached = options.has_key?(:cache) ? options.delete(:cache) : true
1679
- raise ArgumentError, "Unrecognized arguments to agents(): #{options.inspect}" if options.keys.any?
1680
- if cached
1681
- @cached_proxy ||= QueueAgentsListProxy.new(self, true)
1682
- else
1683
- @uncached_proxy ||= QueueAgentsListProxy.new(self, false)
1684
- end
1685
- end
1686
-
1687
- # Check how many channels are waiting in the queue
1688
- # @return [Integer]
1689
- # @raise QueueDoesNotExistError
1690
- def waiting_count
1691
- raise QueueDoesNotExistError.new(name) unless exists?
1692
- environment.variable("QUEUE_WAITING_COUNT(#{name})").to_i
1693
- end
1694
-
1695
- # Check whether the waiting count is zero
1696
- # @return [Boolean]
1697
- def empty?
1698
- waiting_count == 0
1699
- end
1700
-
1701
- # Check whether any calls are waiting in the queue
1702
- # @return [Boolean]
1703
- def any?
1704
- waiting_count > 0
1705
- end
1706
-
1707
- # Check whether a queue exists/is defined in Asterisk
1708
- # @return [Boolean]
1709
- def exists?
1710
- environment.execute('RemoveQueueMember', name, 'SIP/AdhearsionQueueExistenceCheck')
1711
- environment.variable("RQMSTATUS") != 'NOSUCHQUEUE'
1712
- end
1713
-
1714
- private
1715
-
1716
- # Ensure the queue exists by interpreting the QUEUESTATUS variable
1717
- #
1718
- # According to http://www.voip-info.org/wiki/view/Asterisk+cmd+Queue
1719
- # possible values are:
1720
- #
1721
- # TIMEOUT => :timeout
1722
- # FULL => :full
1723
- # JOINEMPTY => :joinempty
1724
- # LEAVEEMPTY => :leaveempty
1725
- # JOINUNAVAIL => :joinunavail
1726
- # LEAVEUNAVAIL => :leaveunavail
1727
- # CONTINUE => :continue
1728
- #
1729
- # If the QUEUESTATUS variable is not set the call was successfully connected,
1730
- # and Adhearsion will return :completed.
1731
- #
1732
- # @param [String] QUEUESTATUS variable from Asterisk
1733
- # @return [Symbol] Symbolized version of QUEUESTATUS
1734
- # @raise QueueDoesNotExistError
1735
- def normalize_queue_status_variable(variable)
1736
- variable = "COMPLETED" if variable.nil?
1737
- variable.downcase.to_sym
1738
- end
1739
-
1740
- class QueueAgentsListProxy
1741
-
1742
- include Enumerable
1743
-
1744
- attr_reader :proxy, :agents
1745
- def initialize(proxy, cached=false)
1746
- @proxy = proxy
1747
- @cached = cached
1748
- end
1749
-
1750
- def count
1751
- if cached? && @cached_count
1752
- @cached_count
1753
- else
1754
- @cached_count = proxy.environment.variable("QUEUE_MEMBER_COUNT(#{proxy.name})").to_i
1755
- end
1756
- end
1757
- alias size count
1758
- alias length count
1759
-
1760
- # @param [Hash] args
1761
- # :name value will be viewable in the queue_log
1762
- # :penalty is the penalty assigned to this agent for answering calls on this queue
1763
- def new(*args)
1764
-
1765
- options = args.last.kind_of?(Hash) ? args.pop : {}
1766
- interface = args.shift
1767
-
1768
- raise ArgumentError, "You must specify an interface to add." if interface.nil?
1769
- raise ArgumentError, "You may only supply an interface and a Hash argument!" if args.any?
1770
-
1771
- penalty = options.delete(:penalty) || ''
1772
- name = options.delete(:name) || ''
1773
- state_interface = options.delete(:state_interface) || ''
1774
-
1775
- raise ArgumentError, "Unrecognized argument(s): #{options.inspect}" if options.any?
1776
-
1777
- proxy.environment.execute("AddQueueMember", proxy.name, interface, penalty, '', name, state_interface)
1778
-
1779
- added = case proxy.environment.variable("AQMSTATUS")
1780
- when "ADDED" then true
1781
- when "MEMBERALREADY" then false
1782
- when "NOSUCHQUEUE" then raise QueueDoesNotExistError.new(proxy.name)
1783
- else
1784
- raise "UNRECOGNIZED AQMSTATUS VALUE!"
1785
- end
1786
-
1787
- if added
1788
- check_agent_cache!
1789
- AgentProxy.new(interface, proxy).tap do |agent_proxy|
1790
- @agents << agent_proxy
1791
- end
1792
- else
1793
- false
1794
- end
1795
- end
1796
-
1797
- # Logs a pre-defined agent into this queue and waits for calls. Pass in :silent => true to stop
1798
- # the message which says "Agent logged in".
1799
- def login!(*args)
1800
- options = args.last.kind_of?(Hash) ? args.pop : {}
1801
-
1802
- silent = options.delete(:silent).equal?(false) ? '' : 's'
1803
- id = args.shift
1804
- id &&= AgentProxy.id_from_agent_channel(id)
1805
- raise ArgumentError, "Unrecognized Hash options to login(): #{options.inspect}" if options.any?
1806
- raise ArgumentError, "Unrecognized argument to login(): #{args.inspect}" if args.any?
1807
-
1808
- proxy.environment.execute('AgentLogin', id, silent)
1809
- end
1810
-
1811
- # Removes the current channel from this queue
1812
- def logout!
1813
- # TODO: DRY this up. Repeated in the AgentProxy...
1814
- proxy.environment.execute 'RemoveQueueMember', proxy.name
1815
- case proxy.environment.variable("RQMSTATUS")
1816
- when "REMOVED" then true
1817
- when "NOTINQUEUE" then false
1818
- when "NOSUCHQUEUE"
1819
- raise QueueDoesNotExistError.new(proxy.name)
1820
- else
1821
- raise "Unrecognized RQMSTATUS variable!"
1822
- end
1823
- end
1824
-
1825
- def each(&block)
1826
- check_agent_cache!
1827
- agents.each(&block)
1828
- end
1829
-
1830
- def first
1831
- check_agent_cache!
1832
- agents.first
1833
- end
1834
-
1835
- def last
1836
- check_agent_cache!
1837
- agents.last
1838
- end
1839
-
1840
- def cached?
1841
- @cached
1842
- end
1843
-
1844
- def to_a
1845
- check_agent_cache!
1846
- @agents
1847
- end
1848
-
1849
- private
1850
-
1851
- def check_agent_cache!
1852
- if cached?
1853
- load_agents! unless agents
1854
- else
1855
- load_agents!
1856
- end
1857
- end
1858
-
1859
- def load_agents!
1860
- raw_data = proxy.environment.variable "QUEUE_MEMBER_LIST(#{proxy.name})"
1861
- @agents = raw_data.split(',').map(&:strip).reject(&:empty?).map do |agent|
1862
- AgentProxy.new(agent, proxy)
1863
- end
1864
- @cached_count = @agents.size
1865
- end
1866
-
1867
- end
1868
-
1869
- class AgentProxy
1870
-
1871
- SUPPORTED_METADATA_NAMES = %w[status password name mohclass exten channel] unless defined? SUPPORTED_METADATA_NAMES
1872
-
1873
- class << self
1874
- def id_from_agent_channel(id)
1875
- id = id.to_s
1876
- id.starts_with?('Agent/') ? id[%r[^Agent/(.+)$],1] : id
1877
- end
1878
- end
1879
-
1880
- attr_reader :interface, :proxy, :queue_name, :id
1881
- def initialize(interface, proxy)
1882
- @interface = interface
1883
- @id = self.class.id_from_agent_channel interface
1884
- @proxy = proxy
1885
- @queue_name = proxy.name
1886
- end
1887
-
1888
- def remove!
1889
- proxy.environment.execute 'RemoveQueueMember', queue_name, interface
1890
- case proxy.environment.variable("RQMSTATUS")
1891
- when "REMOVED" then true
1892
- when "NOTINQUEUE" then false
1893
- when "NOSUCHQUEUE"
1894
- raise QueueDoesNotExistError.new(queue_name)
1895
- else
1896
- raise "Unrecognized RQMSTATUS variable!"
1897
- end
1898
- end
1899
-
1900
- # Pauses the given agent for this queue only. If you wish to pause this agent
1901
- # for all queues, pass in :everywhere => true. Returns true if the agent was
1902
- # successfully paused and false if the agent was not found.
1903
- def pause!(options={})
1904
- everywhere = options.delete(:everywhere)
1905
- args = [(everywhere ? nil : queue_name), interface]
1906
- proxy.environment.execute('PauseQueueMember', *args)
1907
- case proxy.environment.variable("PQMSTATUS")
1908
- when "PAUSED" then true
1909
- when "NOTFOUND" then false
1910
- else
1911
- raise "Unrecognized PQMSTATUS value!"
1912
- end
1913
- end
1914
-
1915
- # Pauses the given agent for this queue only. If you wish to pause this agent
1916
- # for all queues, pass in :everywhere => true. Returns true if the agent was
1917
- # successfully paused and false if the agent was not found.
1918
- def unpause!(options={})
1919
- everywhere = options.delete(:everywhere)
1920
- args = [(everywhere ? nil : queue_name), interface]
1921
- proxy.environment.execute('UnpauseQueueMember', *args)
1922
- case proxy.environment.variable("UPQMSTATUS")
1923
- when "UNPAUSED" then true
1924
- when "NOTFOUND" then false
1925
- else
1926
- raise "Unrecognized UPQMSTATUS value!"
1927
- end
1928
- end
1929
-
1930
- # Returns true/false depending on whether this agent is logged in.
1931
- def logged_in?
1932
- status == 'LOGGEDIN'
1933
- end
1934
-
1935
- private
1936
-
1937
- def status
1938
- agent_metadata 'status'
1939
- end
1940
-
1941
- def agent_metadata(data_name)
1942
- data_name = data_name.to_s.downcase
1943
- raise ArgumentError, "unrecognized agent metadata name #{data_name}" unless SUPPORTED_METADATA_NAMES.include? data_name
1944
- proxy.environment.variable "AGENT(#{id}:#{data_name})"
1945
- end
1946
-
1947
- end
1948
-
1949
- class QueueDoesNotExistError < StandardError
1950
- def initialize(queue_name)
1951
- super "Queue #{queue_name} does not exist!"
1952
- end
1953
- end
1954
-
1955
- end
1956
-
1957
- module MenuDigitResponse
1958
- def timed_out?
1959
- eql? 0.chr
1960
- end
1961
- end
1962
-
1963
- end
1964
- end
1965
- end
1966
- end