adhearsion 0.7.5 → 0.7.6
Sign up to get free protection for your applications and to get access to all the features.
- data/.version +1 -1
- data/CHANGELOG +25 -18
- data/LICENSE +386 -269
- data/Rakefile +4 -4
- data/TODO +22 -0
- data/ahn +72 -56
- data/apps/default/config/adhearsion.sqlite3 +0 -0
- data/apps/default/config/adhearsion.yml +52 -1
- data/apps/default/config/helpers/growler.yml +21 -0
- data/apps/default/config/migration.rb +7 -11
- data/apps/default/helpers/growler.rb +53 -0
- data/apps/default/helpers/lookup.rb +27 -18
- data/apps/default/helpers/manager_proxy.rb +19 -2
- data/apps/default/helpers/micromenus.rb +1 -1
- data/lib/adhearsion.rb +257 -44
- data/lib/constants.rb +15 -12
- data/lib/core_extensions.rb +11 -11
- data/lib/drb_server.rb +101 -0
- data/lib/logging.rb +18 -1
- data/lib/servlet_container.rb +23 -23
- data/test/asterisk_module_test.rb +1 -1
- metadata +35 -23
- data/apps/default/helpers/drb_server.rb +0 -32
@@ -37,7 +37,24 @@ class PBX
|
|
37
37
|
end
|
38
38
|
@@sip_users[:users]
|
39
39
|
end
|
40
|
-
|
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
|
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
|
5
|
-
# modify it under the terms of the GNU General Public
|
6
|
-
# as published by the Free Software Foundation; either
|
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
|
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
|
-
#
|
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
|
15
|
-
# along with this
|
16
|
-
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
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[
|
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
|
-
#
|
89
|
-
#
|
90
|
-
#
|
91
|
-
#
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
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
|
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
|
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
|
-
#
|
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
|
-
#
|
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
|
184
|
-
#
|
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
|
-
#
|
192
|
-
#
|
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.
|
539
|
+
# chomp()ed before returned.
|
330
540
|
def rawr(what=nil)
|
331
|
-
|
332
|
-
putc what
|
333
|
-
|
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)
|
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)
|
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
|
-
|
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
|
488
|
-
eval code
|
489
|
-
end
|
702
|
+
run_inside { eval code }
|
490
703
|
end
|
491
704
|
end
|
492
705
|
end
|