adhearsion 1.2.6 → 2.0.0.alpha1

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