adhearsion 0.8.2 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG CHANGED
@@ -1,4 +1,11 @@
1
+ 0.8.3
2
+ - The "uniqueid" call channel variable available in dialplan.rb is now *always* a String
3
+ - Renamed interruptable_play to interruptible_play and made interruptible_play() public instead of protected.
4
+ - Fixed an Asterisk Manager Interface parsing issue in which colons sometimes got stuck into the key name.
5
+ - AGI "request" variable coercer will not blow up if no request is given. (Helps in testing with netcat/telnet)
6
+
1
7
  0.8.2
8
+ - When a call hangs up, Adhearsion will no longer show random exceptions (that were okay) and instead allows the user to rescue a Hangup exception.
2
9
  - ManagerInterfaceResponse now include()s DRbUndumped, allowing send_action() to be called directly over DRb.
3
10
  - Fixes an inconsequential bug when CTL-C'ing Adhearsion.
4
11
 
@@ -112,7 +112,7 @@ ADHEARSION_FILES = %w{
112
112
 
113
113
  Gem::Specification.new do |s|
114
114
  s.name = "adhearsion"
115
- s.version = "0.8.2"
115
+ s.version = "0.8.3"
116
116
 
117
117
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
118
118
  s.authors = ["Jay Phillips"]
@@ -2,7 +2,7 @@ methods_for :rpc do
2
2
 
3
3
  # Simply create proxy methods for the high-level AMI methods
4
4
 
5
- [:send_action, :introduce, :originate, :call_into_context, :call_and_exec].each do |method_name|
5
+ [:send_action, :introduce, :originate, :call_into_context, :call_and_exec, :ping].each do |method_name|
6
6
  define_method(method_name) do |*args|
7
7
  if VoIP::Asterisk.manager_interface
8
8
  VoIP::Asterisk.manager_interface.send(method_name, *args)
@@ -29,7 +29,7 @@ initialization do
29
29
 
30
30
  host, port = config.values_at "host", "port"
31
31
 
32
- username, password = COMPONENTS.sandbox["username"], COMPONENTS.sandbox["password"]
32
+ username, password = COMPONENTS.sandbox["username"].to_s, COMPONENTS.sandbox["password"].to_s
33
33
 
34
34
  if username.blank? || password.blank? || username == "user123"
35
35
  ahn_log.sandbox.error "You must specify your username and password in this component's config file!"
@@ -5,7 +5,7 @@ unless defined? Adhearsion
5
5
  require File.dirname(__FILE__) + "/../adhearsion/lib/adhearsion.rb"
6
6
  else
7
7
  require 'rubygems'
8
- gem 'adhearsion', '>= 0.7.999'
8
+ gem 'adhearsion', '>= 0.8.2'
9
9
  require 'adhearsion'
10
10
  end
11
11
  end
@@ -5,7 +5,7 @@ module Adhearsion
5
5
  ##
6
6
  # Shuts down the framework.
7
7
  #
8
- def self.shutdown!
8
+ def shutdown!
9
9
  ahn_log "Shutting down gracefully at #{Time.now}."
10
10
  Events.stop!
11
11
  exit
@@ -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 = 2 unless defined? TINY
5
+ TINY = 3 unless defined? TINY
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.') unless defined? STRING
8
8
  end
@@ -4,12 +4,12 @@ require 'adhearsion/voip/menu_state_machine/menu_class'
4
4
  module Adhearsion
5
5
  module VoIP
6
6
  module Asterisk
7
- module Commands
7
+ module Commands
8
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
- # More information here: http://www.voip-info.org/wiki/index.php?page=Asterisk+variable+DIALSTATUS
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.
@@ -22,8 +22,8 @@ module Adhearsion
22
22
  # TORTURE: Privacy mode, callee chose to send caller to torture menu
23
23
  # INVALIDARGS: Error parsing Dial command arguments (added for Asterisk 1.4.1, SVN r53135-53136)
24
24
  #
25
- #
26
- DIAL_STATUSES = Hash.new(:unknown).merge(:answer => :answered,
25
+ # @see http://www.voip-info.org/wiki/index.php?page=Asterisk+variable+DIALSTATUS Asterisk Variable DIALSTATUS
26
+ DIAL_STATUSES = Hash.new(:unknown).merge(:answer => :answered, #:doc:
27
27
  :congestion => :congested,
28
28
  :busy => :busy,
29
29
  :cancel => :cancelled,
@@ -42,11 +42,13 @@ module Adhearsion
42
42
  end
43
43
  } unless defined? DYNAMIC_FEATURE_EXTENSIONS
44
44
 
45
- def write(message)
45
+ # Utility method to write to pbx.
46
+ def write(message)
46
47
  to_pbx.print(message)
47
48
  end
48
49
 
49
- def read
50
+ # Utility method to read from pbx. Hangup if nil.
51
+ def read
50
52
  returning from_pbx.gets do |message|
51
53
  raise Hangup if message.nil?
52
54
  ahn_log.agi.debug "<<< #{message}"
@@ -58,7 +60,7 @@ module Adhearsion
58
60
  # FAGI protocol.
59
61
  # It is not recommended that you call this method directly unless you plan to write a new command method
60
62
  # in which case use this method you to communicate directly with an Asterisk server via the FAGI protocol.
61
- # For more information about FAGI visit: http://www.voip-info.org/wiki/view/Asterisk+FastAGI
63
+ # @see http://www.voip-info.org/wiki/view/Asterisk+FastAGI More information about FAGI
62
64
  def raw_response(message = nil)
63
65
  ahn_log.agi.debug ">>> #{message}"
64
66
  write message if message
@@ -79,8 +81,7 @@ module Adhearsion
79
81
  end
80
82
 
81
83
  # This asterisk dialplan command allows you to instruct Asterisk to start applications
82
- # which are typically run from extensions.conf. For a complete list of these commands
83
- # please visit: http://www.voip-info.org/wiki/view/Asterisk+-+documentation+of+application+commands
84
+ # which are typically run from extensions.conf.
84
85
  #
85
86
  # The most common commands are already made available through the FAGI interface provided
86
87
  # by this code base. For commands that do not fall into this category, then exec is what you
@@ -89,22 +90,19 @@ module Adhearsion
89
90
  # For example, if there are specific asterisk modules you have loaded that will not
90
91
  # available through the standard commands provided through FAGI - then you can used EXEC.
91
92
  #
92
- # Example:
93
- # execute 'SIPAddHeader', '"Call-Info: answer-after=0"
94
- #
95
- # Using execute in this way will add a header to an existing SIP call.
93
+ # @example Using execute in this way will add a header to an existing SIP call.
94
+ # execute 'SIPAddHeader', '"Call-Info: answer-after=0"
96
95
  #
96
+ # @see http://www.voip-info.org/wiki/view/Asterisk+-+documentation+of+application+commands Asterisk Dialplan Commands
97
97
  def execute(application, *arguments)
98
98
  result = raw_response("EXEC #{application} #{arguments * '|'}")
99
99
  return false if error?(result)
100
100
  result
101
101
  end
102
102
 
103
- # Hangs up the current channel.
104
- # After this command is issued, your application will stop executing.
105
- # This should be used in the same way you would call the ruby exit() method to exit an application.
106
- # If it is necessary to do some additional cleanup tasks before returning control back to asterisk, then
107
- # make sure you have setup a begin...ensure block in the context of your adhearsion application dialplan.
103
+ # Hangs up the current channel. After this command is issued, you will not be able to send any more AGI
104
+ # commands but the dialplan Thread will still continue, allowing you to do any post-call work.
105
+ #
108
106
  def hangup
109
107
  raw_response 'HANGUP'
110
108
  end
@@ -118,11 +116,13 @@ module Adhearsion
118
116
  # file encoded using the current channel's codec, if one exists. If not, it will transcode from
119
117
  # the default codec (GSM). Asterisk stores its sound files in /var/lib/asterisk/sounds.
120
118
  #
121
- # Usage:
122
- #
119
+ # @example Play file hello-world.???
123
120
  # play 'hello-world'
121
+ # @example Speak current time
124
122
  # play Time.now
123
+ # @example Play sound file, speak number, play two more sound files
125
124
  # play %w"a-connect-charge-of 22 cents-per-minute will-apply"
125
+ # @example Play two sound files
126
126
  # play "you-sound-cute", "what-are-you-wearing"
127
127
  #
128
128
  def play(*arguments)
@@ -138,9 +138,11 @@ module Adhearsion
138
138
  #
139
139
  # Silence and maxduration is specified in seconds.
140
140
  #
141
- # Usage:
142
- # record
141
+ # @example Asterisk generated filename
142
+ # filename = record
143
+ # @example Specified filename
143
144
  # record '/path/to/my-file.gsm'
145
+ # @example All options specified
144
146
  # record 'my-file.gsm', :silence => 5, :maxduration => 120
145
147
  #
146
148
  def record(*args)
@@ -162,12 +164,13 @@ module Adhearsion
162
164
  execute "SendDTMF", digits.to_s
163
165
  end
164
166
 
167
+ # The with_next_message method...
165
168
  def with_next_message(&block)
166
169
  raise LocalJumpError, "Must supply a block" unless block_given?
167
170
  block.call(next_message)
168
171
  end
169
172
 
170
- # This command shouled be used to advance to the next message in the Asterisk Comedian Voicemail application
173
+ # This command should be used to advance to the next message in the Asterisk Comedian Voicemail application
171
174
  def next_message
172
175
  @call.inbox.pop
173
176
  end
@@ -177,11 +180,9 @@ module Adhearsion
177
180
  not @call.inbox.empty?
178
181
  end
179
182
 
180
- # = Menu Command
183
+ # Menu creates an interactive menu for the caller.
181
184
  #
182
- # The following documentation was derived from this blog post on Jay Phillips' blog:
183
- #
184
- # http://jicksta.com/articles/2008/02/11/menu-command
185
+ # The following documentation was derived from a post on Jay Phillips' blog (see below).
185
186
  #
186
187
  # The menu() command solves the problem of building enormous input-fetching state machines in Ruby without first-class
187
188
  # message passing facilities or an external DSL.
@@ -244,18 +245,18 @@ module Adhearsion
244
245
  # hook after the other hook (e.g. +on_invalid+, then +on_failure+).
245
246
  #
246
247
  # When the +menu()+ state machine runs through the defined rules, it must distinguish between exact and potential matches.
247
- # Its important to understand the differences between these and how they affect the overall outcome:
248
+ # It's important to understand the differences between these and how they affect the overall outcome:
248
249
  #
249
- # |---------------|-------------------|------------------------------------------------------|
250
- # | exact matches | potential matches | result |
251
- # |---------------|-------------------|------------------------------------------------------|
252
- # | 0 | 0 | Fail and start over |
253
- # | 1 | 0 | Match found! |
254
- # | 0 | >0 | Get another digit |
255
- # | >1 | 0 | Go with the first exact match |
256
- # | 1 | >0 | Get another digit. If timeout, use exact match |
257
- # | >1 | >0 | Get another digit. If timeout, use first exact match |
258
- # |---------------|-------------------|------------------------------------------------------|
250
+ # |---------------|-------------------|------------------------------------------------------|
251
+ # | exact matches | potential matches | result |
252
+ # |---------------|-------------------|------------------------------------------------------|
253
+ # | 0 | 0 | Fail and start over |
254
+ # | 1 | 0 | Match found! |
255
+ # | 0 | >0 | Get another digit |
256
+ # | >1 | 0 | Go with the first exact match |
257
+ # | 1 | >0 | Get another digit. If timeout, use exact match |
258
+ # | >1 | >0 | Get another digit. If timeout, use first exact match |
259
+ # |---------------|-------------------|------------------------------------------------------|
259
260
  #
260
261
  # == Database integration
261
262
  #
@@ -290,6 +291,7 @@ module Adhearsion
290
291
  # 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
291
292
  # point, making menu() effectively a pipeline of re-creating the call.
292
293
  #
294
+ # @see http://jicksta.com/articles/2008/02/11/menu-command Original Blog Post
293
295
  def menu(*args, &block)
294
296
  options = args.last.kind_of?(Hash) ? args.pop : {}
295
297
  sound_files = args.flatten
@@ -407,11 +409,11 @@ module Adhearsion
407
409
  buffer << key
408
410
  return buffer if number_of_digits && number_of_digits == buffer.length
409
411
  end
410
- key = wait_for_digit timeout || -1
412
+ key = wait_for_digit(timeout || -1)
411
413
  end
412
414
  end
413
415
 
414
- # An alternative to DialplanContextProc#+@. When jumping to a context, it will *not* resume executing
416
+ # Jumps to a context. An alternative to DialplanContextProc#+@. When jumping to a context, it will *not* resume executing
415
417
  # the former context when the jumped-to context has finished executing. Make sure you don't have any
416
418
  # +ensure+ closures which you expect to execute when the call has finished, as they will run when
417
419
  # this method is called.
@@ -439,6 +441,9 @@ module Adhearsion
439
441
  raise Adhearsion::VoIP::DSL::Dialplan::ControlPassingException.new(context)
440
442
  end
441
443
 
444
+ # The queue method puts a call into a call queue to be answered by an agent registered with that queue.
445
+ # 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
442
447
  def queue(queue_name)
443
448
  queue_name = queue_name.to_s
444
449
 
@@ -523,7 +528,7 @@ module Adhearsion
523
528
  # Used to join a particular conference with the MeetMe application. To
524
529
  # use MeetMe, be sure you have a proper timing device configured on your
525
530
  # Asterisk box. MeetMe is Asterisk's built-in conferencing program.
526
- # More info: http://www.voip-info.org/wiki-Asterisk+cmd+MeetMe
531
+ # @see http://www.voip-info.org/wiki-Asterisk+cmd+MeetMe Asterisk Meetme Application Information
527
532
  def join(conference_id, options={})
528
533
  conference_id = conference_id.to_s.scan(/\w/).join
529
534
  command_flags = options[:options].to_s # This is a passthrough string straight to Asterisk
@@ -536,9 +541,9 @@ module Adhearsion
536
541
  end
537
542
 
538
543
  # Issue this command to access a channel variable that exists in the asterisk dialplan (i.e. extensions.conf)
539
- # A complete description is available here: http://www.voip-info.org/wiki/view/get+variable
540
544
  # Use get_variable to pass information from other modules or high level configurations from the asterisk dialplan
541
545
  # to the adhearsion dialplan.
546
+ # @see: http://www.voip-info.org/wiki/view/get+variable Asterisk Get Variable
542
547
  def get_variable(variable_name)
543
548
  result = raw_response("GET VARIABLE #{variable_name}")
544
549
  case result
@@ -550,15 +555,18 @@ module Adhearsion
550
555
  end
551
556
 
552
557
  # Use set_variable to pass information back to the asterisk dial plan.
553
- # A complete decription is available here: http://www.voip-info.org/wiki/view/set+variable
554
558
  # Keep in mind that the variables are not global variables. These variables only exist for the channel
555
559
  # related to the call that is being serviced by the particular instance of your adhearsion application.
556
560
  # You will not be able to pass information back to the asterisk dialplan for other instances of your adhearsion
557
561
  # application to share. Once the channel is "hungup" then the variables are cleared and their information is gone.
562
+ # @see http://www.voip-info.org/wiki/view/set+variable Asterisk Set Variable
558
563
  def set_variable(variable_name, value)
559
564
  raw_response("SET VARIABLE %s %p" % [variable_name.to_s, value.to_s]) == "200 result=1"
560
565
  end
561
566
 
567
+ # The variable method allows you to either set or get a channel variable from Asterisk
568
+ # The method takes a hash key/value pair if you would like to set a variable
569
+ # Or a single string with the variable to get from Asterisk
562
570
  def variable(*args)
563
571
  if args.last.kind_of? Hash
564
572
  assignments = args.pop
@@ -575,6 +583,10 @@ module Adhearsion
575
583
  end
576
584
  end
577
585
 
586
+ # Use the voicemail method to send a caller to a voicemail box to leave a message.
587
+ # @see http://www.voip-info.org/tiki-index.php?page=Asterisk+cmd+VoiceMail Asterisk Voicemail
588
+ # The method takes the mailbox_number of the user to leave a message for and a
589
+ # greeting_option that will determine which message gets played to the caller.
578
590
  def voicemail(*args)
579
591
  options_hash = args.last.kind_of?(Hash) ? args.pop : {}
580
592
  mailbox_number = args.shift
@@ -607,6 +619,9 @@ module Adhearsion
607
619
  end
608
620
  end
609
621
 
622
+ # The voicemail_main method puts a caller into the voicemail system to fetch their voicemail
623
+ # or set options for their voicemail box.
624
+ # @see http://www.voip-info.org/wiki-Asterisk+cmd+VoiceMailMain Asterisk VoiceMailMain Command
610
625
  def voicemail_main(options={})
611
626
  mailbox, context, folder = options.values_at :mailbox, :context, :folder
612
627
  authenticate = options.has_key?(:authenticate) ? options[:authenticate] : true
@@ -643,42 +658,43 @@ module Adhearsion
643
658
  voicemail_main
644
659
  end
645
660
 
646
- # Use this command to dial an extension i.e. "phone number" in asterisk
647
- # This command maps to the Asterisk DIAL command in the asterisk dialplan: http://www.voip-info.org/wiki-Asterisk+cmd+Dial
661
+ # 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.
648
663
  #
649
664
  # The first parameter, number, must be a string that represents the extension or "number" that asterisk should dial.
650
665
  # Be careful to not just specify a number like 5001, 9095551001
651
666
  # You must specify a properly formatted string as Asterisk would expect to use in order to understand
652
667
  # whether the call should be dialed using SIP, IAX, or some other means.
653
- # Examples:
654
- #
655
- # Make a call to the PSTN using my SIP provider for VoIP termination:
656
- # dial("SIP/19095551001@my.sip.voip.terminator.us")
657
- #
658
- # Make 3 Simulataneous calls to the SIP extensions separated by & symbols, try for 15 seconds and use the callerid
659
- # for this call specified by the variable my_callerid
660
- # dial "SIP/jay-desk-650&SIP/jay-desk-601&SIP/jay-desk-601-2", :for => 15.seconds, :caller_id => my_callerid
661
- #
662
- # Make a call using the IAX provider to the PSTN
663
- # dial("IAX2/my.id@voipjet/19095551234", :name=>"John Doe", :caller_id=>"9095551234")
664
668
  #
665
669
  # Options Parameter:
666
- # :caller_id - the caller id number to be used when the call is placed. It is advised you properly adhere to the
670
+ #
671
+ # +:caller_id+ - the caller id number to be used when the call is placed. It is advised you properly adhere to the
667
672
  # policy of VoIP termination providers with respect to caller id values.
668
673
  #
669
- # :name - this is the name which should be passed with the caller ID information
674
+ # +:name+ - this is the name which should be passed with the caller ID information
670
675
  # if :name=>"John Doe" and :caller_id => "444-333-1000" then the compelete CID and name would be "John Doe" <4443331000>
671
676
  # support for caller id information varies from country to country and from one VoIP termination provider to another.
672
677
  #
673
- # :for - this option can be thought of best as a timeout. i.e. timeout after :for if no one answers the call
678
+ # +:for+ - this option can be thought of best as a timeout. i.e. timeout after :for if no one answers the call
674
679
  # For example, dial("SIP/jay-desk-650&SIP/jay-desk-601&SIP/jay-desk-601-2", :for => 15.seconds, :caller_id => callerid)
675
- # 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
680
+ # 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
676
681
  #
677
- # :options - This is a string of options like "Tr" which are supported by the asterisk DIAL application.
678
- # for a complete list of these options and their usage please visit: http://www.voip-info.org/wiki-Asterisk+cmd+Dial
682
+ # +:options+ - This is a string of options like "Tr" which are supported by the asterisk DIAL application.
683
+ # for a complete list of these options and their usage please check the link below.
679
684
  #
680
- # :confirm - ?
685
+ # +:confirm+ - ?
686
+ #
687
+ # @example Make a call to the PSTN using my SIP provider for VoIP termination
688
+ # dial("SIP/19095551001@my.sip.voip.terminator.us")
681
689
  #
690
+ # @example Make 3 Simulataneous calls to the SIP extensions separated by & symbols, try for 15 seconds and use the callerid
691
+ # for this call specified by the variable my_callerid
692
+ # dial "SIP/jay-desk-650&SIP/jay-desk-601&SIP/jay-desk-601-2", :for => 15.seconds, :caller_id => my_callerid
693
+ #
694
+ # @example Make a call using the IAX provider to the PSTN
695
+ # dial("IAX2/my.id@voipjet/19095551234", :name=>"John Doe", :caller_id=>"9095551234")
696
+ #
697
+ # @see http://www.voip-info.org/wiki-Asterisk+cmd+Dial Asterisk Dial Command
682
698
  def dial(number, options={})
683
699
  *recognized_options = :caller_id, :name, :for, :options, :confirm
684
700
 
@@ -728,29 +744,44 @@ module Adhearsion
728
744
  yield
729
745
  Time.now - start_time
730
746
  end
747
+
748
+ #
749
+ # This will play a sequence of files, stopping the playback if a digit is pressed. If a digit is pressed, it will be
750
+ # returned as a String. If the files played with no keypad input, nil will be returned.
751
+ #
752
+ def interruptible_play(*files)
753
+ files.flatten.each do |file|
754
+ result = result_digit_from raw_response("EXEC BACKGROUND #{file}")
755
+ return result if result != 0.chr
756
+ end
757
+ nil
758
+ end
731
759
 
732
760
  protected
733
761
 
762
+ # wait_for_digits waits for the input of digits based on the number of milliseconds
734
763
  def wait_for_digit(timeout=-1)
735
764
  timeout *= 1_000 if timeout != -1
736
765
  result = result_digit_from raw_response("WAIT FOR DIGIT #{timeout.to_i}")
737
766
  (result == 0.chr) ? nil : result
738
767
  end
739
768
 
769
+ ##
770
+ # Deprecated name of interruptible_play(). This is a misspelling!
771
+ #
740
772
  def interruptable_play(*files)
741
- files.flatten.each do |file|
742
- result = result_digit_from raw_response("EXEC BACKGROUND #{file}")
743
- return result if result != 0.chr
744
- end
745
- nil
773
+ 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
+ interruptible_play(*files)
746
775
  end
747
776
 
777
+ # set_callier_id_number method allows setting of the callerid number of the call
748
778
  def set_caller_id_number(caller_id)
749
779
  return unless caller_id
750
780
  raise ArgumentError, "Caller ID must be numerical" if caller_id.to_s !~ /^\d+$/
751
781
  raw_response %(SET CALLERID %p) % caller_id
752
782
  end
753
783
 
784
+ # set_caller_id_name method allows the setting of the callerid name of the call
754
785
  def set_caller_id_name(caller_id_name)
755
786
  return unless caller_id_name
756
787
  variable "CALLERID(name)" => caller_id_name
@@ -902,8 +933,8 @@ module Adhearsion
902
933
 
903
934
  # timeout with pressed digits: 200 result=<digits> (timeout)
904
935
  # timeout without pressed digits: 200 result= (timeout)
905
- # (http://www.voip-info.org/wiki/view/get+data)
906
- def input_timed_out?(result)
936
+ # @see http://www.voip-info.org/wiki/view/get+data AGI Get Data
937
+ def input_timed_out?(result)
907
938
  result.starts_with?(response_prefix) && result.ends_with?('(timeout)')
908
939
  end
909
940