adhearsion 0.8.3 → 0.8.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data/CHANGELOG +11 -2
  2. data/EVENTS +1 -1
  3. data/Rakefile +4 -4
  4. data/adhearsion.gemspec +8 -4
  5. data/app_generators/ahn/USAGE +3 -3
  6. data/app_generators/ahn/ahn_generator.rb +11 -11
  7. data/app_generators/ahn/templates/components/ami_remote/ami_remote.rb +3 -3
  8. data/app_generators/ahn/templates/components/disabled/restful_rpc/example-client.rb +6 -6
  9. data/app_generators/ahn/templates/components/disabled/restful_rpc/restful_rpc.rb +16 -16
  10. data/app_generators/ahn/templates/components/disabled/restful_rpc/spec/restful_rpc_spec.rb +42 -42
  11. data/app_generators/ahn/templates/components/disabled/sandbox/sandbox.rb +11 -11
  12. data/app_generators/ahn/templates/components/disabled/stomp_gateway/README.markdown +1 -1
  13. data/app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.rb +6 -6
  14. data/app_generators/ahn/templates/components/simon_game/simon_game.rb +4 -4
  15. data/app_generators/ahn/templates/config/startup.rb +31 -16
  16. data/bin/ahn +3 -3
  17. data/bin/ahnctl +8 -8
  18. data/bin/jahn +3 -3
  19. data/examples/asterisk_manager_interface/standalone.rb +2 -2
  20. data/lib/adhearsion.rb +4 -2
  21. data/lib/adhearsion/cli.rb +31 -31
  22. data/lib/adhearsion/component_manager.rb +39 -39
  23. data/lib/adhearsion/component_manager/component_tester.rb +14 -14
  24. data/lib/adhearsion/component_manager/spec_framework.rb +1 -1
  25. data/lib/adhearsion/events_support.rb +12 -12
  26. data/lib/adhearsion/foundation/blank_slate.rb +1 -1
  27. data/lib/adhearsion/foundation/custom_daemonizer.rb +2 -2
  28. data/lib/adhearsion/foundation/event_socket.rb +26 -26
  29. data/lib/adhearsion/foundation/future_resource.rb +6 -6
  30. data/lib/adhearsion/foundation/metaprogramming.rb +2 -2
  31. data/lib/adhearsion/foundation/numeric.rb +3 -3
  32. data/lib/adhearsion/foundation/relationship_properties.rb +7 -7
  33. data/lib/adhearsion/foundation/string.rb +8 -8
  34. data/lib/adhearsion/foundation/synchronized_hash.rb +8 -8
  35. data/lib/adhearsion/host_definitions.rb +16 -16
  36. data/lib/adhearsion/initializer.rb +74 -65
  37. data/lib/adhearsion/initializer/asterisk.rb +15 -9
  38. data/lib/adhearsion/initializer/configuration.rb +54 -39
  39. data/lib/adhearsion/initializer/database.rb +4 -4
  40. data/lib/adhearsion/initializer/drb.rb +6 -6
  41. data/lib/adhearsion/initializer/freeswitch.rb +1 -1
  42. data/lib/adhearsion/initializer/ldap.rb +51 -0
  43. data/lib/adhearsion/initializer/rails.rb +8 -8
  44. data/lib/adhearsion/logging.rb +16 -16
  45. data/lib/adhearsion/tasks/deprecations.rb +12 -12
  46. data/lib/adhearsion/tasks/generating.rb +2 -2
  47. data/lib/adhearsion/tasks/testing.rb +7 -7
  48. data/lib/adhearsion/version.rb +1 -1
  49. data/lib/adhearsion/voip/asterisk/agi_server.rb +44 -14
  50. data/lib/adhearsion/voip/asterisk/commands.rb +281 -237
  51. data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +8 -8
  52. data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +14 -14
  53. data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +16 -16
  54. data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +39 -39
  55. data/lib/adhearsion/voip/asterisk/config_manager.rb +13 -13
  56. data/lib/adhearsion/voip/asterisk/manager_interface.rb +91 -87
  57. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rb +739 -739
  58. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb +60 -60
  59. data/lib/adhearsion/voip/asterisk/manager_interface/ami_messages.rb +16 -16
  60. data/lib/adhearsion/voip/asterisk/manager_interface/ami_protocol_lexer_machine.rl +1 -1
  61. data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +13 -13
  62. data/lib/adhearsion/voip/asterisk/super_manager.rb +3 -3
  63. data/lib/adhearsion/voip/call.rb +101 -64
  64. data/lib/adhearsion/voip/call_routing.rb +9 -9
  65. data/lib/adhearsion/voip/constants.rb +7 -7
  66. data/lib/adhearsion/voip/conveniences.rb +1 -1
  67. data/lib/adhearsion/voip/dial_plan.rb +42 -40
  68. data/lib/adhearsion/voip/dsl/dialing_dsl.rb +27 -27
  69. data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +1 -1
  70. data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +6 -6
  71. data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +17 -17
  72. data/lib/adhearsion/voip/dsl/dialplan/parser.rb +7 -7
  73. data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +2 -2
  74. data/lib/adhearsion/voip/dsl/numerical_string.rb +3 -3
  75. data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +7 -7
  76. data/lib/adhearsion/voip/freeswitch/event_handler.rb +1 -1
  77. data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +20 -20
  78. data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +5 -5
  79. data/lib/adhearsion/voip/freeswitch/oes_server.rb +33 -33
  80. data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +1 -1
  81. data/lib/adhearsion/voip/menu_state_machine/matchers.rb +2 -2
  82. data/lib/adhearsion/voip/menu_state_machine/menu_class.rb +9 -9
  83. data/lib/theatre.rb +18 -18
  84. data/lib/theatre/callback_definition_loader.rb +17 -17
  85. data/lib/theatre/guid.rb +6 -6
  86. data/lib/theatre/invocation.rb +19 -19
  87. data/lib/theatre/namespace_manager.rb +28 -28
  88. metadata +55 -14
@@ -2,11 +2,11 @@ require 'log4r'
2
2
 
3
3
  module Adhearsion
4
4
  module Logging
5
-
5
+
6
6
  @@logging_level_lock = Mutex.new
7
-
7
+
8
8
  class << self
9
-
9
+
10
10
  def silence!
11
11
  self.logging_level = :fatal
12
12
  end
@@ -14,7 +14,7 @@ module Adhearsion
14
14
  def unsilence!
15
15
  self.logging_level = :info
16
16
  end
17
-
17
+
18
18
  def logging_level=(new_logging_level)
19
19
  new_logging_level = Log4r.const_get(new_logging_level.to_s.upcase)
20
20
  @@logging_level_lock.synchronize do
@@ -24,44 +24,44 @@ module Adhearsion
24
24
  end
25
25
  end
26
26
  end
27
-
27
+
28
28
  def logging_level
29
29
  @@logging_level_lock.synchronize do
30
30
  return @@logging_level ||= Log4r::INFO
31
31
  end
32
32
  end
33
33
  end
34
-
34
+
35
35
  class AdhearsionLogger < Log4r::Logger
36
-
36
+
37
37
  @@outputters = [Log4r::Outputter.stdout]
38
-
38
+
39
39
  class << self
40
40
  def outputters
41
41
  @@outputters
42
42
  end
43
-
43
+
44
44
  def outputters=(other)
45
45
  @@outputters = other
46
46
  end
47
47
  end
48
-
48
+
49
49
  def initialize(*args)
50
50
  super
51
51
  redefine_outputters
52
52
  end
53
-
53
+
54
54
  def redefine_outputters
55
55
  self.outputters = @@outputters
56
56
  end
57
-
57
+
58
58
  def method_missing(logger_name, *args, &block)
59
59
  define_logging_method(logger_name, self.class.new(logger_name.to_s))
60
60
  send(logger_name, *args, &block)
61
61
  end
62
-
62
+
63
63
  private
64
-
64
+
65
65
  def define_logging_method(name, logger)
66
66
  # Can't use Module#define_method() because blocks in Ruby 1.8.x can't
67
67
  # have their own block arguments.
@@ -77,9 +77,9 @@ module Adhearsion
77
77
  CODE
78
78
  end
79
79
  end
80
-
80
+
81
81
  DefaultAdhearsionLogger = AdhearsionLogger.new 'ahn'
82
-
82
+
83
83
  end
84
84
  end
85
85
 
@@ -12,47 +12,47 @@ This has been deprecated. The new format is this:
12
12
  paths:
13
13
  models: {models,gui/app/models}/*.rb
14
14
 
15
- This Rake task will fix your .ahnrc if you have
15
+ This Rake task will fix your .ahnrc if you have
16
16
  DESC
17
17
  task :fix_ahnrc_path_format do
18
18
  puts "\nThis will remove all comments from your .ahnrc file. A backup will be created as .ahnrc.backup."
19
19
  puts "If you wish to do this manually to preserve your comments, simply overwrite .ahnrc with .ahnrc.backup"
20
20
  puts "and apply the change manually."
21
- puts
22
-
21
+ puts
22
+
23
23
  require 'fileutils'
24
24
  require 'yaml'
25
-
25
+
26
26
  ahnrc_file = File.expand_path(".ahnrc")
27
-
27
+
28
28
  FileUtils.cp ahnrc_file, ahnrc_file + ".backup"
29
29
  ahnrc_contents = YAML.load_file ahnrc_file
30
-
30
+
31
31
  abort '.ahnrc does not have a "paths" section!' unless ahnrc_contents.has_key? "paths"
32
-
32
+
33
33
  paths = ahnrc_contents["paths"]
34
34
  paths.clone.each_pair do |key,value|
35
35
  if value.kind_of?(Hash)
36
36
  if value.has_key?("directory") || value.has_key?("pattern")
37
37
  directory, pattern = value.values_at "directory", "pattern"
38
38
  new_path = "#{directory}/#{pattern}"
39
-
39
+
40
40
  puts "!!! CHANGING KEY #{key.inspect}!"
41
41
  puts "!!! NEW: #{new_path.inspect}"
42
42
  puts "!!! OLD:\n#{{key => value}.to_yaml.sub("---", "")}\n\n"
43
-
43
+
44
44
  paths[key] = new_path
45
45
  end
46
46
  end
47
47
  end
48
-
48
+
49
49
  ahnrc_contents["paths"] = paths
50
50
  new_yaml = ahnrc_contents.to_yaml.gsub("--- \n", "")
51
-
51
+
52
52
  puts "New .ahnrc file:\n" + ("#" * 25) + "\n"
53
53
  puts new_yaml
54
54
  puts '#' * 25
55
-
55
+
56
56
  File.open(ahnrc_file, "w") { |file| file.puts new_yaml }
57
57
  puts "Wrote to .ahnrc. Done!"
58
58
  end
@@ -3,9 +3,9 @@ namespace:create do
3
3
  task:war do
4
4
  # Hmm, this will is a tough one
5
5
  end
6
-
6
+
7
7
  task:rails_plugin do
8
-
8
+
9
9
  end
10
10
 
11
11
  task:migration do
@@ -7,14 +7,14 @@ namespace:test do
7
7
  setup_and_execute(component_name)
8
8
  end
9
9
  end
10
-
11
- private
12
-
10
+
11
+ private
12
+
13
13
  def setup_and_execute(component_path)
14
14
  task = create_test_task_for(component_path)
15
15
  Rake::Task[task.name].execute
16
16
  end
17
-
17
+
18
18
  def create_test_task_for(component_path)
19
19
  Rake::TestTask.new(task_name_for(component_path)) do |t|
20
20
  t.libs = ["lib", "test"].map{|subdir| File.join(component_path, subdir)}
@@ -22,15 +22,15 @@ namespace:test do
22
22
  t.verbose = true
23
23
  end
24
24
  end
25
-
25
+
26
26
  def task_name_for(component_path)
27
27
  "test_#{component_path.split(/\//).last}"
28
28
  end
29
-
29
+
30
30
  def all_component_directories
31
31
  Dir['components/*']
32
32
  end
33
-
33
+
34
34
  def full_path_for(component)
35
35
  component =~ /^components\// ? component : File.join("components", component)
36
36
  end
@@ -2,7 +2,7 @@ module Adhearsion #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0 unless defined? MAJOR
4
4
  MINOR = 8 unless defined? MINOR
5
- TINY = 3 unless defined? TINY
5
+ TINY = 4 unless defined? TINY
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.') unless defined? STRING
8
8
  end
@@ -4,22 +4,33 @@ module Adhearsion
4
4
  module Asterisk
5
5
  module AGI
6
6
  class Server
7
-
7
+
8
8
  class RubyServer < GServer
9
-
9
+
10
10
  def initialize(port, host)
11
11
  super(port, host, (1.0/0.0)) # (1.0/0.0) == Infinity
12
12
  end
13
-
13
+
14
+ def disconnecting(port)
15
+ @call.deliver_message :cancel
16
+ super(port)
17
+ end
18
+
14
19
  def serve(io)
15
- call = Adhearsion.receive_call_from(io)
20
+ begin
21
+ call = Adhearsion.receive_call_from(io)
22
+ rescue EOFError
23
+ # We didn't get the initial headers we were expecting
24
+ return
25
+ end
26
+
16
27
  Events.trigger_immediately([:asterisk, :before_call], call)
17
- ahn_log.agi "Handling call with variables #{call.variables.inspect}"
18
-
28
+ ahn_log.agi.debug "Handling call with variables #{call.variables.inspect}"
29
+
19
30
  return DialPlan::ConfirmationManager.handle(call) if DialPlan::ConfirmationManager.confirmation_call?(call)
20
-
31
+
21
32
  # This is what happens 99.9% of the time.
22
-
33
+
23
34
  DialPlan::Manager.handle call
24
35
  rescue Hangup
25
36
  ahn_log.agi "HANGUP event for call with uniqueid #{call.variables[:uniqueid].inspect} and channel #{call.variables[:channel].inspect}"
@@ -48,14 +59,14 @@ module Adhearsion
48
59
  call.hangup!
49
60
  # TBD: (may have more hooks than what Jay has defined in hooks.rb)
50
61
  rescue => e
51
- ahn_log.agi.error e.inspect
52
- ahn_log.agi.error e.backtrace.map { |s| " " * 5 + s }.join("\n")
62
+ ahn_log.agi.error "#{e.class}: #{e.message}"
63
+ ahn_log.agi.error e.backtrace.join("\n\t")
53
64
  ensure
54
65
  Adhearsion.remove_inactive_call call rescue nil
55
66
  end
56
-
67
+
57
68
  end
58
-
69
+
59
70
  DEFAULT_OPTIONS = { :server_class => RubyServer, :port => 4573, :host => "0.0.0.0" } unless defined? DEFAULT_OPTIONS
60
71
  attr_reader :host, :port, :server_class, :server
61
72
 
@@ -66,17 +77,36 @@ module Adhearsion
66
77
  end
67
78
 
68
79
  def start
80
+ server.audit = true
69
81
  server.start
70
82
  end
71
83
 
84
+ def graceful_shutdown
85
+ if @shutting_down
86
+ server.stop
87
+ return
88
+ end
89
+
90
+ @shutting_down = true
91
+
92
+ while server.connections > 0
93
+ sleep 0.2
94
+ end
95
+
96
+ server.stop
97
+ end
98
+
72
99
  def shutdown
100
+ server.shutdown
101
+ end
102
+
103
+ def stop
73
104
  server.stop
74
105
  end
75
-
106
+
76
107
  def join
77
108
  server.join
78
109
  end
79
-
80
110
  end
81
111
  end
82
112
  end
@@ -4,14 +4,14 @@ require 'adhearsion/voip/menu_state_machine/menu_class'
4
4
  module Adhearsion
5
5
  module VoIP
6
6
  module Asterisk
7
- module Commands
8
-
7
+ module Commands
8
+
9
9
  RESPONSE_PREFIX = "200 result=" unless defined? RESPONSE_PREFIX
10
-
10
+
11
11
  # These are the status messages that asterisk will issue after a dial command is executed.
12
- #
12
+ #
13
13
  # Here is a current list of dial status messages which are not all necessarily supported by adhearsion:
14
- #
14
+ #
15
15
  # ANSWER: Call is answered. A successful dial. The caller reached the callee.
16
16
  # BUSY: Busy signal. The dial command reached its number but the number is busy.
17
17
  # NOANSWER: No answer. The dial command reached its number, the number rang for too long, then the dial timed out.
@@ -24,13 +24,13 @@ module Adhearsion
24
24
  #
25
25
  # @see http://www.voip-info.org/wiki/index.php?page=Asterisk+variable+DIALSTATUS Asterisk Variable DIALSTATUS
26
26
  DIAL_STATUSES = Hash.new(:unknown).merge(:answer => :answered, #:doc:
27
- :congestion => :congested,
27
+ :congestion => :congested,
28
28
  :busy => :busy,
29
29
  :cancel => :cancelled,
30
30
  :noanswer => :unanswered,
31
31
  :cancelled => :cancelled,
32
32
  :chanunavail => :channel_unavailable) unless defined? DIAL_STATUSES
33
-
33
+
34
34
  DYNAMIC_FEATURE_EXTENSIONS = {
35
35
  :attended_transfer => lambda do |options|
36
36
  variable "TRANSFER_CONTEXT" => options[:context] if options && options.has_key?(:context)
@@ -41,20 +41,22 @@ module Adhearsion
41
41
  extend_dynamic_features_with 'blindxfer'
42
42
  end
43
43
  } unless defined? DYNAMIC_FEATURE_EXTENSIONS
44
-
45
- # Utility method to write to pbx.
46
- def write(message)
44
+
45
+ # Utility method to write to pbx.
46
+ def write(message)
47
47
  to_pbx.print(message)
48
48
  end
49
-
50
- # Utility method to read from pbx. Hangup if nil.
51
- def read
49
+
50
+ # Utility method to read from pbx. Hangup if nil.
51
+ def read
52
52
  returning from_pbx.gets do |message|
53
53
  raise Hangup if message.nil?
54
+ raise Hangup if message.match(/^HANGUP\n?$/i)
55
+ raise Hangup if message.match(/^511 Command Not Permitted on a dead channel/i)
54
56
  ahn_log.agi.debug "<<< #{message}"
55
57
  end
56
58
  end
57
-
59
+
58
60
  # This method is the underlying method executed by nearly all the command methods in this module.
59
61
  # It is used to send the plaintext commands in the proper AGI format over TCP/IP back to an Asterisk server via the
60
62
  # FAGI protocol.
@@ -62,24 +64,38 @@ module Adhearsion
62
64
  # in which case use this method you to communicate directly with an Asterisk server via the FAGI protocol.
63
65
  # @see http://www.voip-info.org/wiki/view/Asterisk+FastAGI More information about FAGI
64
66
  def raw_response(message = nil)
67
+ raise ArgumentError.new("illegal NUL in message #{message.inspect}") if message =~ /\0/
65
68
  ahn_log.agi.debug ">>> #{message}"
66
69
  write message if message
67
70
  read
68
71
  end
69
-
72
+
73
+ def response(command, *arguments)
74
+ # Arguments surrounded by quotes; quotes backslash-escaped.
75
+ # See parse_args in asterisk/res/res_agi.c (Asterisk 1.4.21.1)
76
+ quote_arg = lambda { |arg|
77
+ '"' + arg.gsub(/["\\]/) { |m| "\\#{m}" } + '"'
78
+ }
79
+ if arguments.empty?
80
+ raw_response("#{command}")
81
+ else
82
+ raw_response("#{command} " + arguments.map{ |arg| quote_arg.call(arg.to_s) }.join(' '))
83
+ end
84
+ end
85
+
70
86
  # The answer command must be called first before any other commands can be issued.
71
87
  # In typical adhearsion applications the answer command is called by default as soon
72
88
  # as a call is transfered to a valid context in dialplan.rb.
73
89
  # If you do not want your adhearsion application to automatically issue an answer command,
74
90
  # then you must edit your startup.rb file and configure this setting.
75
- # Keep in mind that you should not need to issue another answer command after
91
+ # Keep in mind that you should not need to issue another answer command after
76
92
  # an answer command has already been issued either explicitly by your code or implicitly
77
93
  # by the standard adhearsion configuration.
78
94
  def answer
79
- raw_response "ANSWER"
95
+ response "ANSWER"
80
96
  true
81
97
  end
82
-
98
+
83
99
  # This asterisk dialplan command allows you to instruct Asterisk to start applications
84
100
  # which are typically run from extensions.conf.
85
101
  #
@@ -87,26 +103,39 @@ module Adhearsion
87
103
  # by this code base. For commands that do not fall into this category, then exec is what you
88
104
  # should use.
89
105
  #
90
- # For example, if there are specific asterisk modules you have loaded that will not
106
+ # For example, if there are specific asterisk modules you have loaded that will not
91
107
  # available through the standard commands provided through FAGI - then you can used EXEC.
92
108
  #
93
109
  # @example Using execute in this way will add a header to an existing SIP call.
94
110
  # execute 'SIPAddHeader', '"Call-Info: answer-after=0"
95
- #
111
+ #
96
112
  # @see http://www.voip-info.org/wiki/view/Asterisk+-+documentation+of+application+commands Asterisk Dialplan Commands
97
113
  def execute(application, *arguments)
98
- result = raw_response("EXEC #{application} #{arguments * '|'}")
114
+ result = raw_response(%{EXEC %s "%s"} % [ application,
115
+ arguments.join(%{"#{AHN_CONFIG.asterisk.argument_delimiter}"}) ])
99
116
  return false if error?(result)
100
117
  result
101
118
  end
102
-
119
+
120
+ # Sends a message to the console via the verbose message system.
121
+ # @example Use this command to inform someone watching the Asterisk console
122
+ # of actions happening within Adhearsion.
123
+ # verbose 'Processing call with Adhearsion' 3
124
+ #
125
+ # @see http://www.voip-info.org/wiki/view/verbose
126
+ def verbose(message, level = nil)
127
+ result = raw_response("VERBOSE \"#{message}\" #{level}")
128
+ return false if error?(result)
129
+ result
130
+ end
131
+
103
132
  # Hangs up the current channel. After this command is issued, you will not be able to send any more AGI
104
133
  # commands but the dialplan Thread will still continue, allowing you to do any post-call work.
105
134
  #
106
135
  def hangup
107
- raw_response 'HANGUP'
136
+ response 'HANGUP'
108
137
  end
109
-
138
+
110
139
  # Plays the specified sound file names. This method will handle Time/DateTime objects (e.g. Time.now),
111
140
  # Fixnums (e.g. 1000), Strings which are valid Fixnums (e.g "123"), and direct sound files. When playing
112
141
  # numbers, Adhearsion assumes you're saying the number, not the digits. For example, play("100")
@@ -118,11 +147,11 @@ module Adhearsion
118
147
  #
119
148
  # @example Play file hello-world.???
120
149
  # play 'hello-world'
121
- # @example Speak current time
150
+ # @example Speak current time
122
151
  # play Time.now
123
- # @example Play sound file, speak number, play two more sound files
152
+ # @example Play sound file, speak number, play two more sound files
124
153
  # play %w"a-connect-charge-of 22 cents-per-minute will-apply"
125
- # @example Play two sound files
154
+ # @example Play two sound files
126
155
  # play "you-sound-cute", "what-are-you-wearing"
127
156
  #
128
157
  def play(*arguments)
@@ -148,27 +177,42 @@ module Adhearsion
148
177
  def record(*args)
149
178
  options = args.last.kind_of?(Hash) ? args.pop : {}
150
179
  filename = args.shift || "/tmp/recording_%d.gsm"
180
+ if (!options.has_key?(:format))
181
+ format = filename.slice!(/\.[^\.]+$/)
182
+ if (format.nil?)
183
+ ahn_log.agi.warn "Format not specified and not detected. Defaulting to \"gsm\""
184
+ format = gsm
185
+ end
186
+ format.sub!(/^\./, "")
187
+ else
188
+ format = options.delete(:format)
189
+ end
151
190
  silence = options.delete(:silence) || 0
152
- maxduration = options.delete(:maxduration) || 0
191
+ maxduration = options.delete(:maxduration) || -1
192
+ escapedigits = options.delete(:escapedigits) || "#"
153
193
 
154
- execute("Record", filename, silence, maxduration)
194
+ if (silence > 0)
195
+ response("RECORD FILE", filename, format, escapedigits, maxduration,0, "BEEP", "s=#{silence}")
196
+ else
197
+ response("RECORD FILE", filename, format, escapedigits, maxduration, 0, "BEEP")
198
+ end
155
199
 
156
200
  # If the user hangs up before the recording is entered, -1 is returned and RECORDED_FILE
157
201
  # will not contain the name of the file, even though it IS in fact recorded.
158
- filename.index("%d") ? get_variable('RECORDED_FILE') : filename
202
+ filename.index("%d") ? get_variable('RECORDED_FILE') : filename + "." + format
159
203
  end
160
-
204
+
161
205
  # Simulates pressing the specified digits over the current channel. Can be used to
162
206
  # traverse a phone menu.
163
207
  def dtmf(digits)
164
208
  execute "SendDTMF", digits.to_s
165
209
  end
166
-
210
+
167
211
  # The with_next_message method...
168
212
  def with_next_message(&block)
169
213
  raise LocalJumpError, "Must supply a block" unless block_given?
170
- block.call(next_message)
171
- end
214
+ block.call(next_message)
215
+ end
172
216
 
173
217
  # This command should be used to advance to the next message in the Asterisk Comedian Voicemail application
174
218
  def next_message
@@ -183,12 +227,12 @@ module Adhearsion
183
227
  # Menu creates an interactive menu for the caller.
184
228
  #
185
229
  # The following documentation was derived from a post on Jay Phillips' blog (see below).
186
- #
230
+ #
187
231
  # The menu() command solves the problem of building enormous input-fetching state machines in Ruby without first-class
188
232
  # message passing facilities or an external DSL.
189
- #
233
+ #
190
234
  # Here is an example dialplan which uses the menu() command effectively.
191
- #
235
+ #
192
236
  # from_pstn {
193
237
  # menu 'welcome', 'for-spanish-press-8', 'main-ivr',
194
238
  # :timeout => 8.seconds, :tries => 3 do |link|
@@ -197,56 +241,56 @@ module Adhearsion
197
241
  # link.representative 4
198
242
  # link.spanish 8
199
243
  # link.employee 900..999
200
- #
244
+ #
201
245
  # link.on_invalid { play 'invalid' }
202
- #
246
+ #
203
247
  # link.on_premature_timeout do |str|
204
248
  # play 'sorry'
205
249
  # end
206
- #
250
+ #
207
251
  # link.on_failure do
208
252
  # play 'goodbye'
209
253
  # hangup
210
254
  # end
211
255
  # end
212
256
  # }
213
- #
257
+ #
214
258
  # shipment_status {
215
259
  # # Fetch a tracking number and pass it to a web service.
216
260
  # }
217
- #
261
+ #
218
262
  # ordering {
219
263
  # # Enter another menu that lets them enter credit card
220
264
  # # information and place their order over the phone.
221
265
  # }
222
- #
266
+ #
223
267
  # representative {
224
268
  # # Place the caller into a queue
225
269
  # }
226
- #
270
+ #
227
271
  # spanish {
228
272
  # # Special options for the spanish menu.
229
273
  # }
230
- #
274
+ #
231
275
  # employee {
232
276
  # dial "SIP/#{extension}" # Overly simplistic
233
277
  # }
234
- #
278
+ #
235
279
  # The main detail to note is the declarations within the menu() command’s block. Each line seems to refer to a link object
236
280
  # executing a seemingly arbitrary method with an argument that’s either a number or a Range of numbers. The +link+ object
237
281
  # collects these arbitrary method invocations and assembles a set of rules. The seemingly arbitrary method name is the name
238
282
  # of the context to which the menu should jump in case its argument (the pattern) is found to be a match.
239
- #
283
+ #
240
284
  # With these context names and patterns defined, the +menu()+ command plays in sequence the sound files you supply as
241
285
  # arguments, stopping playback abruptly if the user enters a digit. If no digits were pressed when the files finish playing,
242
286
  # it waits +:timeout+ seconds. If no digits are pressed after the timeout, it executes the +on_premature_timeout+ hook you
243
287
  # define (if any) and then tries again a maximum of +:tries+ times. If digits are pressed that result in no possible match,
244
288
  # it executes the +on_invalid+ hook. When/if all tries are exhausted with no positive match, it executes the +on_failure+
245
289
  # hook after the other hook (e.g. +on_invalid+, then +on_failure+).
246
- #
290
+ #
247
291
  # When the +menu()+ state machine runs through the defined rules, it must distinguish between exact and potential matches.
248
292
  # It's important to understand the differences between these and how they affect the overall outcome:
249
- #
293
+ #
250
294
  # |---------------|-------------------|------------------------------------------------------|
251
295
  # | exact matches | potential matches | result |
252
296
  # |---------------|-------------------|------------------------------------------------------|
@@ -257,23 +301,23 @@ module Adhearsion
257
301
  # | 1 | >0 | Get another digit. If timeout, use exact match |
258
302
  # | >1 | >0 | Get another digit. If timeout, use first exact match |
259
303
  # |---------------|-------------------|------------------------------------------------------|
260
- #
304
+ #
261
305
  # == Database integration
262
- #
306
+ #
263
307
  # To do database integration, I recommend programatically executing methods on the link object within the block. For example:
264
- #
308
+ #
265
309
  # menu do |link|
266
310
  # for employee in Employee.find(:all)
267
311
  # link.internal employee.extension
268
312
  # end
269
313
  # end
270
- #
314
+ #
271
315
  # or this more efficient and Rubyish way
272
- #
316
+ #
273
317
  # menu do |link|
274
318
  # link.internal *Employee.find(:all).map(&:extension)
275
319
  # end
276
- #
320
+ #
277
321
  # If this second example seems like too much Ruby magic, let me explain — +Employee.find(:all)+ effectively does a “SELECT *
278
322
  # FROM employees” on the database with ActiveRecord, returning (what you’d think is) an Array. The +map(&:extension)+ is
279
323
  # fanciness that means “replace every instance in this Array with the result of calling extension on that object”. Now we
@@ -281,23 +325,23 @@ module Adhearsion
281
325
  # being one argument (an Array) into a sequence of n arguments, where n is the number of items in the Array it’s “splatting”.
282
326
  # Lastly, these arguments are passed to the internal method, the name of a context which will handle dialing this user if one
283
327
  # of the supplied patterns matches.
284
- #
328
+ #
285
329
  # == Handling a successful pattern match
286
- #
330
+ #
287
331
  # Which brings me to another important note. Let’s say that the user’s input successfully matched one of the patterns
288
332
  # returned by that Employe.find... magic. When it jumps to the internal context, that context can access the variable entered
289
333
  # through the extension variable. This was a tricky design decision that I think, overall, works great. It makes the +menu()+
290
334
  # command feel much more first-class in the Adhearsion dialplan grammar and decouples the receiving context from the menu
291
335
  # 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
292
336
  # point, making menu() effectively a pipeline of re-creating the call.
293
- #
337
+ #
294
338
  # @see http://jicksta.com/articles/2008/02/11/menu-command Original Blog Post
295
339
  def menu(*args, &block)
296
340
  options = args.last.kind_of?(Hash) ? args.pop : {}
297
341
  sound_files = args.flatten
298
-
342
+
299
343
  menu_instance = Menu.new(options, &block)
300
-
344
+
301
345
  initial_digit_prompt = sound_files.any?
302
346
 
303
347
  # This method is basically one big begin/rescue block. When we start the Menu state machine by continue()ing, the state
@@ -316,7 +360,7 @@ module Adhearsion
316
360
  menu_instance.execute_invalid_hook
317
361
  menu_instance.restart!
318
362
  when Menu::MenuGetAnotherDigit
319
-
363
+
320
364
  next_digit = play_sound_files_for_menu(menu_instance, sound_files)
321
365
  if next_digit
322
366
  menu_instance << next_digit
@@ -344,7 +388,7 @@ module Adhearsion
344
388
 
345
389
  end
346
390
  end
347
-
391
+
348
392
  # This method is used to receive keypad input from the user. Digits are collected
349
393
  # via DTMF (keypad) input until one of three things happens:
350
394
  #
@@ -364,7 +408,7 @@ module Adhearsion
364
408
  # # or when the "0" key is pressed.
365
409
  # input 3, :play => "you-sound-cute"
366
410
  # input :play => ["if-this-is-correct-press", 1, "otherwise-press", 2]
367
- #
411
+ #
368
412
  # When specifying files to play, the playback of the sequence of files will stop
369
413
  # immediately when the user presses the first digit.
370
414
  #
@@ -378,7 +422,7 @@ module Adhearsion
378
422
  def input(*args)
379
423
  options = args.last.kind_of?(Hash) ? args.pop : {}
380
424
  number_of_digits = args.shift
381
-
425
+
382
426
  sound_files = Array options.delete(:play)
383
427
  timeout = options.delete(:timeout)
384
428
  terminating_key = options.delete(:accept_key)
@@ -387,15 +431,15 @@ module Adhearsion
387
431
  elsif number_of_digits.nil? && !terminating_key.equal?(false)
388
432
  '#'
389
433
  end
390
-
434
+
391
435
  if number_of_digits && number_of_digits < 0
392
436
  ahn_log.agi.warn "Giving -1 to input() is now deprecated. Don't specify a first " +
393
437
  "argument to simulate unlimited digits." if number_of_digits == -1
394
438
  raise ArgumentError, "The number of digits must be positive!"
395
439
  end
396
-
440
+
397
441
  buffer = ''
398
- key = sound_files.any? ? interruptable_play(*sound_files) || '' : wait_for_digit(timeout || -1)
442
+ key = sound_files.any? ? interruptible_play(*sound_files) || '' : wait_for_digit(timeout || -1)
399
443
  loop do
400
444
  return buffer if key.nil?
401
445
  if terminating_key
@@ -412,7 +456,7 @@ module Adhearsion
412
456
  key = wait_for_digit(timeout || -1)
413
457
  end
414
458
  end
415
-
459
+
416
460
  # Jumps to a context. An alternative to DialplanContextProc#+@. When jumping to a context, it will *not* resume executing
417
461
  # the former context when the jumped-to context has finished executing. Make sure you don't have any
418
462
  # +ensure+ closures which you expect to execute when the call has finished, as they will run when
@@ -426,27 +470,27 @@ module Adhearsion
426
470
  def jump_to(context, overrides={})
427
471
  context = lookup_context_with_name(context) if context.kind_of?(Symbol) || (context.kind_of?(String) && context =~ /^[\w_]+$/)
428
472
  raise Adhearsion::VoIP::DSL::Dialplan::ContextNotFoundException unless context.kind_of?(Adhearsion::DialPlan::DialplanContextProc)
429
-
473
+
430
474
  if overrides.any?
431
475
  overrides = overrides.symbolize_keys
432
476
  if overrides.has_key?(:extension) && !overrides[:extension].kind_of?(Adhearsion::VoIP::DSL::PhoneNumber)
433
477
  overrides[:extension] = Adhearsion::VoIP::DSL::PhoneNumber.new overrides[:extension]
434
478
  end
435
-
479
+
436
480
  overrides.each_pair do |key, value|
437
481
  meta_def(key) { value }
438
482
  end
439
483
  end
440
-
484
+
441
485
  raise Adhearsion::VoIP::DSL::Dialplan::ControlPassingException.new(context)
442
486
  end
443
-
487
+
444
488
  # The queue method puts a call into a call queue to be answered by an agent registered with that queue.
445
489
  # The queue method takes a queue_name as an argument to place the caller in the appropriate queue.
446
- # @see http://www.voip-info.org/wiki-Asterisk+cmd+Queue Full information on the Asterisk Queue
490
+ # @see http://www.voip-info.org/wiki-Asterisk+cmd+Queue Full information on the Asterisk Queue
447
491
  def queue(queue_name)
448
492
  queue_name = queue_name.to_s
449
-
493
+
450
494
  @queue_proxy_hash_lock = Mutex.new unless defined? @queue_proxy_hash_lock
451
495
  @queue_proxy_hash_lock.synchronize do
452
496
  @queue_proxy_hash ||= {}
@@ -458,7 +502,7 @@ module Adhearsion
458
502
  end
459
503
  end
460
504
  end
461
-
505
+
462
506
  # Returns the status of the last dial(). Possible dial
463
507
  # statuses include :answer, :busy, :no_answer, :cancelled,
464
508
  # :congested, and :channel_unavailable. If :cancel is
@@ -480,15 +524,15 @@ module Adhearsion
480
524
  def last_dial_unsuccessful?
481
525
  not last_dial_successful?
482
526
  end
483
-
527
+
484
528
  # This feature is presently experimental! Do not use it!
485
529
  def speak(text, engine=:none)
486
- engine = Adhearsion::Configuration::AsteriskConfiguration.speech_engine || engine
530
+ engine = AHN_CONFIG.asterisk.speech_engine || engine
487
531
  execute SpeechEngines.send(engine, text)
488
532
  end
489
-
533
+
490
534
  # This method is a high-level way of enabling features you create/uncomment from features.conf.
491
- #
535
+ #
492
536
  # Certain Symbol features you enable (as defined in DYNAMIC_FEATURE_EXTENSIONS) have optional
493
537
  # arguments that you can also specify here. The usage examples show how to do this.
494
538
  #
@@ -502,7 +546,7 @@ module Adhearsion
502
546
  # enable_feature :blind_transfer, :context => 'my_dial' # Enables 'blindxfer' and sets TRANSFER_CONTEXT
503
547
  #
504
548
  # enable_feature "foobar" # Enables "foobar"
505
- #
549
+ #
506
550
  # enable_feature("dup"); enable_feature("dup") # Enables "dup" only once.
507
551
  def enable_feature(feature_name, optional_options=nil)
508
552
  if DYNAMIC_FEATURE_EXTENSIONS.has_key? feature_name
@@ -513,7 +557,7 @@ module Adhearsion
513
557
  extend_dynamic_features_with feature_name
514
558
  end
515
559
  end
516
-
560
+
517
561
  # Disables a feature name specified in features.conf. If you're disabling it, it was probably
518
562
  # set by enable_feature().
519
563
  def disable_feature(feature_name)
@@ -524,7 +568,7 @@ module Adhearsion
524
568
  variable 'DYNAMIC_FEATURES' => enabled_features.join('#')
525
569
  end
526
570
  end
527
-
571
+
528
572
  # Used to join a particular conference with the MeetMe application. To
529
573
  # use MeetMe, be sure you have a proper timing device configured on your
530
574
  # Asterisk box. MeetMe is Asterisk's built-in conferencing program.
@@ -536,24 +580,24 @@ module Adhearsion
536
580
  raise ArgumentError, "A conference PIN number must be numerical!" if pin && pin.to_s !~ /^\d+$/
537
581
  # The 'd' option of MeetMe creates conferences dynamically.
538
582
  command_flags += 'd' unless command_flags.include? 'd'
539
-
583
+
540
584
  execute "MeetMe", conference_id, command_flags, options[:pin]
541
585
  end
542
-
586
+
543
587
  # Issue this command to access a channel variable that exists in the asterisk dialplan (i.e. extensions.conf)
544
588
  # Use get_variable to pass information from other modules or high level configurations from the asterisk dialplan
545
589
  # to the adhearsion dialplan.
546
590
  # @see: http://www.voip-info.org/wiki/view/get+variable Asterisk Get Variable
547
591
  def get_variable(variable_name)
548
- result = raw_response("GET VARIABLE #{variable_name}")
592
+ result = response("GET VARIABLE", variable_name)
549
593
  case result
550
594
  when "200 result=0"
551
595
  return nil
552
- when /^200 result=1 \((.*)\)$/
553
- return $LAST_PAREN_MATCH
596
+ when /^200 result=1 \((.*)\)$/
597
+ return $LAST_PAREN_MATCH
554
598
  end
555
599
  end
556
-
600
+
557
601
  # Use set_variable to pass information back to the asterisk dial plan.
558
602
  # Keep in mind that the variables are not global variables. These variables only exist for the channel
559
603
  # related to the call that is being serviced by the particular instance of your adhearsion application.
@@ -561,9 +605,9 @@ module Adhearsion
561
605
  # application to share. Once the channel is "hungup" then the variables are cleared and their information is gone.
562
606
  # @see http://www.voip-info.org/wiki/view/set+variable Asterisk Set Variable
563
607
  def set_variable(variable_name, value)
564
- raw_response("SET VARIABLE %s %p" % [variable_name.to_s, value.to_s]) == "200 result=1"
608
+ response("SET VARIABLE", variable_name, value) == "200 result=1"
565
609
  end
566
-
610
+
567
611
  # The variable method allows you to either set or get a channel variable from Asterisk
568
612
  # The method takes a hash key/value pair if you would like to set a variable
569
613
  # Or a single string with the variable to get from Asterisk
@@ -582,8 +626,8 @@ module Adhearsion
582
626
  end
583
627
  end
584
628
  end
585
-
586
- # Use the voicemail method to send a caller to a voicemail box to leave a message.
629
+
630
+ # Use the voicemail method to send a caller to a voicemail box to leave a message.
587
631
  # @see http://www.voip-info.org/tiki-index.php?page=Asterisk+cmd+VoiceMail Asterisk Voicemail
588
632
  # The method takes the mailbox_number of the user to leave a message for and a
589
633
  # greeting_option that will determine which message gets played to the caller.
@@ -594,14 +638,14 @@ module Adhearsion
594
638
  skip_option = options_hash.delete(:skip)
595
639
  raise ArgumentError, 'You supplied too many arguments!' if mailbox_number && options_hash.any?
596
640
  greeting_option = case greeting_option
597
- when :busy: 'b'
598
- when :unavailable: 'u'
599
- when nil: nil
641
+ when :busy then 'b'
642
+ when :unavailable then 'u'
643
+ when nil then nil
600
644
  else raise ArgumentError, "Unrecognized greeting #{greeting_option}"
601
645
  end
602
646
  skip_option &&= 's'
603
647
  options = "#{greeting_option}#{skip_option}"
604
-
648
+
605
649
  raise ArgumentError, "Mailbox cannot be blank!" if !mailbox_number.nil? && mailbox_number.blank?
606
650
  number_with_context = if mailbox_number then mailbox_number else
607
651
  raise ArgumentError, "You must supply ONE context name!" if options_hash.size != 1
@@ -613,53 +657,53 @@ module Adhearsion
613
657
  end
614
658
  execute('voicemail', number_with_context, options)
615
659
  case variable('VMSTATUS')
616
- when 'SUCCESS': true
617
- when 'USEREXIT': false
660
+ when 'SUCCESS' then true
661
+ when 'USEREXIT' then false
618
662
  else nil
619
663
  end
620
664
  end
621
-
665
+
622
666
  # The voicemail_main method puts a caller into the voicemail system to fetch their voicemail
623
667
  # or set options for their voicemail box.
624
668
  # @see http://www.voip-info.org/wiki-Asterisk+cmd+VoiceMailMain Asterisk VoiceMailMain Command
625
669
  def voicemail_main(options={})
626
670
  mailbox, context, folder = options.values_at :mailbox, :context, :folder
627
671
  authenticate = options.has_key?(:authenticate) ? options[:authenticate] : true
628
-
672
+
629
673
  folder = if folder
630
674
  if folder.to_s =~ /^[\w_]+$/
631
675
  "a(#{folder})"
632
676
  else
633
- raise ArgumentError, "Voicemail folder must be alphanumerical/underscore characters only!"
677
+ raise ArgumentError, "Voicemail folder must be alphanumerical/underscore characters only!"
634
678
  end
635
679
  elsif folder == ''
636
680
  raise "Folder name cannot be an empty String!"
637
681
  else
638
682
  nil
639
683
  end
640
-
684
+
641
685
  real_mailbox = ""
642
686
  real_mailbox << "#{mailbox}" unless mailbox.blank?
643
687
  real_mailbox << "@#{context}" unless context.blank?
644
-
688
+
645
689
  real_options = ""
646
690
  real_options << "s" if !authenticate
647
691
  real_options << folder unless folder.blank?
648
-
692
+
649
693
  command_args = [real_mailbox]
650
694
  command_args << real_options unless real_options.blank?
651
695
  command_args.clear if command_args == [""]
652
-
696
+
653
697
  execute 'VoiceMailMain', *command_args
654
698
  end
655
-
699
+
656
700
  def check_voicemail
657
701
  ahn_log.agi.warn "THE check_voicemail() DIALPLAN METHOD WILL SOON BE DEPRECATED! CHANGE THIS TO voicemail_main() INSTEAD"
658
702
  voicemail_main
659
703
  end
660
-
704
+
661
705
  # Use this command to dial an extension or "phone number" in asterisk.
662
- # This command maps to the Asterisk DIAL command in the asterisk dialplan.
706
+ # This command maps to the Asterisk DIAL command in the asterisk dialplan.
663
707
  #
664
708
  # The first parameter, number, must be a string that represents the extension or "number" that asterisk should dial.
665
709
  # Be careful to not just specify a number like 5001, 9095551001
@@ -686,18 +730,18 @@ module Adhearsion
686
730
  #
687
731
  # @example Make a call to the PSTN using my SIP provider for VoIP termination
688
732
  # dial("SIP/19095551001@my.sip.voip.terminator.us")
689
- #
733
+ #
690
734
  # @example Make 3 Simulataneous calls to the SIP extensions separated by & symbols, try for 15 seconds and use the callerid
691
735
  # for this call specified by the variable my_callerid
692
736
  # dial "SIP/jay-desk-650&SIP/jay-desk-601&SIP/jay-desk-601-2", :for => 15.seconds, :caller_id => my_callerid
693
737
  #
694
- # @example Make a call using the IAX provider to the PSTN
738
+ # @example Make a call using the IAX provider to the PSTN
695
739
  # dial("IAX2/my.id@voipjet/19095551234", :name=>"John Doe", :caller_id=>"9095551234")
696
740
  #
697
741
  # @see http://www.voip-info.org/wiki-Asterisk+cmd+Dial Asterisk Dial Command
698
742
  def dial(number, options={})
699
743
  *recognized_options = :caller_id, :name, :for, :options, :confirm
700
-
744
+
701
745
  unrecognized_options = options.keys - recognized_options
702
746
  raise ArgumentError, "Unknown dial options: #{unrecognized_options.to_sentence}" if unrecognized_options.any?
703
747
  set_caller_id_name options[:name]
@@ -707,8 +751,8 @@ module Adhearsion
707
751
  all_options = all_options ? all_options + confirm_option : confirm_option
708
752
  execute "Dial", number, options[:for], all_options
709
753
  end
710
-
711
-
754
+
755
+
712
756
  # This implementation of dial() uses the experimental call routing DSL.
713
757
  #
714
758
  # def dial(number, options={})
@@ -716,24 +760,24 @@ module Adhearsion
716
760
  # return :no_route if rules.empty?
717
761
  # call_attempt_status = nil
718
762
  # rules.each do |provider|
719
- #
763
+ #
720
764
  # response = execute "Dial",
721
765
  # provider.format_number_for_platform(number),
722
766
  # timeout_from_dial_options(options),
723
767
  # asterisk_options_from_dial_options(options)
724
- #
768
+ #
725
769
  # call_attempt_status = last_dial_status
726
770
  # break if call_attempt_status == :answered
727
771
  # end
728
772
  # call_attempt_status
729
773
  # end
730
-
731
-
774
+
775
+
732
776
  # Speaks the digits given as an argument. For example, "123" is spoken as "one two three".
733
777
  def say_digits(digits)
734
778
  execute "saydigits", validate_digits(digits)
735
779
  end
736
-
780
+
737
781
  # Returns the number of seconds the given block takes to execute as a Float. This
738
782
  # is particularly useful in dialplans for tracking billable time. Note that
739
783
  # if the call is hung up during the block, you will need to rescue the
@@ -744,28 +788,28 @@ module Adhearsion
744
788
  yield
745
789
  Time.now - start_time
746
790
  end
747
-
791
+
748
792
  #
749
793
  # This will play a sequence of files, stopping the playback if a digit is pressed. If a digit is pressed, it will be
750
794
  # returned as a String. If the files played with no keypad input, nil will be returned.
751
795
  #
752
796
  def interruptible_play(*files)
753
797
  files.flatten.each do |file|
754
- result = result_digit_from raw_response("EXEC BACKGROUND #{file}")
798
+ result = result_digit_from response("STREAM FILE", file, "1234567890*#")
755
799
  return result if result != 0.chr
756
800
  end
757
801
  nil
758
802
  end
759
803
 
760
804
  protected
761
-
805
+
762
806
  # wait_for_digits waits for the input of digits based on the number of milliseconds
763
807
  def wait_for_digit(timeout=-1)
764
808
  timeout *= 1_000 if timeout != -1
765
- result = result_digit_from raw_response("WAIT FOR DIGIT #{timeout.to_i}")
809
+ result = result_digit_from response("WAIT FOR DIGIT", timeout.to_i)
766
810
  (result == 0.chr) ? nil : result
767
811
  end
768
-
812
+
769
813
  ##
770
814
  # Deprecated name of interruptible_play(). This is a misspelling!
771
815
  #
@@ -773,35 +817,35 @@ module Adhearsion
773
817
  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!'
774
818
  interruptible_play(*files)
775
819
  end
776
-
820
+
777
821
  # set_callier_id_number method allows setting of the callerid number of the call
778
822
  def set_caller_id_number(caller_id)
779
823
  return unless caller_id
780
824
  raise ArgumentError, "Caller ID must be numerical" if caller_id.to_s !~ /^\d+$/
781
- raw_response %(SET CALLERID %p) % caller_id
825
+ response "SET CALLERID", caller_id
782
826
  end
783
-
827
+
784
828
  # set_caller_id_name method allows the setting of the callerid name of the call
785
829
  def set_caller_id_name(caller_id_name)
786
830
  return unless caller_id_name
787
831
  variable "CALLERID(name)" => caller_id_name
788
832
  end
789
-
833
+
790
834
  def timeout_from_dial_options(options)
791
835
  options[:for] || options[:timeout]
792
836
  end
793
-
837
+
794
838
  def asterisk_options_from_dial_options(options)
795
839
  # TODO: Will become much more sophisticated soon to handle callerid, etc
796
840
  options[:options]
797
841
  end
798
-
842
+
799
843
  def dial_macro_option_compiler(confirm_argument_value)
800
844
  defaults = { :macro => 'ahn_dial_confirmer',
801
845
  :timeout => 20.seconds,
802
846
  :play => "beep",
803
847
  :key => '#' }
804
-
848
+
805
849
  case confirm_argument_value
806
850
  when true
807
851
  DialPlan::ConfirmationManager.encode_hash_for_dial_macro_argument(defaults)
@@ -809,7 +853,7 @@ module Adhearsion
809
853
  ''
810
854
  when Proc
811
855
  raise NotImplementedError, "Coming in the future, you can do :confirm => my_context."
812
-
856
+
813
857
  when Hash
814
858
  options = defaults.merge confirm_argument_value
815
859
  if((confirm_argument_value.keys - defaults.keys).any?)
@@ -830,33 +874,33 @@ module Adhearsion
830
874
  raise ArgumentError, "Unrecognized DTMF key: #{options[:key]}" unless options[:key].to_s =~ /^[\d#*]$/
831
875
  options[:play] = Array(options[:play]).join('++')
832
876
  DialPlan::ConfirmationManager.encode_hash_for_dial_macro_argument options
833
-
877
+
834
878
  else
835
879
  raise ArgumentError, "Unrecognized :confirm option: #{confirm_argument_value.inspect}!"
836
880
  end
837
881
  end
838
-
882
+
839
883
  def result_digit_from(response_string)
840
884
  raise ArgumentError, "Can't coerce nil into AGI response! This could be a bug!" unless response_string
841
885
  digit = response_string[/^#{response_prefix}(-?\d+(\.\d+)?)/,1]
842
886
  digit.to_i.chr if digit && digit.to_s != "-1"
843
887
  end
844
-
888
+
845
889
  def extract_input_from(result)
846
890
  return false if error?(result)
847
891
  # return false if input_timed_out?(result)
848
-
892
+
849
893
  # This regexp doesn't match if there was a timeout with no
850
894
  # inputted digits, therefore returning nil.
851
-
852
- result[/^#{response_prefix}([\d*]+)/, 1]
895
+
896
+ result[/^#{response_prefix}([\d*]+)/, 1]
853
897
  end
854
-
898
+
855
899
  def extract_variable_from(result)
856
900
  return false if error?(result)
857
901
  result[/^#{response_prefix}1 \((.+)\)/, 1]
858
902
  end
859
-
903
+
860
904
  def get_dial_status
861
905
  dial_status = variable('DIALSTATUS')
862
906
  dial_status ? dial_status.downcase.to_sym : :cancelled
@@ -867,13 +911,13 @@ module Adhearsion
867
911
  execute(:sayunixtime, argument.to_i)
868
912
  end
869
913
  end
870
-
914
+
871
915
  def play_numeric(argument)
872
916
  if argument.kind_of?(Numeric) || argument =~ /^\d+$/
873
917
  execute(:saynumber, argument)
874
918
  end
875
919
  end
876
-
920
+
877
921
  def play_string(argument)
878
922
  execute(:playback, argument)
879
923
  end
@@ -881,16 +925,16 @@ module Adhearsion
881
925
  def play_sound_files_for_menu(menu_instance, sound_files)
882
926
  digit = nil
883
927
  if sound_files.any? && menu_instance.digit_buffer_empty?
884
- digit = interruptable_play(*sound_files)
928
+ digit = interruptible_play(*sound_files)
885
929
  end
886
930
  digit || wait_for_digit(menu_instance.timeout)
887
931
  end
888
-
932
+
889
933
  def extend_dynamic_features_with(feature_name)
890
934
  current_variable = variable("DYNAMIC_FEATURES") || ''
891
935
  enabled_features = current_variable.split '#'
892
936
  unless enabled_features.include? feature_name
893
- enabled_features << feature_name
937
+ enabled_features << feature_name
894
938
  variable "DYNAMIC_FEATURES" => enabled_features.join('#')
895
939
  end
896
940
  end
@@ -916,46 +960,46 @@ module Adhearsion
916
960
  def to_pbx
917
961
  io
918
962
  end
919
-
963
+
920
964
  def from_pbx
921
965
  io
922
966
  end
923
-
967
+
924
968
  def validate_digits(digits)
925
969
  returning digits.to_s do |digits_as_string|
926
970
  raise ArgumentError, "Can only be called with valid digits!" unless digits_as_string =~ /^[0-9*#-]+$/
927
971
  end
928
972
  end
929
-
973
+
930
974
  def error?(result)
931
975
  result.to_s[/^#{response_prefix}(?:-\d+|0)/]
932
976
  end
933
-
977
+
934
978
  # timeout with pressed digits: 200 result=<digits> (timeout)
935
979
  # timeout without pressed digits: 200 result= (timeout)
936
980
  # @see http://www.voip-info.org/wiki/view/get+data AGI Get Data
937
981
  def input_timed_out?(result)
938
982
  result.starts_with?(response_prefix) && result.ends_with?('(timeout)')
939
983
  end
940
-
984
+
941
985
  def io
942
986
  call.io
943
987
  end
944
-
988
+
945
989
  def response_prefix
946
990
  RESPONSE_PREFIX
947
991
  end
948
-
992
+
949
993
  class QueueProxy
950
-
994
+
951
995
  class << self
952
-
996
+
953
997
  def format_join_hash_key_arguments(options)
954
-
998
+
955
999
  bad_argument = lambda do |(key, value)|
956
1000
  raise ArgumentError, "Unrecognize value for #{key.inspect} -- #{value.inspect}"
957
1001
  end
958
-
1002
+
959
1003
  # Direct Queue() arguments:
960
1004
  timeout = options.delete :timeout
961
1005
  announcement = options.delete :announce
@@ -968,40 +1012,40 @@ module Adhearsion
968
1012
  raise ArgumentError, "Unrecognized args to join!: #{options.inspect}" if options.any?
969
1013
 
970
1014
  ring_style = case ring_style
971
- when :ringing: 'r'
972
- when :music: ''
1015
+ when :ringing then 'r'
1016
+ when :music then ''
973
1017
  when nil
974
1018
  else bad_argument[:play => ring_style]
975
1019
  end.to_s
976
-
1020
+
977
1021
  allow_hangup = case allow_hangup
978
- when :caller: 'H'
979
- when :agent: 'h'
980
- when :everyone: 'Hh'
1022
+ when :caller then 'H'
1023
+ when :agent then 'h'
1024
+ when :everyone then 'Hh'
981
1025
  when nil
982
1026
  else bad_argument[:allow_hangup => allow_hangup]
983
1027
  end.to_s
984
-
1028
+
985
1029
  allow_transfer = case allow_transfer
986
- when :caller: 'T'
987
- when :agent: 't'
988
- when :everyone: 'Tt'
1030
+ when :caller then 'T'
1031
+ when :agent then 't'
1032
+ when :everyone then 'Tt'
989
1033
  when nil
990
1034
  else bad_argument[:allow_transfer => allow_transfer]
991
1035
  end.to_s
992
-
1036
+
993
1037
  terse_character_options = ring_style + allow_transfer + allow_hangup
994
-
1038
+
995
1039
  [terse_character_options, '', announcement, timeout].map(&:to_s)
996
1040
  end
997
1041
 
998
1042
  end
999
-
1043
+
1000
1044
  attr_reader :name, :environment
1001
1045
  def initialize(name, environment)
1002
1046
  @name, @environment = name, environment
1003
1047
  end
1004
-
1048
+
1005
1049
  # Makes the current channel join the queue. Below are explanations of the recognized Hash-key
1006
1050
  # arguments supported by this method.
1007
1051
  #
@@ -1010,9 +1054,9 @@ module Adhearsion
1010
1054
  # :announce - A sound file to play instead of the normal queue announcement.
1011
1055
  # :allow_transfer - Can be :caller, :agent, or :everyone. Allow someone to transfer the call.
1012
1056
  # :allow_hangup - Can be :caller, :agent, or :everyone. Allow someone to hangup with the * key.
1013
- #
1057
+ #
1014
1058
  # Usage examples:
1015
- #
1059
+ #
1016
1060
  # - queue('sales').join!
1017
1061
  # - queue('sales').join! :timeout => 1.minute
1018
1062
  # - queue('sales').join! :play => :music
@@ -1023,12 +1067,12 @@ module Adhearsion
1023
1067
  # - queue('sales').join! :allow_hangup => :caller
1024
1068
  # - queue('sales').join! :allow_hangup => :agent
1025
1069
  # - queue('sales').join! :allow_hangup => :everyone
1026
- # - queue('sales').join! :allow_transfer => :agent, :timeout => 30.seconds,
1070
+ # - queue('sales').join! :allow_transfer => :agent, :timeout => 30.seconds,
1027
1071
  def join!(options={})
1028
1072
  environment.execute("queue", name, *self.class.format_join_hash_key_arguments(options))
1029
1073
  normalize_queue_status_variable environment.variable("QUEUESTATUS")
1030
1074
  end
1031
-
1075
+
1032
1076
  def agents(options={})
1033
1077
  cached = options.has_key?(:cache) ? options.delete(:cache) : true
1034
1078
  raise ArgumentError, "Unrecognized arguments to agents(): #{options.inspect}" if options.keys.any?
@@ -1038,43 +1082,43 @@ module Adhearsion
1038
1082
  @uncached_proxy ||= QueueAgentsListProxy.new(self, false)
1039
1083
  end
1040
1084
  end
1041
-
1085
+
1042
1086
  def waiting_count
1043
1087
  raise QueueDoesNotExistError.new(name) unless exists?
1044
1088
  environment.variable("QUEUE_WAITING_COUNT(#{name})").to_i
1045
1089
  end
1046
-
1090
+
1047
1091
  def empty?
1048
1092
  waiting_count == 0
1049
1093
  end
1050
-
1094
+
1051
1095
  def any?
1052
1096
  waiting_count > 0
1053
1097
  end
1054
-
1098
+
1055
1099
  def exists?
1056
1100
  environment.execute('RemoveQueueMember', name, 'SIP/AdhearsionQueueExistenceCheck')
1057
1101
  environment.variable("RQMSTATUS") != 'NOSUCHQUEUE'
1058
1102
  end
1059
-
1103
+
1060
1104
  private
1061
-
1105
+
1062
1106
  def normalize_queue_status_variable(variable)
1063
1107
  returning variable.downcase.to_sym do |queue_status|
1064
1108
  raise QueueDoesNotExistError.new(name) if queue_status == :unknown
1065
1109
  end
1066
1110
  end
1067
-
1111
+
1068
1112
  class QueueAgentsListProxy
1069
-
1113
+
1070
1114
  include Enumerable
1071
-
1115
+
1072
1116
  attr_reader :proxy, :agents
1073
1117
  def initialize(proxy, cached=false)
1074
1118
  @proxy = proxy
1075
1119
  @cached = cached
1076
1120
  end
1077
-
1121
+
1078
1122
  def count
1079
1123
  if cached? && @cached_count
1080
1124
  @cached_count
@@ -1084,89 +1128,89 @@ module Adhearsion
1084
1128
  end
1085
1129
  alias size count
1086
1130
  alias length count
1087
-
1131
+
1088
1132
  # Supported Hash-key arguments are :penalty and :name. The :name value will be viewable in
1089
1133
  # the queue_log. The :penalty is the penalty assigned to this agent for answering calls on
1090
1134
  # this queue
1091
1135
  def new(*args)
1092
-
1136
+
1093
1137
  options = args.last.kind_of?(Hash) ? args.pop : {}
1094
1138
  interface = args.shift || ''
1095
-
1139
+
1096
1140
  raise ArgumentError, "You may only supply an interface and a Hash argument!" if args.any?
1097
-
1141
+
1098
1142
  penalty = options.delete(:penalty) || ''
1099
1143
  name = options.delete(:name) || ''
1100
-
1144
+
1101
1145
  raise ArgumentError, "Unrecognized argument(s): #{options.inspect}" if options.any?
1102
-
1146
+
1103
1147
  proxy.environment.execute("AddQueueMember", proxy.name, interface, penalty, '', name)
1104
-
1148
+
1105
1149
  case proxy.environment.variable("AQMSTATUS")
1106
- when "ADDED" : true
1107
- when "MEMBERALREADY" : false
1108
- when "NOSUCHQUEUE" : raise QueueDoesNotExistError.new(proxy.name)
1150
+ when "ADDED" then true
1151
+ when "MEMBERALREADY" then false
1152
+ when "NOSUCHQUEUE" then raise QueueDoesNotExistError.new(proxy.name)
1109
1153
  else
1110
1154
  raise "UNRECOGNIZED AQMSTATUS VALUE!"
1111
1155
  end
1112
-
1156
+
1113
1157
  # TODO: THIS SHOULD RETURN AN AGENT INSTANCE
1114
1158
  end
1115
-
1159
+
1116
1160
  # Logs a pre-defined agent into this queue and waits for calls. Pass in :silent => true to stop
1117
1161
  # the message which says "Agent logged in".
1118
1162
  def login!(*args)
1119
1163
  options = args.last.kind_of?(Hash) ? args.pop : {}
1120
-
1164
+
1121
1165
  silent = options.delete(:silent).equal?(false) ? '' : 's'
1122
1166
  id = args.shift
1123
1167
  id &&= AgentProxy.id_from_agent_channel(id)
1124
1168
  raise ArgumentError, "Unrecognized Hash options to login(): #{options.inspect}" if options.any?
1125
1169
  raise ArgumentError, "Unrecognized argument to login(): #{args.inspect}" if args.any?
1126
-
1170
+
1127
1171
  proxy.environment.execute('AgentLogin', id, silent)
1128
1172
  end
1129
-
1173
+
1130
1174
  # Removes the current channel from this queue
1131
1175
  def logout!
1132
1176
  # TODO: DRY this up. Repeated in the AgentProxy...
1133
1177
  proxy.environment.execute 'RemoveQueueMember', proxy.name
1134
1178
  case proxy.environment.variable("RQMSTATUS")
1135
- when "REMOVED" : true
1136
- when "NOTINQUEUE" : false
1179
+ when "REMOVED" then true
1180
+ when "NOTINQUEUE" then false
1137
1181
  when "NOSUCHQUEUE"
1138
1182
  raise QueueDoesNotExistError.new(proxy.name)
1139
1183
  else
1140
1184
  raise "Unrecognized RQMSTATUS variable!"
1141
1185
  end
1142
1186
  end
1143
-
1187
+
1144
1188
  def each(&block)
1145
1189
  check_agent_cache!
1146
1190
  agents.each(&block)
1147
1191
  end
1148
-
1192
+
1149
1193
  def first
1150
1194
  check_agent_cache!
1151
1195
  agents.first
1152
1196
  end
1153
-
1197
+
1154
1198
  def last
1155
1199
  check_agent_cache!
1156
1200
  agents.last
1157
1201
  end
1158
-
1202
+
1159
1203
  def cached?
1160
1204
  @cached
1161
1205
  end
1162
-
1206
+
1163
1207
  def to_a
1164
1208
  check_agent_cache!
1165
1209
  @agents
1166
1210
  end
1167
-
1211
+
1168
1212
  private
1169
-
1213
+
1170
1214
  def check_agent_cache!
1171
1215
  if cached?
1172
1216
  load_agents! unless agents
@@ -1174,7 +1218,7 @@ module Adhearsion
1174
1218
  load_agents!
1175
1219
  end
1176
1220
  end
1177
-
1221
+
1178
1222
  def load_agents!
1179
1223
  raw_data = proxy.environment.variable "QUEUE_MEMBER_LIST(#{proxy.name})"
1180
1224
  @agents = raw_data.split(',').map(&:strip).reject(&:empty?).map do |agent|
@@ -1182,11 +1226,11 @@ module Adhearsion
1182
1226
  end
1183
1227
  @cached_count = @agents.size
1184
1228
  end
1185
-
1229
+
1186
1230
  end
1187
-
1231
+
1188
1232
  class AgentProxy
1189
-
1233
+
1190
1234
  SUPPORTED_METADATA_NAMES = %w[status password name mohclass exten channel] unless defined? SUPPORTED_METADATA_NAMES
1191
1235
 
1192
1236
  class << self
@@ -1203,19 +1247,19 @@ module Adhearsion
1203
1247
  @proxy = proxy
1204
1248
  @queue_name = proxy.name
1205
1249
  end
1206
-
1250
+
1207
1251
  def remove!
1208
1252
  proxy.environment.execute 'RemoveQueueMember', queue_name, interface
1209
1253
  case proxy.environment.variable("RQMSTATUS")
1210
- when "REMOVED" : true
1211
- when "NOTINQUEUE" : false
1254
+ when "REMOVED" then true
1255
+ when "NOTINQUEUE" then false
1212
1256
  when "NOSUCHQUEUE"
1213
1257
  raise QueueDoesNotExistError.new(queue_name)
1214
1258
  else
1215
1259
  raise "Unrecognized RQMSTATUS variable!"
1216
1260
  end
1217
1261
  end
1218
-
1262
+
1219
1263
  # Pauses the given agent for this queue only. If you wish to pause this agent
1220
1264
  # for all queues, pass in :everywhere => true. Returns true if the agent was
1221
1265
  # successfully paused and false if the agent was not found.
@@ -1224,13 +1268,13 @@ module Adhearsion
1224
1268
  args = [(everywhere ? nil : queue_name), interface]
1225
1269
  proxy.environment.execute('PauseQueueMember', *args)
1226
1270
  case proxy.environment.variable("PQMSTATUS")
1227
- when "PAUSED" : true
1228
- when "NOTFOUND" : false
1271
+ when "PAUSED" then true
1272
+ when "NOTFOUND" then false
1229
1273
  else
1230
1274
  raise "Unrecognized PQMSTATUS value!"
1231
1275
  end
1232
1276
  end
1233
-
1277
+
1234
1278
  # Pauses the given agent for this queue only. If you wish to pause this agent
1235
1279
  # for all queues, pass in :everywhere => true. Returns true if the agent was
1236
1280
  # successfully paused and false if the agent was not found.
@@ -1239,40 +1283,40 @@ module Adhearsion
1239
1283
  args = [(everywhere ? nil : queue_name), interface]
1240
1284
  proxy.environment.execute('UnpauseQueueMember', *args)
1241
1285
  case proxy.environment.variable("UPQMSTATUS")
1242
- when "UNPAUSED" : true
1243
- when "NOTFOUND" : false
1286
+ when "UNPAUSED" then true
1287
+ when "NOTFOUND" then false
1244
1288
  else
1245
1289
  raise "Unrecognized UPQMSTATUS value!"
1246
1290
  end
1247
1291
  end
1248
-
1292
+
1249
1293
  # Returns true/false depending on whether this agent is logged in.
1250
1294
  def logged_in?
1251
1295
  status == 'LOGGEDIN'
1252
1296
  end
1253
-
1297
+
1254
1298
  private
1255
-
1299
+
1256
1300
  def status
1257
1301
  agent_metadata 'status'
1258
1302
  end
1259
-
1303
+
1260
1304
  def agent_metadata(data_name)
1261
1305
  data_name = data_name.to_s.downcase
1262
1306
  raise ArgumentError, "unrecognized agent metadata name #{data_name}" unless SUPPORTED_METADATA_NAMES.include? data_name
1263
1307
  proxy.environment.variable "AGENT(#{id}:#{data_name})"
1264
1308
  end
1265
-
1309
+
1266
1310
  end
1267
-
1311
+
1268
1312
  class QueueDoesNotExistError < Exception
1269
1313
  def initialize(queue_name)
1270
1314
  super "Queue #{queue_name} does not exist!"
1271
1315
  end
1272
1316
  end
1273
-
1317
+
1274
1318
  end
1275
-
1319
+
1276
1320
  module MenuDigitResponse
1277
1321
  def timed_out?
1278
1322
  eql? 0.chr
@@ -1280,36 +1324,36 @@ module Adhearsion
1280
1324
  end
1281
1325
 
1282
1326
  module SpeechEngines
1283
-
1327
+
1284
1328
  class InvalidSpeechEngine < Exception; end
1285
-
1329
+
1286
1330
  class << self
1287
1331
  def cepstral(text)
1288
1332
  puts "in ceptral"
1289
1333
  puts escape(text)
1290
1334
  end
1291
-
1335
+
1292
1336
  def festival(text)
1293
1337
  raise NotImplementedError
1294
1338
  end
1295
-
1339
+
1296
1340
  def none(text)
1297
1341
  raise InvalidSpeechEngine, "No speech engine selected. You must specify one in your Adhearsion config file."
1298
1342
  end
1299
-
1343
+
1300
1344
  def method_missing(engine_name, text)
1301
1345
  raise InvalidSpeechEngine, "Unsupported speech engine #{engine_name} for speaking '#{text}'"
1302
1346
  end
1303
-
1347
+
1304
1348
  private
1305
-
1349
+
1306
1350
  def escape(text)
1307
1351
  "%p" % text
1308
1352
  end
1309
-
1353
+
1310
1354
  end
1311
1355
  end
1312
-
1356
+
1313
1357
  end
1314
1358
  end
1315
1359
  end