adhearsion 0.7.5 → 0.7.6

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.
@@ -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