adhearsion 0.7.5 → 0.7.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -37,7 +37,24 @@ class PBX
37
37
  end
38
38
  @@sip_users[:users]
39
39
  end
40
- def self.record channel, file, format, mix
41
-
40
+
41
+ def self.originate hash
42
+ # This is an ugly hack to use until RAI's AMI work is ported in.
43
+ h = {}
44
+ h['Application'] = hash.delete :app
45
+ hash.each { |k,v| h[k.to_s.titleize] = v }
46
+ @@rami_client.originate h
47
+ end
48
+
49
+ def self.record hash={}, &block
50
+ # TODO: Implement me!
51
+ raise NotImplementedError
52
+
53
+ # defaults = {:file => "#{String.random(5)}", :folder => nil,
54
+ # :channel => Thread.current[:VARS]['channel'], :format => 'wav', :mix => '1'}
55
+ # defaults = defaults.merge hash
56
+ # PBX.rami_client.... # TODO
42
57
  end
58
+
59
+
43
60
  end
@@ -21,7 +21,7 @@ require 'webrick'
21
21
  require 'stringio'
22
22
 
23
23
  class WEBrick::HTTPRequest
24
- def ip() @peeraddr[3] end
24
+ def ip() @peeraddr[3][/(\d{1,3}\.){3}\d{1,3}/] end
25
25
  end
26
26
 
27
27
  # Micromenu catchers are special hooks that allow integration
data/lib/adhearsion.rb CHANGED
@@ -1,19 +1,19 @@
1
1
  # Adhearsion, open source technology integrator
2
- # Copyright 2006 Jay Phillips
2
+ # Copyright (C) 2006,2007 Jay Phillips
3
3
  #
4
- # This program is free software; you can redistribute it and/or
5
- # modify it under the terms of the GNU General Public License
6
- # as published by the Free Software Foundation; either version 2
7
- # of the License, or (at your option) any later version.
4
+ # This library is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU Lesser General Public
6
+ # License as published by the Free Software Foundation; either
7
+ # version 2.1 of the License, or (at your option) any later version.
8
8
  #
9
- # This program is distributed in the hope that it will be useful,
9
+ # This library is distributed in the hope that it will be useful,
10
10
  # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
- # GNU General Public License for more details.
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
13
  #
14
- # You should have received a copy of the GNU General Public License
15
- # along with this program; if not, write to the Free Software
16
- # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
 
18
18
  require 'core_extensions'
19
19
 
@@ -69,7 +69,7 @@ module Asterisk
69
69
  def input digits=nil, hash={}
70
70
  timeout, file = (hash[:timeout] || -1), (hash[:play] || hash[:file] || 'beep')
71
71
  result = rawr "GET DATA #{file} #{timeout} #{digits}"
72
- result = result[/\=-?\d+/]
72
+ result = result[/\=-?[\d*]+/]
73
73
  result ? result[1..-1] : false
74
74
  end
75
75
 
@@ -85,20 +85,66 @@ module Asterisk
85
85
  end
86
86
  alias bill time
87
87
 
88
- # Requires an AMI connection! Given a block, everything contained
89
- # within it will be recorded. If no arguments are given, a String
90
- # for the filename will be generated from the current time and a
91
- # sequence of random alphanumeric characters.
92
- def record hash={}, &block
93
- defaults = {:file => "#{String.random(5)}", :folder => nil,
94
- :channel => Thread.current[:VARS]['channel'], :format => 'wav', :mix => '1'}
95
- defaults = defaults.merge hash
96
- #PBX.record
88
+ # When called, this command will record the current channel to a file. The
89
+ # recording will stop either when the caller hangs up or when she presses
90
+ # the # key. Four arguments exist for your use:
91
+ #
92
+ # - :file defaults to "/tmp/recording_%d.wav" where %d is a number cleverly
93
+ # incremented by Asterisk. If you override this argument, you too can
94
+ # use '%d' in the filename to have it automatically incremented for you.
95
+ # The return value of record() will be whatever filename Asterisk just used.
96
+ # Filenames can be either an absolute path or simple name. When no apparent
97
+ # directory is given, Asterisk will use /var/lib/asterisk/sounds. Ensure this
98
+ # directory is writable to the user account with which Asterisk runs.
99
+ #
100
+ # - :silence specifies the number of seconds Asterisk should wait in silence
101
+ # before ending the call.
102
+ #
103
+ # - :max is the maximum length of the sound file in seconds. Feel free to use
104
+ # Adhearsion's Fixnum convenience methods here (e.g. 1.minute, 12.hours)
105
+ #
106
+ # - :options can be 'skip' to return immediately if the line is not up, or
107
+ # 'noanswer' to record even if the line is not up.
108
+ #
109
+ # === Notes
110
+ #
111
+ # Keep in mind if you user decides to hang up to end the recording, the thread
112
+ # handling the call will end but the file will still be saved. Try to do any
113
+ # necessary processing before-hand. If you're trying to use the return value
114
+ # of record(), you may not get it if the thread gets terminated. Instead, create
115
+ # your own filename and save it somewhere safe (in a database for example) and
116
+ # then record the user to your pre-created filename.
117
+ #
118
+ # === Examples
119
+ #
120
+ # record # A filename will be made for you and returned
121
+ # record :file => 'foo.wav'
122
+ # record :max => 2.minutes + 30.seconds
123
+ # record :file => '/tmp/recordings/COMPLAINT_%d.gsm', :silence => 5
124
+ #
125
+ def record hash={}
126
+ raise NotImplementedError if block_given? # TODO support recording a block of code
127
+ defaults = {:file => "/tmp/recording_%d.wav", :silence => 0, :max => 0}
128
+ args = defaults.merge hash
129
+
130
+ exec :Record, args[:file], args[:silence], args[:max], args[:options]
131
+ args[:file].index('%d') ? get_variable('RECORDED_FILE')[1..-2] : args[:file]
97
132
  end
133
+
134
+ # Invokes the AGI RECORD FILE method as outlined below
135
+ # Note that the timeout value must be expressed in milliseconds
136
+ def record_file file, hash={}
137
+ #RECORD FILE <filename> <format> <escape digits> <timeout>
138
+ format, digits, timeout, beep, silence = (hash[:format] || 'gsm'), (hash[:digits] || '#'),
139
+ (hash[:timeout] || 15000), (hash[:beep] ? 'BEEP' : nil), ("s=#{hash[:silence]}" || nil)
140
+ result = rawr "RECORD FILE #{file} #{format} \"#{digits}\" #{timeout} #{beep} #{silence}"
141
+ result = result[/\=-?\d+/]
142
+ result ? result[1..-1] : false
143
+ end
98
144
 
99
145
  # Waits for single digit numpad key response from the user. If no timeout argument
100
146
  # is given, the system will wait indefinitely. The argument is the desired time
101
- # to wait fo0r the response in seconds. Feel free to use the ActiveSupport extensions
147
+ # to wait for the response in seconds. Feel free to use the ActiveSupport extensions
102
148
  # for using this method like wait_for_digit(2.minutes) or something similar. When the
103
149
  # timeout is encountered (or an error occurs receiving the input), the method
104
150
  # returns nil. If the asterisk or pound key was pressed, a String is returned of that
@@ -106,7 +152,7 @@ module Asterisk
106
152
  def wait_for_digit timeout=-1
107
153
  digit = rawr("WAIT FOR DIGIT #{timeout < 0 ? -1 : timeout / 1000.0}").match(/=(.*)$/)[1].to_i
108
154
  return nil if digit <= 0 # If there was an error or timeout
109
- return digit.chr if [32,45].include? digit
155
+ return digit.chr if (32..45).include? digit
110
156
  digit - ?0
111
157
  end
112
158
 
@@ -134,7 +180,7 @@ module Asterisk
134
180
  # of syntactically sweet formats:
135
181
  #
136
182
  # - A "Group" of users. This is determined by whether the passed group
137
- # responds to the +members+, +users+, +member+, or +user+ methods. If
183
+ # responds to the members, users, member, or user methods. If
138
184
  # so, all members of that group will ring simultaneously. This would
139
185
  # be especially useful for, say, technical support groups which all
140
186
  # share the same line. When the first person picks up the line, the
@@ -143,7 +189,7 @@ module Asterisk
143
189
  # standards below.
144
190
  # - An Array of "Users".
145
191
  # - A single "User". A User is defined by something that responds to the
146
- # +extension+ or +extensions+ methods. The return value of these two
192
+ # extension or extensions methods. The return value of these two
147
193
  # methods can either be any of the following formats:
148
194
  # - A String. The String can contain a simple number or be a fully
149
195
  # qualified Asterisk-ready String. e.g. 'SIP/codemecca@sipphone'.
@@ -177,19 +223,19 @@ module Asterisk
177
223
  # of time (which you likely will to have the call redirect to voicemail for
178
224
  # example), then a :for => 15.seconds type of syntax can be used. Examples:
179
225
  #
180
- # - dial :jay, :for => 2.minutes
226
+ # dial :jay, :for => 2.minutes
181
227
  #
182
228
  # The result of the last call can be retrieved by using the dial plan
183
- # methods +last_call_successful?+, +last_call_unsuccessful?+, or
184
- # +last_dial_status+. Note: +last_dial_status+ returns a Symbol representing
229
+ # methods last_call_successful?, last_call_unsuccessful?, or
230
+ # last_dial_status. Note: last_dial_status returns a Symbol representing
185
231
  # the result of the last call. For more information, see its appropriate
186
232
  # documentation.
187
233
  #
188
234
  # Additionally, you can use the :options hash key argument to specify options
189
235
  # which Asterisk's Dial() application normally takes. For example:
190
236
  #
191
- # - dial :jay, :for => 30.seconds, :options => 'tmw'
192
- # - dial 1_444_555_6666, :options => 'S(30)'
237
+ # dial :jay, :for => 30.seconds, :options => 'tmw'
238
+ # dial 1_444_555_6666, :options => 'S(30)'
193
239
  #
194
240
  # == Resources
195
241
  #
@@ -322,18 +368,183 @@ module Asterisk
322
368
  end
323
369
  end
324
370
 
371
+ # == Dialplan Instruction Caching
372
+ #
373
+ # If a cynic ever says Ruby doesn't make an efficient dial plan manager,
374
+ # point them to this little method. The cache() method is given a block
375
+ # of code and will automagically cache whatever's inside it. The caching
376
+ # mechanism can even be refined by specifying a time-to-live on the cache
377
+ # and special variables on which the cache depends.
378
+ #
379
+ # Here is a sample block of dial plan code that would be cached indefinitely:
380
+ #
381
+ # play_commands {
382
+ # cache do
383
+ # File.read('commands.txt').each { |sound_file| play sound_file }
384
+ # end
385
+ # }
386
+ #
387
+ # This example reads commands.txt, an arbitrary file containing sound file names
388
+ # on each line and plays them. Because the file access and String
389
+ # manipulation may be resource intensive, the commands sent to Asterisk
390
+ # will be cached indefinitely (until Adhearsion restarts in this case), but
391
+ # the file will be read and parsed only once, improving performance significantly.
392
+ #
393
+ # == Caching for a Certain Amount of Time
394
+ #
395
+ # Often you may want to cache a block of code, but not indefinitely. A
396
+ # hash key argument can be given to the cache method to have the stored
397
+ # code expire after a certain duration.
398
+ #
399
+ # cache :for => 1.hour do
400
+ # play weather_report('Dallas, Texas')
401
+ # end
402
+ #
403
+ # This example uses the weather helper to pull down information from the internet
404
+ # and process it. While on a small scale calling this method often would only be
405
+ # inconvenient to the user waiting for the content, having potentially hundreds
406
+ # of users calling this method would greatly drag the system down. Caching it for
407
+ # one hour keeps the system snappy and the users happy.
408
+ #
409
+ # == Caching Per a Variable
410
+ #
411
+ # cache :per => extension do
412
+ # employee = User.find_by_extension extension
413
+ # dial extension
414
+ # end
415
+ #
416
+ # Another more creative example:
417
+ #
418
+ # cache :per => extension / 100 do
419
+ # # This would be useful if you separated your IVR's endpoints into
420
+ # # blocks of 100. For example, say every employee had an extension
421
+ # # between 100 and 199. In this case, (extension / 100) would
422
+ # # return 1. In this way, any numbers between 100 and 199 would have
423
+ # # instructions unique to them cached. Say extension 200 through 299
424
+ # # dialed in conference. In this case, instructions pertaining only
425
+ # # to conferences would be cached (extension/100 == 2).
426
+ # end
427
+ #
428
+ # But don't do this:
429
+ #
430
+ # cache :per => extension do
431
+ # user = User.find_by_extension extension
432
+ # speak "Calling #{user.name}"
433
+ # im user.jabber, "Incoming call!"
434
+ # dial user
435
+ # end
436
+ #
437
+ # In this case, the instant message will be sent only once until the cache
438
+ # expires. This is probably not what you wanted. A working (albeit trivially
439
+ # simplistic) way to do this would be:
440
+ #
441
+ # user = User.find_by_extension extension
442
+ # cache :per => extension do
443
+ # speak "Calling #{user.name}"
444
+ # end
445
+ # im user.jabber, "Incoming call from #{calleridname}!"
446
+ # cache :per => extension do
447
+ # dial user
448
+ # end
449
+ #
450
+ # Yet another cool example:
451
+ #
452
+ # cache :per => Time.now.wday, :for => 1.month do
453
+ # # Cache your instructions based on the day of the week, refreshing
454
+ # # no more than once a month.
455
+ # end
456
+ #
457
+ # === Caching Per Multiple Variables
458
+ #
459
+ # If you should wish to cache per several variables, simply encapsulate them in an
460
+ # Array. For example:
461
+ #
462
+ # cache :per => [extension, callerid], :for => 1.day+2.hours+30.minutes do
463
+ # # Do your crazy crap here
464
+ # end
465
+ #
466
+ # == Notes on Caching
467
+ #
468
+ # - All caches are flushed completely when Adhearsion restarts
469
+ #
470
+ # - If a phone call ends before a cache block finishes, nothing will be cached (a good thing).
471
+ #
472
+ # - Presently, you cannot nest cache blocks. Doing this will produce bizarre results.
473
+ #
474
+ # - Common sense should tell you not to cache per constantly changing things.
475
+ # For example, if you're receiving calls from all over the country, don't
476
+ # cache per everyone's phone number. This will probably result in an
477
+ # OutOfMemoryError as everyone's number will have cached instructions. The
478
+ # better way to do this would be to cache the blocks that all numbers share,
479
+ # then execute content-specific
480
+ #
481
+ # - Variables declared inside of the block won't be accessible outside of the block.
482
+ # If you don't want this, declare them outside of the block and use them inside.
483
+ #
484
+ # - If you're doing non-dialplan related stuff within a cache, it will only be
485
+ # performed once. Don't put your logic to count billing time or something
486
+ # similar because that varies between each call. Caching only caches what is
487
+ # sent to Asterisk, not your Ruby code (of course).
488
+ #
489
+ # - If cached code relies on data from a database, it's best to set its expiration
490
+ # to something brief (5 or 10 minutes) because database content is *supposed*
491
+ # to change, but the demand of pulling the content out may affect scaling. Having
492
+ # the data cached for a short amount of time is happy middle ground.
493
+ def cache hash={}, &block
494
+ raise ArgumentError, "Must provide a block to cache!" unless block_given?
495
+
496
+ constraint = hash[:per]
497
+ ttl = hash[:for]
498
+
499
+ # $cache_dir is a Hash of caller()s. The repository from $cache_dir is
500
+ # another Hash mapping the :per elements to the cached queue (Array) of
501
+ # rawr() commands.
502
+
503
+ repo = nil
504
+ $cache_dir.synchronize do |dir|
505
+ repo = dir[caller]
506
+ repo = dir[caller] = {} unless repo
507
+ end
508
+
509
+ # The first element of each queue is a Time to be used for comparison
510
+ # with Time.now to determine whether the cache is stale.
511
+ queue = repo[constraint]
512
+ queue = nil if queue && queue.first && queue.first < Time.now
513
+
514
+ # If a unexpired queue exists, let's ship its commands off to rawr()
515
+ if queue then queue[1..-1].each { |cmd| rawr cmd }
516
+ else
517
+ # Here, several things may have happened. The call to cache
518
+ # could have been executed for the first time, there was no cached
519
+ # queue for a particular constraint, or the cached expiration's
520
+ # date has been met.
521
+
522
+ # We cache the commands by setting Thread.current[:cache] which rawr()
523
+ # will fill with commands it gets. yield then executes the block and
524
+ # we pull out the commands rawr() gave.
525
+ Thread.current[:cache] = []
526
+ yield
527
+ queue = Thread.current[:cache]
528
+ queue.unshift ttl ? ttl.from_now : nil
529
+ $cache_dir.synchronize { repo[constraint] = queue }
530
+ Thread.current[:cache] = nil
531
+ end
532
+ end
533
+ $cache_dir = {}
534
+
325
535
  # The rawr() method is the main way of receiving a raw response from the Asterisk
326
536
  # server. When no argument is given, it will immediately ask for a response,
327
537
  # returning that String. When an argument +is+ given, it will first send that
328
538
  # command and then return the response that command generated. Everything is
329
- # chomp()ed before returned. Pun here totally intended.
539
+ # chomp()ed before returned.
330
540
  def rawr(what=nil)
331
- begin
332
- putc what if what
333
- PBX.io.gets.chomp!
334
- rescue => e
335
- #log "Socket no longer available for communication. Exceptions will likely occur."
541
+ if what
542
+ putc what
543
+ Thread.current[:cache] << what if Thread.current[:cache]
336
544
  end
545
+ PBX.io.gets.chomp!
546
+ rescue
547
+ log "Call likely ended (socket closed). It's okay if exceptions follow."
337
548
  end
338
549
 
339
550
  # Very simply sends the command over the AGI IO socket and forgets about it.
@@ -381,10 +592,10 @@ module Asterisk
381
592
  end
382
593
 
383
594
  # Answer the channel. Adhearsion is configured by default to automatically do this
384
- # when a call comes in.
595
+ # when a call comes in. This behavior can be specified in adhearsion.yml.
385
596
  def answer() rawr 'ANSWER' end
386
597
  # Hangs up the channel. Adhearsion is configured by default to matically do this
387
- # when a context completes execution
598
+ # when a context completes execution. This behavior can be specified in adhearsion.yml.
388
599
  def hangup() rawr 'HANGUP' end
389
600
  # Direct translation of the Asterisk NoOp() application. Used primarily for
390
601
  # viewing debug information in the Asterisk CLI.
@@ -416,14 +627,14 @@ class IAX
416
627
  # Simple convenience method to make Adhearsion's DSL more Asterisk-like.
417
628
  # Virtually anything can be provided after the "/", as long as its to_s()
418
629
  # method is what you intended to represent.
419
- def self./(arg=nil) IAX(arg) end
630
+ def self./(arg=nil) "IAX2/#{arg}" end
420
631
  end
421
632
 
422
633
  class ZAP
423
634
  # Simple convenience method to make Adhearsion's DSL more Asterisk-like.
424
635
  # Virtually anything can be provided after the "/", as long as its to_s()
425
636
  # method is what you intended to represent.
426
- def self./(arg=nil) ZAP(arg) end
637
+ def self./(arg=nil) "Zap/#{arg}" end
427
638
  end
428
639
 
429
640
  # For users who may try to use the (proper) IAX2 form. See IAX for more info.
@@ -437,6 +648,10 @@ class Zap < ZAP; end
437
648
  # this in your own helpers, all methods will need to be class (a.k.a. static) methods
438
649
  # since no actual instance of this object is passed around.
439
650
  class PBX
651
+ def method_missing name
652
+ raise NameError, "Method #{name} not found! Are you sure manager_proxy is configured properly?"
653
+ end
654
+
440
655
  def PBX.io() Thread.current[:io] end
441
656
  end
442
657
 
@@ -476,7 +691,7 @@ class Contexts
476
691
  end
477
692
 
478
693
  def method_missing name, *args, &block
479
- Kernel.method_missing name, *args, &block
694
+ Object::method_missing name, *args, &block
480
695
  end
481
696
 
482
697
  def run_inside &code
@@ -484,9 +699,7 @@ class Contexts
484
699
  end
485
700
 
486
701
  def eval_inside code
487
- run_inside do
488
- eval code
489
- end
702
+ run_inside { eval code }
490
703
  end
491
704
  end
492
705
  end