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