AsteriskRuby 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +174 -0
- data/examples/AGIMenu/config/menu.yaml +78 -0
- data/examples/AGIMenu/example1-filename.rb +64 -0
- data/examples/AGIMenu/example2-file.rb +65 -0
- data/examples/AGIMenu/example3-yaml.rb +65 -0
- data/examples/AGIMenu/example4-hash.rb +74 -0
- data/examples/AGIMenu/example5-empty.rb +65 -0
- data/examples/AGISelection/example1.rb +68 -0
- data/examples/AGIServer/config/example-config.yaml +64 -0
- data/examples/AGIServer/example1-routing.rb +105 -0
- data/examples/AGIServer/example2-agiblock.rb +91 -0
- data/examples/AGIServer/example3-agiparamsblock.rb +92 -0
- data/examples/AGIState/example1.rb +65 -0
- data/lib/AGI.rb +1072 -0
- data/lib/AGIExceptions.rb +139 -0
- data/lib/AGIFramework.rb +69 -0
- data/lib/AGIMenu.rb +244 -0
- data/lib/AGIResponse.rb +160 -0
- data/lib/AGIRoute.rb +72 -0
- data/lib/AGIRouter.rb +153 -0
- data/lib/AGISelection.rb +155 -0
- data/lib/AGIServer.rb +295 -0
- data/lib/AGIState.rb +91 -0
- data/lib/AsteriskRuby.rb +65 -0
- data/tests/AGI/tests.rb +519 -0
- data/tests/AGIMenu/config/menu.yaml +79 -0
- data/tests/AGIMenu/tests.rb +179 -0
- data/tests/AGISelection/tests.rb +104 -0
- data/tests/AGIServer/tests.rb +224 -0
- data/tests/AGIState/tests.rb +80 -0
- metadata +100 -0
@@ -0,0 +1,92 @@
|
|
1
|
+
#!/usr/local/bin/ruby -wKU -I ../../lib
|
2
|
+
## Copyright (c) 2007, Vonage Holdings
|
3
|
+
##
|
4
|
+
## All rights reserved.
|
5
|
+
##
|
6
|
+
## Redistribution and use in source and binary forms, with or without
|
7
|
+
## modification, are permitted provided that the following conditions are met:
|
8
|
+
##
|
9
|
+
## * Redistributions of source code must retain the above copyright
|
10
|
+
## notice, this list of conditions and the following disclaimer.
|
11
|
+
## * Redistributions in binary form must reproduce the above copyright
|
12
|
+
## notice, this list of conditions and the following disclaimer in the
|
13
|
+
## documentation and/or other materials provided with the distribution.
|
14
|
+
## * Neither the name of Vonage Holdings nor the names of its
|
15
|
+
## contributors may be used to endorse or promote products derived from this
|
16
|
+
## software without specific prior written permission.
|
17
|
+
##
|
18
|
+
## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
19
|
+
## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
20
|
+
## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
21
|
+
## ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
22
|
+
## LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
23
|
+
## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
24
|
+
## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
25
|
+
## INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
26
|
+
## CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
27
|
+
## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
28
|
+
## POSSIBILITY OF SUCH DAMAGE.
|
29
|
+
##
|
30
|
+
## Author: Michael Komitee <mkomitee@gmail.com>
|
31
|
+
|
32
|
+
require 'AGIServer'
|
33
|
+
require 'yaml'
|
34
|
+
|
35
|
+
trap('INT') { AGIServer.shutdown }
|
36
|
+
trap('TERM') { AGIServer.shutdown }
|
37
|
+
|
38
|
+
logger = Logger.new(STDERR)
|
39
|
+
logger.level = Logger::DEBUG
|
40
|
+
|
41
|
+
config = YAML.load_file('config/example-config.yaml')
|
42
|
+
config[:logger] = logger
|
43
|
+
config[:params] = {:custom1 => 'data1'}
|
44
|
+
|
45
|
+
begin
|
46
|
+
MyAgiServer = AGIServer.new(config)
|
47
|
+
rescue Errno::EADDRINUSE
|
48
|
+
error = "Cannot start MyAgiServer, Address already in use."
|
49
|
+
logger.fatal(error)
|
50
|
+
puts error
|
51
|
+
exit
|
52
|
+
else
|
53
|
+
puts "#{$$}"
|
54
|
+
end
|
55
|
+
|
56
|
+
trap('INT') { AGIServer.shutdown }
|
57
|
+
trap('TERM') { AGIServer.shutdown }
|
58
|
+
|
59
|
+
MyAgiServer.start do |agi,params|
|
60
|
+
agi.answer
|
61
|
+
print "PARAMS = #{params.pretty_inspect}"
|
62
|
+
agi.hangup
|
63
|
+
end
|
64
|
+
MyAgiServer.finish
|
65
|
+
|
66
|
+
## Copyright (c) 2007, Vonage Holdings
|
67
|
+
##
|
68
|
+
## All rights reserved.
|
69
|
+
##
|
70
|
+
## Redistribution and use in source and binary forms, with or without
|
71
|
+
## modification, are permitted provided that the following conditions are met:
|
72
|
+
##
|
73
|
+
## * Redistributions of source code must retain the above copyright
|
74
|
+
## notice, this list of conditions and the following disclaimer.
|
75
|
+
## * Redistributions in binary form must reproduce the above copyright
|
76
|
+
## notice, this list of conditions and the following disclaimer in the
|
77
|
+
## documentation and/or other materials provided with the distribution.
|
78
|
+
## * Neither the name of Vonage Holdings nor the names of its
|
79
|
+
## contributors may be used to endorse or promote products derived from this
|
80
|
+
## software without specific prior written permission.
|
81
|
+
##
|
82
|
+
## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
83
|
+
## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
84
|
+
## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
85
|
+
## ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
86
|
+
## LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
87
|
+
## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
88
|
+
## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
89
|
+
## INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
90
|
+
## CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
91
|
+
## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
92
|
+
## POSSIBILITY OF SUCH DAMAGE.
|
@@ -0,0 +1,65 @@
|
|
1
|
+
#!/usr/local/bin/ruby -wKU -I ../../lib
|
2
|
+
## Copyright (c) 2007, Vonage Holdings
|
3
|
+
##
|
4
|
+
## All rights reserved.
|
5
|
+
##
|
6
|
+
## Redistribution and use in source and binary forms, with or without
|
7
|
+
## modification, are permitted provided that the following conditions are met:
|
8
|
+
##
|
9
|
+
## * Redistributions of source code must retain the above copyright
|
10
|
+
## notice, this list of conditions and the following disclaimer.
|
11
|
+
## * Redistributions in binary form must reproduce the above copyright
|
12
|
+
## notice, this list of conditions and the following disclaimer in the
|
13
|
+
## documentation and/or other materials provided with the distribution.
|
14
|
+
## * Neither the name of Vonage Holdings nor the names of its
|
15
|
+
## contributors may be used to endorse or promote products derived from this
|
16
|
+
## software without specific prior written permission.
|
17
|
+
##
|
18
|
+
## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
19
|
+
## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
20
|
+
## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
21
|
+
## ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
22
|
+
## LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
23
|
+
## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
24
|
+
## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
25
|
+
## INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
26
|
+
## CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
27
|
+
## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
28
|
+
## POSSIBILITY OF SUCH DAMAGE.
|
29
|
+
##
|
30
|
+
## Author: Michael Komitee <mkomitee@gmail.com>
|
31
|
+
|
32
|
+
require 'AGIState'
|
33
|
+
|
34
|
+
state = AGIState.new(:threshold => 10)
|
35
|
+
10.times do
|
36
|
+
state.failure_inc
|
37
|
+
end
|
38
|
+
|
39
|
+
## Copyright (c) 2007, Vonage Holdings
|
40
|
+
##
|
41
|
+
## All rights reserved.
|
42
|
+
##
|
43
|
+
## Redistribution and use in source and binary forms, with or without
|
44
|
+
## modification, are permitted provided that the following conditions are met:
|
45
|
+
##
|
46
|
+
## * Redistributions of source code must retain the above copyright
|
47
|
+
## notice, this list of conditions and the following disclaimer.
|
48
|
+
## * Redistributions in binary form must reproduce the above copyright
|
49
|
+
## notice, this list of conditions and the following disclaimer in the
|
50
|
+
## documentation and/or other materials provided with the distribution.
|
51
|
+
## * Neither the name of Vonage Holdings nor the names of its
|
52
|
+
## contributors may be used to endorse or promote products derived from this
|
53
|
+
## software without specific prior written permission.
|
54
|
+
##
|
55
|
+
## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
56
|
+
## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
57
|
+
## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
58
|
+
## ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
59
|
+
## LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
60
|
+
## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
61
|
+
## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
62
|
+
## INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
63
|
+
## CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
64
|
+
## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
65
|
+
## POSSIBILITY OF SUCH DAMAGE.
|
data/lib/AGI.rb
ADDED
@@ -0,0 +1,1072 @@
|
|
1
|
+
=begin rdoc
|
2
|
+
Copyright (c) 2007, Vonage Holdings
|
3
|
+
|
4
|
+
All rights reserved.
|
5
|
+
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
8
|
+
|
9
|
+
* Redistributions of source code must retain the above copyright
|
10
|
+
notice, this list of conditions and the following disclaimer.
|
11
|
+
* Redistributions in binary form must reproduce the above copyright
|
12
|
+
notice, this list of conditions and the following disclaimer in the
|
13
|
+
documentation and/or other materials provided with the distribution.
|
14
|
+
* Neither the name of Vonage Holdings nor the names of its
|
15
|
+
contributors may be used to endorse or promote products derived from this
|
16
|
+
software without specific prior written permission.
|
17
|
+
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
19
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
20
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
21
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
22
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
23
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
24
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
25
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
26
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
27
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
28
|
+
POSSIBILITY OF SUCH DAMAGE.
|
29
|
+
|
30
|
+
Author: Michael Komitee <mkomitee@gmail.com>
|
31
|
+
|
32
|
+
The AGI object can be used to interact with an Asterisk. It can be used within the AGIServer framework, or independantly. Simply instantiate an AGI object and pass it input and output IO objects. Asterisk extensions can exec an agi script (in asterisk parlance, agi or deadagi), in which case you'll want to use stdin and stdout, or asterisk extensions can connect to a daemonized agi server (in asterisk parlance, fagi) and you'd want to use a tcp socket.
|
33
|
+
|
34
|
+
agi = AGI.new(:input => STDIN, :output => STDOUT)
|
35
|
+
agi.init
|
36
|
+
agi.answer
|
37
|
+
agi.hangup
|
38
|
+
|
39
|
+
Note, all agi instances will be uninitialized. The initialization process of an agi channel must be performed before any other interaction with the channel can be accomplished.
|
40
|
+
=end
|
41
|
+
|
42
|
+
#AGI is the Asterisk Gateway Interface, an interface for adding functionality to asterisk. This class implements an object that knows the AGI language and can therefore serve as a bridge between ruby and Asterisk. It can interact with any IO object it's given, so can be used in a sockets based FastAGI or a simple STDIN/STDOUT based Fork-Exec'd AGI. Please see {The Voip Info Asterisk AGI site}[http://www.voip-info.org/wiki-Asterisk+AGI] for more details.
|
43
|
+
require 'AGIExceptions.rb'
|
44
|
+
require 'AGIResponse.rb'
|
45
|
+
require 'logger'
|
46
|
+
|
47
|
+
#AGI is the Asterisk Gateway Interface, an interface for adding functionality to asterisk. This class implements an object that knows the AGI language and can therefore serve as a bridge between ruby and Asterisk. It can interact with any IO object it's given, so can be used in a sockets based FastAGI or a simple STDIN/STDOUT based Fork-Exec'd AGI. Please see {The Voip Info Asterisk AGI site}[http://www.voip-info.org/wiki-Asterisk+AGI] for more details.
|
48
|
+
class AGI
|
49
|
+
# Channel Parameters, populated by init
|
50
|
+
attr_reader :channel_params
|
51
|
+
# Additional AGI parameters provided to new
|
52
|
+
attr_reader :init_params
|
53
|
+
#Creates an AGI Object based on the provided Parameter Hash.
|
54
|
+
#* :input sets the IO object to use for AGI inbound communication from Asterisk, Defaults to STDIN.
|
55
|
+
#* :output sets the IO object to use for AGI outbound communication to Asterisk, Defaults to STDOUT:
|
56
|
+
#* :logger sets the Logger object to use for logging. Defaults to Logger.new(STDERR).
|
57
|
+
#
|
58
|
+
#Please note, everything else provided in the hash is available in the resulting object's init_params accessor.
|
59
|
+
def initialize(params={})
|
60
|
+
@input = params[:input] || STDIN
|
61
|
+
@output = params[:output] || STDOUT
|
62
|
+
@logger = params[:logger] || Logger.new(STDERR)
|
63
|
+
@init_params = params # To store other options for user
|
64
|
+
@channel_params = {}
|
65
|
+
@last_response = nil
|
66
|
+
@initialized = false
|
67
|
+
end
|
68
|
+
|
69
|
+
#Causes Asterisk to answer the channel.
|
70
|
+
#
|
71
|
+
#Returns an AGIResponse object.
|
72
|
+
def answer
|
73
|
+
response = AGIResponse.new
|
74
|
+
command_str = "ANSWER"
|
75
|
+
begin
|
76
|
+
response.native = execute(command_str)
|
77
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
78
|
+
raise
|
79
|
+
end
|
80
|
+
if response.native == -1 then
|
81
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
82
|
+
elsif response.native == 0
|
83
|
+
response.success = true
|
84
|
+
end
|
85
|
+
return response
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
#Signals Asterisk to stream the given audio file(s) to the channel. If digits are provided, allows the user to terminate the audio transmission by supplying DTMF. This differs from stream file because it does not accept an offset, and accepts an Array of sound files to play. It actually uses multiple calls to stream_file to accomplish this task.
|
90
|
+
#
|
91
|
+
#Please see stream_file for Returns, as this is a wrapper for that method
|
92
|
+
def background(audio, digits='')
|
93
|
+
result = nil
|
94
|
+
if audio.class == Array then
|
95
|
+
audio.each do |file|
|
96
|
+
begin
|
97
|
+
result = stream_file(file, digits)
|
98
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError, AGIChannelError
|
99
|
+
raise
|
100
|
+
end
|
101
|
+
return result unless result.success?
|
102
|
+
return result if result.data
|
103
|
+
end
|
104
|
+
return result
|
105
|
+
elsif audio.class == String then
|
106
|
+
begin
|
107
|
+
result = stream_file(audio, digits)
|
108
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError, AGIChannelError
|
109
|
+
raise
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
#Is a combination of asterisks background functionality and say_digits. It says the designated digit_string. If digits are provided, allows the user to terminate the audio transmission by supplying DTMF. Allows an optional directory which contains digit-audio. (1.gsm, 2.gsm, ...)
|
115
|
+
#
|
116
|
+
#Please see background for returns, as this is a wrapper for that method
|
117
|
+
def background_digits(digit_string, digits='', path='digits')
|
118
|
+
audio = []
|
119
|
+
digit_string.to_s.scan(/./m) { |digit| audio << "#{path}/#{digit}" }
|
120
|
+
begin
|
121
|
+
response = background(audio, digits)
|
122
|
+
rescue Exception
|
123
|
+
raise
|
124
|
+
end
|
125
|
+
return response
|
126
|
+
end
|
127
|
+
alias_method :background_say_digits, :background_digits
|
128
|
+
|
129
|
+
#Queries Asterisk for the status of the named channel. If no channel is named, defaults to the current channel.
|
130
|
+
#
|
131
|
+
#Returns an AGIResponse object with data signifying the status of the channel:
|
132
|
+
#- 0, 'DOWN, UNAVAILABLE', Channel is down and available
|
133
|
+
#- 1, 'DOWN, RESERVED', Channel is down, but reserved
|
134
|
+
#- 2, 'OFF HOOK', Channel is off hook
|
135
|
+
#- 3, 'DIGITS DIALED', Digits (or equivalent) have been dialed
|
136
|
+
#- 4, 'LINE RINGING', Line is ringing
|
137
|
+
#- 5, 'REMOTE RINGING', Remote end is ringing
|
138
|
+
#- 6, 'UP', Line is up
|
139
|
+
#- 7, 'BUSY', Line is busy
|
140
|
+
def channel_status(channel=nil)
|
141
|
+
response = AGIResponse.new
|
142
|
+
if channel.nil? then
|
143
|
+
command_str = "CHANNEL STATUS"
|
144
|
+
else
|
145
|
+
command_str = "CHANNEL STATUS #{channel}"
|
146
|
+
end
|
147
|
+
begin
|
148
|
+
response.native = execute(command_str)
|
149
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
150
|
+
raise
|
151
|
+
end
|
152
|
+
if response.native == -1 then
|
153
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
154
|
+
else
|
155
|
+
response.success = true
|
156
|
+
end
|
157
|
+
if response.native == 0 then
|
158
|
+
response.data = "DOWN, AVAILABLE"
|
159
|
+
elsif response.native == 1 then
|
160
|
+
response.data = "DOWN, RESERVED"
|
161
|
+
elsif response.native == 2 then
|
162
|
+
response.data = "OFF HOOK"
|
163
|
+
elsif response.native == 3 then
|
164
|
+
response.data = "DIGITS DIALED"
|
165
|
+
elsif response.native == 4 then
|
166
|
+
response.data = "LINE RINGING"
|
167
|
+
elsif response.native == 5 then
|
168
|
+
response.data = "REMOTE RINGING"
|
169
|
+
elsif response.native == 6 then
|
170
|
+
response.data = "UP"
|
171
|
+
elsif response.native == 7 then
|
172
|
+
response.data = "BUSY"
|
173
|
+
end
|
174
|
+
return response
|
175
|
+
end
|
176
|
+
|
177
|
+
#Signals Asterisk to stream the given audio file to the channel starting at an optional offset until either the entire file has been streamed or the user provides one of a set of DTMF digits. Unlike stream_file, this allows the user on the channel to control playback using a fast-forward key, a rewind key, and a pause key.
|
178
|
+
#
|
179
|
+
#Returns an AGIResponse including the DTMF digit provided by the channel.
|
180
|
+
def control_stream_file(file, digits='""', skipms=3000, ffchar='*', rewchar='#', pausechar=nil)
|
181
|
+
response = AGIResponse.new
|
182
|
+
if pausechar.nil?
|
183
|
+
command_str = "CONTROL STREAM FILE file digits skipms ffchar rewchar"
|
184
|
+
else
|
185
|
+
command_str = "CONTROL STREAM FILE file digits skipms ffchar rewchar pausechar"
|
186
|
+
end
|
187
|
+
begin
|
188
|
+
response.native = execute(command_str)
|
189
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
190
|
+
raise
|
191
|
+
end
|
192
|
+
if response.native == -1 then
|
193
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
194
|
+
elsif response.native == 0
|
195
|
+
response.success = true
|
196
|
+
else
|
197
|
+
response.success = true
|
198
|
+
response.data = response.native.chr
|
199
|
+
end
|
200
|
+
return response
|
201
|
+
end
|
202
|
+
|
203
|
+
#Signals Asterisk to delete the appropriate ASTDB database key/value.
|
204
|
+
#
|
205
|
+
#Returns an AGIResponse object
|
206
|
+
def database_del(family, key)
|
207
|
+
response = AGIResponse.new
|
208
|
+
command_str = "DATABASE DEL #{family} #{key}"
|
209
|
+
begin
|
210
|
+
response.native = execute(command_str)
|
211
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
212
|
+
raise
|
213
|
+
end
|
214
|
+
if response.native == -1 then
|
215
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
216
|
+
elsif response.native == 1 then
|
217
|
+
response.success = true
|
218
|
+
end
|
219
|
+
return response
|
220
|
+
end
|
221
|
+
|
222
|
+
#Signals Asterisk to delete the appropriate ASTDB database key/value family.
|
223
|
+
#
|
224
|
+
#Returns an AGIResponse object
|
225
|
+
def database_deltree(family, keytree=nil)
|
226
|
+
response = AGIResponse.new
|
227
|
+
if keytree.nil? then
|
228
|
+
command_str = "DATABASE DELTREE #{family}"
|
229
|
+
else
|
230
|
+
command_str = "DATABASE DELTREE #{family} #{keytree}"
|
231
|
+
end
|
232
|
+
begin
|
233
|
+
response.native = execute(command_str)
|
234
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
235
|
+
raise
|
236
|
+
end
|
237
|
+
if response.native == -1 then
|
238
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
239
|
+
elsif response.native == 1 then
|
240
|
+
response.success = true
|
241
|
+
end
|
242
|
+
return response
|
243
|
+
end
|
244
|
+
|
245
|
+
#Signals Asterisk to return the appropriate ASTDB database key's value
|
246
|
+
#
|
247
|
+
#Returns an AGIResponse object with data including the value of the requested database key
|
248
|
+
def database_get(family, key)
|
249
|
+
response = AGIResponse.new
|
250
|
+
command_str = "DATABASE GET #{family} #{key}"
|
251
|
+
begin
|
252
|
+
response.native = execute(command_str)
|
253
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
254
|
+
raise
|
255
|
+
end
|
256
|
+
if response.native == -1 then
|
257
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
258
|
+
elsif response.native == 1 then
|
259
|
+
response.success = true
|
260
|
+
response.data = parse_response
|
261
|
+
end
|
262
|
+
return response
|
263
|
+
end
|
264
|
+
|
265
|
+
#Signals Asterisk to insert the given value into the ASTDB database
|
266
|
+
#
|
267
|
+
#Returns an AGIResponse
|
268
|
+
def database_put(family, key, value)
|
269
|
+
response = AGIResponse.new
|
270
|
+
command_str = "DATABASE PUT #{family} #{key} #{value}"
|
271
|
+
begin
|
272
|
+
response.native = execute(command_str)
|
273
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
274
|
+
raise
|
275
|
+
end
|
276
|
+
if response.native == -1 then
|
277
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
278
|
+
elsif response.native == 1 then
|
279
|
+
response.success = true
|
280
|
+
end
|
281
|
+
return response
|
282
|
+
end
|
283
|
+
|
284
|
+
#Signals Asterisk to execute the given Asterisk Application by sending the command "EXEC" to Asterisk using the AGI
|
285
|
+
#
|
286
|
+
#Returns an AGIResponse.
|
287
|
+
#
|
288
|
+
#Please note, the success? method to this AGIResponse indicates success of the EXEC command, not the underlying Asterisk Application. If Asterisk provides data in it's standard format, it will be included as data in the AGIResponse object. If it does not, the native asterisk response will be.
|
289
|
+
def exec(string)
|
290
|
+
response = AGIResponse.new
|
291
|
+
command_str = "EXEC #{string}"
|
292
|
+
begin
|
293
|
+
response.native = execute(command_str)
|
294
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
295
|
+
raise
|
296
|
+
end
|
297
|
+
if response.native == -2 then
|
298
|
+
raise AGICommandError.new(@last_response, "Application Not Found in (#{command_str})")
|
299
|
+
elsif response.native == -1 then
|
300
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
301
|
+
else
|
302
|
+
response.success = true
|
303
|
+
response.data = parse_response
|
304
|
+
response.data ||= response.native
|
305
|
+
end
|
306
|
+
return response
|
307
|
+
end
|
308
|
+
|
309
|
+
#Signals Asterisk to collect DTMF digits from the channel while playing an audio file. Optionally accepts a timeout option (in seconds) and a maximum number of digits to collect.
|
310
|
+
#
|
311
|
+
#Returns an AGIResponse with data available which denotes the DTMF digits provided by the channel.
|
312
|
+
def get_data(file, timeout=nil, max_digits=nil)
|
313
|
+
response = AGIResponse.new
|
314
|
+
if timeout.nil? then
|
315
|
+
command_str = "GET DATA #{file}"
|
316
|
+
elsif max_digits.nil? then
|
317
|
+
command_str = "GET DATA #{file} #{(timeout.to_i * 1000)}"
|
318
|
+
else
|
319
|
+
command_str = "GET DATA #{file} #{(timeout.to_i * 1000)} #{max_digits}"
|
320
|
+
end
|
321
|
+
begin
|
322
|
+
response.native = execute(command_str)
|
323
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
324
|
+
raise
|
325
|
+
end
|
326
|
+
if response.native == -1 then
|
327
|
+
raise AGIChannelError.new(@last_response,"Channel Failure in #{command_str}")
|
328
|
+
else
|
329
|
+
response.success = true
|
330
|
+
response.data = response.native
|
331
|
+
end
|
332
|
+
return response
|
333
|
+
end
|
334
|
+
|
335
|
+
#Signals Asterisk to return the contents of the requested (complex) channel variable
|
336
|
+
#
|
337
|
+
#Returns an AGIResponse with the variable's value
|
338
|
+
def get_full_variable(variablename, channel=nil)
|
339
|
+
response = AGIResponse.new
|
340
|
+
if channel.nil?
|
341
|
+
command_str = "GET FULL VARIABLE #{variablename}"
|
342
|
+
else
|
343
|
+
command_str = "GET FULL VARIABLE #{variablename} #{channel}"
|
344
|
+
end
|
345
|
+
begin
|
346
|
+
response.native = execute(command_str)
|
347
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
348
|
+
raise
|
349
|
+
end
|
350
|
+
if response.native == -1 then
|
351
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
352
|
+
elsif response.native == 1
|
353
|
+
response.success = true
|
354
|
+
response.data = parse_response
|
355
|
+
end
|
356
|
+
return response
|
357
|
+
end
|
358
|
+
|
359
|
+
#Signals Asterisk to stream the given audio file to the channel starting at an optional offset until either the entire file has been streamed or the user provides one of a set of DTMF digits. Unlike stream_file, this accepts a timeout option.
|
360
|
+
#
|
361
|
+
#Returns an AGIResponse including the DTMF digit provided by the channel.
|
362
|
+
def get_option(file, digits='""', timeout=0)
|
363
|
+
response = AGIResponse.new
|
364
|
+
command_str = "GET OPTION #{file} #{digits} #{(timeout.to_i * 1000)}"
|
365
|
+
begin
|
366
|
+
response.native = execute(command_str)
|
367
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
368
|
+
raise
|
369
|
+
end
|
370
|
+
if response.native == -1 then
|
371
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
372
|
+
elsif response.native == 0
|
373
|
+
response.success = true
|
374
|
+
else
|
375
|
+
response.success = true
|
376
|
+
response.data = response.native.chr
|
377
|
+
end
|
378
|
+
return response
|
379
|
+
end
|
380
|
+
|
381
|
+
|
382
|
+
#Signals Asterisk to return the contents of the requested channel variable
|
383
|
+
#
|
384
|
+
#Returns an AGIResponse with the variable's value
|
385
|
+
def get_variable(variablename)
|
386
|
+
response = AGIResponse.new
|
387
|
+
command_str = "GET VARIABLE #{variablename}"
|
388
|
+
begin
|
389
|
+
response.native = execute(command_str)
|
390
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
391
|
+
raise
|
392
|
+
end
|
393
|
+
if response.native == -1 then
|
394
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
395
|
+
elsif response.native == 1
|
396
|
+
response.success = true
|
397
|
+
response.data = parse_response
|
398
|
+
end
|
399
|
+
return response
|
400
|
+
end
|
401
|
+
|
402
|
+
#Signals Asterisk to hangup the requested channel. If no channel is provided, defaults to the current channel.
|
403
|
+
#
|
404
|
+
#Returns an AGIResponse.
|
405
|
+
def hangup(channel=nil)
|
406
|
+
response = AGIResponse.new
|
407
|
+
if channel.nil? then
|
408
|
+
command_str = "HANGUP"
|
409
|
+
else
|
410
|
+
command_str = "HANGUP #{channel}"
|
411
|
+
end
|
412
|
+
begin
|
413
|
+
response.native = execute(command_str)
|
414
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
415
|
+
raise
|
416
|
+
end
|
417
|
+
if response.native == -1 then
|
418
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
419
|
+
elsif response.native == 1
|
420
|
+
response.success = true
|
421
|
+
end
|
422
|
+
return response
|
423
|
+
end
|
424
|
+
|
425
|
+
#Initializes the channel by getting all variables provided by Asterisk as it initiates the connection. These values are then stored in the instance variable @channel_params, a Hash object. While initializing the channel, the IO object(s) provided to new as :input and :output are set to operate synchronously.
|
426
|
+
#
|
427
|
+
#Note, a channel can only be initialized once. If the AGI is being initialized a second time, this will throw an AGIInitializeError. If this is desired functionality, please see the reinit method.
|
428
|
+
def init
|
429
|
+
if @initialized
|
430
|
+
raise AGIInitializeError.new(nil, "Tried to init previously initialized channel. If this is desired, use reinit()")
|
431
|
+
end
|
432
|
+
begin
|
433
|
+
@input.sync = true
|
434
|
+
@output.sync = true
|
435
|
+
while( line = @input.gets.chomp )
|
436
|
+
if line =~ %r{^\s*$} then
|
437
|
+
break
|
438
|
+
elsif line =~ %r{^agi_(\w+)\:\s+(.+)$} then
|
439
|
+
if @channel_params.has_key?($1) then
|
440
|
+
@logger.warn{"AGI Got Duplicate Channel Parameter for #{$1} (was \"#{@channel_params[$1]}\" reset to \"#{$2})\""}
|
441
|
+
end
|
442
|
+
@channel_params[$1] = $2
|
443
|
+
@logger.debug{"AGI Got Channel Parameter #{$1} = #{$2}"}
|
444
|
+
end
|
445
|
+
end
|
446
|
+
rescue NoMethodError => error
|
447
|
+
if error.to_s =~ %r{chomp} then
|
448
|
+
raise AGIHangupError.new(nil, "Channel Hungup during init")
|
449
|
+
else
|
450
|
+
raise
|
451
|
+
end
|
452
|
+
end
|
453
|
+
@initialized = true
|
454
|
+
end
|
455
|
+
|
456
|
+
#Signals Asterisk to ... do nothing
|
457
|
+
#
|
458
|
+
#Returns an AGIResponse. (just in case you want to make sure asterisk successfully didnt do anything. Don't ask me, I just implement them)
|
459
|
+
def noop(string=nil)
|
460
|
+
response = AGIResponse.new
|
461
|
+
if string.nil? then
|
462
|
+
command_str = "NOOP"
|
463
|
+
else
|
464
|
+
command_str = "NOOP #{string}"
|
465
|
+
end
|
466
|
+
begin
|
467
|
+
response.native = execute(command_str)
|
468
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
469
|
+
raise
|
470
|
+
end
|
471
|
+
if response.native == -1 then
|
472
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
473
|
+
else
|
474
|
+
response.success = true
|
475
|
+
end
|
476
|
+
return response
|
477
|
+
end
|
478
|
+
|
479
|
+
#Signals Asterisk to query the channel to request a single text character from the channel
|
480
|
+
#
|
481
|
+
#Returns an AGIResponse including the character received.
|
482
|
+
def receive_char(timeout=0)
|
483
|
+
response = AGIResponse.new
|
484
|
+
command_str = "RECEIVE CHAR #{(timeout.to_i * 1000)}"
|
485
|
+
begin
|
486
|
+
response.native = execute(command_str)
|
487
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
488
|
+
raise
|
489
|
+
end
|
490
|
+
if response.native == -1 then
|
491
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
492
|
+
elsif response.native == 0
|
493
|
+
raise AGICommandError.new(@last_response, "Channel doesn't support TEXT in (#{command_str})")
|
494
|
+
else
|
495
|
+
response.success = true
|
496
|
+
response.data = response.native.chr
|
497
|
+
end
|
498
|
+
return response
|
499
|
+
end
|
500
|
+
|
501
|
+
#Signals Asterisk to query the channel to provide audio data to record into a file. Asterisk will record until certain digits are provided as DTMF, or the operation times out, or silence is detected for a second timeout. Can optionally cause asterisk to send a beep to the channel to signal the user the intention of recording sound. By default, there is no timeout,no silence detection, and no beep.
|
502
|
+
#
|
503
|
+
#Returns an AGIResponse.
|
504
|
+
def record_file(filename, format, digits, timeout=-1, beep=false, silence=nil)
|
505
|
+
beep_str = ''
|
506
|
+
if ( beep == true ) then
|
507
|
+
beep_str = "BEEP"
|
508
|
+
end
|
509
|
+
silence_str = ''
|
510
|
+
unless silence.nil?
|
511
|
+
silence_str = "s=#{silence}"
|
512
|
+
end
|
513
|
+
response = AGIResponse.new
|
514
|
+
command_str = "RECORD FILE #{filename} #{format} #{digits} #{(timeout.to_i * 1000)} #{beep_str} #{silence_str}"
|
515
|
+
begin
|
516
|
+
response.native = execute(command_str)
|
517
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
518
|
+
raise
|
519
|
+
end
|
520
|
+
if response.native == -1 then
|
521
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
522
|
+
elsif response.native > 0
|
523
|
+
response.success = true
|
524
|
+
response.data = response.native.chr
|
525
|
+
else
|
526
|
+
response.success = true
|
527
|
+
end
|
528
|
+
return response
|
529
|
+
end
|
530
|
+
|
531
|
+
|
532
|
+
#Initializes the channel by getting all variables provided by Asterisk as it initiates the connection. These values are then stored in the instance variable @channel_params, a Hash object. While initializing the channel, the IO object(s) provided to new as :input and :output are set to operate synchronously.
|
533
|
+
#
|
534
|
+
#Note, unlike the init method, this can be called on an AGI object multiple times. Realize, however, that each time you do, the channel will have to provide a set of initialization data, and all previously stored channel parameters will be forgotten.
|
535
|
+
def reinit
|
536
|
+
@initialized = false
|
537
|
+
@channel_params = {}
|
538
|
+
@last_response = nil
|
539
|
+
init
|
540
|
+
end
|
541
|
+
|
542
|
+
#Signals Asterisk to announce the string provided as a series of characters If digits are provided as well, will allow the user to terminate the announcement if one of the digits are provided by DTMF.
|
543
|
+
#
|
544
|
+
#Returns an AGIResponse including the DTMF digit provided by the channel.
|
545
|
+
def say_alpha(string, digits='""')
|
546
|
+
response = AGIResponse.new
|
547
|
+
command_str = "SAY ALPHA '#{string}' #{digits}"
|
548
|
+
begin
|
549
|
+
response.native = execute(command_str)
|
550
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
551
|
+
raise
|
552
|
+
end
|
553
|
+
if response.native == -1 then
|
554
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
555
|
+
elsif response.native == 0
|
556
|
+
response.success = true
|
557
|
+
else
|
558
|
+
response.success = true
|
559
|
+
response.data = response.native.chr
|
560
|
+
end
|
561
|
+
return response
|
562
|
+
end
|
563
|
+
|
564
|
+
#Signals Asterisk to announce the given date. If digits are provided as well, will allow the user to terminate the announcement if one of the digits are provided by DTMF. Can accept either a Time object or an integer designation of the number of seconds since 00:00:00 January 1, 1970, Coordinated Universal Time (UTC). Defaults to now.
|
565
|
+
#
|
566
|
+
#Returns an AGIResponse including the DTMF digit provided by the channel, if any are.
|
567
|
+
def say_date(time=Time.now, digits='""')
|
568
|
+
response = AGIResponse.new
|
569
|
+
command_str = "SAY DATE #{time.to_i} #{digits}"
|
570
|
+
begin
|
571
|
+
response.native = execute(command_str)
|
572
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
573
|
+
raise
|
574
|
+
end
|
575
|
+
if response.native == -1 then
|
576
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
577
|
+
elsif response.native == 0
|
578
|
+
response.success = true
|
579
|
+
else
|
580
|
+
response.success = true
|
581
|
+
response.data = response.native.chr
|
582
|
+
end
|
583
|
+
return response
|
584
|
+
end
|
585
|
+
|
586
|
+
#Signals Asterisk to announce the given date and time. If digits are provided as well, will allow the user to terminate the announcement if one of the digits are provided by DTMF. Can accept either a Time object or an integer designation of the number of seconds since 00:00:00 January 1, 1970, Coordinated Universal Time (UTC). Defaults to now.
|
587
|
+
#
|
588
|
+
#Returns an AGIResponse including the DTMF digit provided by the channel, if any are.
|
589
|
+
def say_datetime(time=Time.now, digits='""', format="ABdY", timezone=nil)
|
590
|
+
response = AGIResponse.new
|
591
|
+
if timezone.nil?
|
592
|
+
command_str = "SAY DATETIME #{time.to_i} #{digits} #{format}"
|
593
|
+
else
|
594
|
+
command_str = "SAY DATETIME #{time.to_i} #{digits} #{format} #{timezone}"
|
595
|
+
end
|
596
|
+
begin
|
597
|
+
response.native = execute(command_str)
|
598
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
599
|
+
raise
|
600
|
+
end
|
601
|
+
if response.native == -1 then
|
602
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
603
|
+
elsif response.native == 0
|
604
|
+
response.success = true
|
605
|
+
else
|
606
|
+
response.success = true
|
607
|
+
response.data = response.native.chr
|
608
|
+
end
|
609
|
+
return response
|
610
|
+
end
|
611
|
+
|
612
|
+
#Signals Asterisk to announce the number provided as a series of digits. If digits are provided as well, will allow the user to terminate the announcement if one of the digits are provided by DTMF.
|
613
|
+
#
|
614
|
+
#Returns an AGIResponse including the DTMF digit provided by the channel.
|
615
|
+
def say_digits(number, digits='""')
|
616
|
+
response = AGIResponse.new
|
617
|
+
command_str = "SAY DIGITS #{number} #{digits}"
|
618
|
+
begin
|
619
|
+
response.native = execute(command_str)
|
620
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
621
|
+
raise
|
622
|
+
end
|
623
|
+
if response.native == -1 then
|
624
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
625
|
+
elsif response.native == 0
|
626
|
+
response.success = true
|
627
|
+
else
|
628
|
+
response.success = true
|
629
|
+
response.data = response.native.chr
|
630
|
+
end
|
631
|
+
return response
|
632
|
+
end
|
633
|
+
|
634
|
+
#Signals Asterisk to announce the number provided as a single number. If digits are provided as well, will allow the user to terminate the announcement if one of the digits are provided by DTMF.
|
635
|
+
#
|
636
|
+
#Returns an AGIResponse including the DTMF digit provided by the channel.
|
637
|
+
def say_number(number, digits='""')
|
638
|
+
response = AGIResponse.new
|
639
|
+
command_str = "SAY NUMBER #{number} #{digits}"
|
640
|
+
begin
|
641
|
+
response.native = execute(command_str)
|
642
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
643
|
+
raise
|
644
|
+
end
|
645
|
+
if response.native == -1 then
|
646
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
647
|
+
elsif response.native == 0
|
648
|
+
response.success = true
|
649
|
+
else
|
650
|
+
response.success = true
|
651
|
+
response.data = response.native.chr
|
652
|
+
end
|
653
|
+
return response
|
654
|
+
end
|
655
|
+
|
656
|
+
#Signals Asterisk to announce the string provided as a series of characters. If digits are provided as well, will allow the user to terminate the announcement if one of the digits are provided by DTMF.
|
657
|
+
#
|
658
|
+
#Returns an AGIResponse including the DTMF digit provided by the channel.
|
659
|
+
def say_phonetic(string, digits='""')
|
660
|
+
response = AGIResponse.new
|
661
|
+
command_str = "SAY PHONETIC '#{string}' #{digits}"
|
662
|
+
begin
|
663
|
+
response.native = execute(command_str)
|
664
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
665
|
+
raise
|
666
|
+
end
|
667
|
+
if response.native == -1 then
|
668
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
669
|
+
elsif response.native == 0
|
670
|
+
response.success = true
|
671
|
+
else
|
672
|
+
response.success = true
|
673
|
+
response.data = response.native.chr
|
674
|
+
end
|
675
|
+
return response
|
676
|
+
end
|
677
|
+
|
678
|
+
#Signals Asterisk to announce the given time. If digits are provided as well, will allow the user to terminate the announcement if one of the digits are provided by DTMF. Can accept either a Time object or an integer designation of the number of seconds since 00:00:00 January 1, 1970, Coordinated Universal Time (UTC). Defaults to now.
|
679
|
+
#
|
680
|
+
#Returns an AGIResponse including the DTMF digit provided by the channel, if any are.
|
681
|
+
def say_time(time=Time.now, digits='""')
|
682
|
+
response = AGIResponse.new
|
683
|
+
command_str = "SAY TIME #{time.to_i} #{digits}"
|
684
|
+
begin
|
685
|
+
response.native = execute(command_str)
|
686
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
687
|
+
raise
|
688
|
+
end
|
689
|
+
if response.native == -1 then
|
690
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
691
|
+
elsif response.native == 0
|
692
|
+
response.success = true
|
693
|
+
else
|
694
|
+
response.success = true
|
695
|
+
response.data = response.native.chr
|
696
|
+
end
|
697
|
+
return response
|
698
|
+
end
|
699
|
+
|
700
|
+
#Signals Asterisk to transfer an image across the channel.
|
701
|
+
#
|
702
|
+
#Returns an AGIResponse.
|
703
|
+
#
|
704
|
+
#Please note, at present Asterisk returns the same value to the AGI if the image is sent and if the channel does not support image transmission. The AGIResponse, therefore, reflects the same. AGIResponse.success? will be true for both successful transmission and for channel-doesn't support.
|
705
|
+
def send_image(image)
|
706
|
+
response = AGIResponse.new
|
707
|
+
command_str = "SEND IMAGE #{image}"
|
708
|
+
begin
|
709
|
+
response.native = execute(command_str)
|
710
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
711
|
+
raise
|
712
|
+
end
|
713
|
+
if response.native == -1 then
|
714
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
715
|
+
elsif response.native == 0
|
716
|
+
response.success = true
|
717
|
+
end
|
718
|
+
return response
|
719
|
+
end
|
720
|
+
|
721
|
+
#Signals Asterisk to transfer text across the channel.
|
722
|
+
#
|
723
|
+
#Returns an AGIResponse.
|
724
|
+
#
|
725
|
+
#Please note, at present Asterisk returns the same value to the AGI if the text is sent and if the channel does not support text transmission. The AGIResponse, therefore, reflects the same. AGIResponse.success? will be true for both successful transmission and for channel-doesn't support.
|
726
|
+
def send_text(text)
|
727
|
+
response = AGIResponse.new
|
728
|
+
command_str = "SEND TEXT '#{text}'"
|
729
|
+
begin
|
730
|
+
response.native = execute(command_str)
|
731
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
732
|
+
raise
|
733
|
+
end
|
734
|
+
if response.native == -1 then
|
735
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
736
|
+
elsif response.native == 0
|
737
|
+
response.success = true
|
738
|
+
end
|
739
|
+
return response
|
740
|
+
end
|
741
|
+
|
742
|
+
#Signals Asterisk to hangup the channel after a given amount of time has elapsed.
|
743
|
+
#
|
744
|
+
#Returns an AGIResponse.
|
745
|
+
def set_autohangup(time)
|
746
|
+
response = AGIResponse.new
|
747
|
+
command_str = "SET AUTOHANGUP #{time}"
|
748
|
+
begin
|
749
|
+
response.native = execute(command_str)
|
750
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
751
|
+
raise
|
752
|
+
end
|
753
|
+
if response.native == -1 then
|
754
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
755
|
+
elsif response.native == 0
|
756
|
+
response.success = true
|
757
|
+
end
|
758
|
+
return response
|
759
|
+
end
|
760
|
+
|
761
|
+
#Signals Asterisk to set the callerid on the channel.
|
762
|
+
#
|
763
|
+
#Returns an AGIResponse.
|
764
|
+
def set_callerid(callerid)
|
765
|
+
response = AGIResponse.new
|
766
|
+
command_str = "SET CALLERID #{callerid}"
|
767
|
+
begin
|
768
|
+
response.native = execute(command_str)
|
769
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
770
|
+
raise
|
771
|
+
end
|
772
|
+
if response.native == -1 then
|
773
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
774
|
+
elsif response.native == 1
|
775
|
+
response.success = true
|
776
|
+
end
|
777
|
+
return response
|
778
|
+
end
|
779
|
+
|
780
|
+
#Signals Asterisk to set the context for the channel upon AGI completion.
|
781
|
+
#
|
782
|
+
#Returns an AGIResponse.
|
783
|
+
def set_context(context)
|
784
|
+
response = AGIResponse.new
|
785
|
+
command_str = "SET CONTEXT #{context}"
|
786
|
+
begin
|
787
|
+
response.native = execute(command_str)
|
788
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
789
|
+
raise
|
790
|
+
end
|
791
|
+
if response.native == -1 then
|
792
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
793
|
+
elsif response.native == 0
|
794
|
+
response.success = true
|
795
|
+
end
|
796
|
+
return response
|
797
|
+
end
|
798
|
+
|
799
|
+
#Signals Asterisk to set the extension for the channel upon AGI completion.
|
800
|
+
#
|
801
|
+
#Returns an AGIResponse.
|
802
|
+
def set_extension(extension)
|
803
|
+
response = AGIResponse.new
|
804
|
+
command_str = "SET EXTENSION #{extension}"
|
805
|
+
begin
|
806
|
+
response.native = execute(command_str)
|
807
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
808
|
+
raise
|
809
|
+
end
|
810
|
+
if response.native == -1 then
|
811
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
812
|
+
elsif response.native == 0
|
813
|
+
response.success = true
|
814
|
+
end
|
815
|
+
return response
|
816
|
+
end
|
817
|
+
|
818
|
+
#Signals Asterisk to enable or disable music-on-hold for the channel. If music_class is provided, it will select music from the apropriate music class, if it is not provided, asterisk will use music from the default class. The toggle option can either be 'on' or 'off'.
|
819
|
+
#
|
820
|
+
#Returns an AGIResponse.
|
821
|
+
def set_music(toggle, music_class=nil)
|
822
|
+
unless ( toggle == 'on' || toggle == 'off')
|
823
|
+
raise ArgumentError, "Argument 1 must be 'on' or 'off' to set music"
|
824
|
+
end
|
825
|
+
response = AGIResponse.new
|
826
|
+
if music_class.nil? then
|
827
|
+
command_str = "SET MUSIC #{toggle}"
|
828
|
+
else
|
829
|
+
command_str = "SET MUSIC #{toggle} #{music_class}"
|
830
|
+
end
|
831
|
+
begin
|
832
|
+
response.native = execute(command_str)
|
833
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
834
|
+
raise
|
835
|
+
end
|
836
|
+
if response.native == -1 then
|
837
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
838
|
+
elsif response.native == 0
|
839
|
+
response.success = true
|
840
|
+
end
|
841
|
+
return response
|
842
|
+
end
|
843
|
+
|
844
|
+
#Signals Asterisk to set the priority for the channel upon AGI completion.
|
845
|
+
#
|
846
|
+
#Returns an AGIResponse.
|
847
|
+
def set_priority(priority)
|
848
|
+
response = AGIResponse.new
|
849
|
+
command_str = "SET PRIORITY #{priority}"
|
850
|
+
begin
|
851
|
+
response.native = execute(command_str)
|
852
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
853
|
+
raise
|
854
|
+
end
|
855
|
+
if response.native == -1 then
|
856
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
857
|
+
elsif response.native == 0
|
858
|
+
response.success = true
|
859
|
+
end
|
860
|
+
return response
|
861
|
+
end
|
862
|
+
|
863
|
+
#Signals Asterisk to set the contents of the requested channel variable.
|
864
|
+
#
|
865
|
+
#Returns an AGIResponse
|
866
|
+
def set_variable(variable, value)
|
867
|
+
response = AGIResponse.new
|
868
|
+
command_str = "SET VARIABLE #{variable} #{value}"
|
869
|
+
begin
|
870
|
+
response.native = execute(command_str)
|
871
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
872
|
+
raise
|
873
|
+
end
|
874
|
+
if response.native == -1 then
|
875
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
876
|
+
elsif response.native == 1
|
877
|
+
response.success = true
|
878
|
+
end
|
879
|
+
return response
|
880
|
+
end
|
881
|
+
|
882
|
+
#Signals Asterisk to stream the given audio file to the channel starting at an optional offset until either the entire file has been streamed or the user provides one of a set of DTMF digits.
|
883
|
+
#
|
884
|
+
#Returns an AGIResponse including the DTMF digit provided by the channel.
|
885
|
+
def stream_file(file, digits='', offset=nil)
|
886
|
+
digits.gsub!(/['"]/, '')
|
887
|
+
response = AGIResponse.new
|
888
|
+
if offset.nil? then
|
889
|
+
command_str = "STREAM FILE #{file} '#{digits}'"
|
890
|
+
else
|
891
|
+
command_Str = "STREAM FILE #{file} ''#{digits}' #{offset}"
|
892
|
+
end
|
893
|
+
begin
|
894
|
+
response.native = execute(command_str)
|
895
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
896
|
+
raise
|
897
|
+
end
|
898
|
+
if response.native == -1 then
|
899
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
900
|
+
elsif response.native == 0
|
901
|
+
response.success = true
|
902
|
+
else
|
903
|
+
response.success = true
|
904
|
+
response.data = response.native.chr
|
905
|
+
end
|
906
|
+
return response
|
907
|
+
end
|
908
|
+
|
909
|
+
#Signals Asterisk to enable or disable tdd mode for the channel
|
910
|
+
#
|
911
|
+
#Returns an AGIResponse.
|
912
|
+
def tdd_mode(toggle)
|
913
|
+
unless ( toggle == 'on' || toggle == 'off')
|
914
|
+
raise ArgumentError, "Argument 1 must be 'on' or 'off' to set tdd mode"
|
915
|
+
end
|
916
|
+
command_str = "TDD MODE #{toggle}"
|
917
|
+
response = AGIResponse.new
|
918
|
+
begin
|
919
|
+
response.native = execute(command_str)
|
920
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
921
|
+
raise
|
922
|
+
end
|
923
|
+
if response.native == -1 then
|
924
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
925
|
+
elsif response.native == 0
|
926
|
+
raise AGIChannelError.new(@last_response, "Channel is not TDD-Capable")
|
927
|
+
elsif response.native == 1
|
928
|
+
response.success = true
|
929
|
+
end
|
930
|
+
return response
|
931
|
+
end
|
932
|
+
|
933
|
+
#Signals Asterisk to log the given message using the given log level to asterisk's verbose log.
|
934
|
+
#
|
935
|
+
#Returns an AGIResponse.
|
936
|
+
def verbose(message, level)
|
937
|
+
response = AGIResponse.new
|
938
|
+
command_str = "VERBOSE #{message} #{level}"
|
939
|
+
begin
|
940
|
+
response.native = execute(command_str)
|
941
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
942
|
+
raise
|
943
|
+
end
|
944
|
+
if response.native == -1 then
|
945
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
946
|
+
elsif response.native == 1
|
947
|
+
response.success = true
|
948
|
+
end
|
949
|
+
return response
|
950
|
+
end
|
951
|
+
|
952
|
+
#Signals Asterisk to collect a single DTMF digit from the channel while waiting for an optional timeout.
|
953
|
+
#
|
954
|
+
#Returns an AGIResponse with data available which denotes the DTMF digits provided by the channel.
|
955
|
+
def wait_for_digit(timeout=-1)
|
956
|
+
response = AGIResponse.new
|
957
|
+
command_str = "WAIT FOR DIGIT #{timeout}"
|
958
|
+
begin
|
959
|
+
response.native = execute(command_str)
|
960
|
+
rescue AGITimeoutError, AGICommandError, AGIHangupError
|
961
|
+
raise
|
962
|
+
end
|
963
|
+
if response.native == -1 then
|
964
|
+
raise AGIChannelError.new(@last_response, "Channel Failure in (#{command_str})")
|
965
|
+
elsif response.native == 0
|
966
|
+
response.success = true
|
967
|
+
else
|
968
|
+
response.success = true
|
969
|
+
response.data = response.native.chr
|
970
|
+
end
|
971
|
+
return response
|
972
|
+
end
|
973
|
+
|
974
|
+
private
|
975
|
+
def execute(command_str)
|
976
|
+
@last_response = nil
|
977
|
+
_execcommand(command_str)
|
978
|
+
begin
|
979
|
+
result = _readresponse()
|
980
|
+
response = _checkresult(result)
|
981
|
+
rescue AGIHangupError => error
|
982
|
+
@logger.warn{"Received AGI Hangup Error in command (#{command_str}): #{error.to_s}"}
|
983
|
+
raise
|
984
|
+
rescue AGICommandError => error
|
985
|
+
@logger.warn{"Received AGI Command Error in command (#{command_str}): #{error.to_s}"}
|
986
|
+
raise
|
987
|
+
rescue AGITimeoutError => error
|
988
|
+
@logger.warn{"Received AGI Timeout Error in command (#{command_str}): #{error.to_s}"}
|
989
|
+
raise
|
990
|
+
else
|
991
|
+
return response
|
992
|
+
end
|
993
|
+
end
|
994
|
+
|
995
|
+
def _execcommand(command_str)
|
996
|
+
# returns nothing, merely sends a command string to asterisk
|
997
|
+
@logger.debug{"AGI Sent to Asterisk: #{command_str}"}
|
998
|
+
@output.print("#{command_str}\n")
|
999
|
+
return nil
|
1000
|
+
end
|
1001
|
+
|
1002
|
+
def _readresponse
|
1003
|
+
# returns the data returned by asterisk
|
1004
|
+
begin
|
1005
|
+
response = @input.gets.chomp
|
1006
|
+
rescue NoMethodError
|
1007
|
+
# NoMethodError here implies chomp called on nil result of gets,
|
1008
|
+
# reraise as Hangup
|
1009
|
+
raise AGIHangupError.new(nil, "Channel Hungup during command execution")
|
1010
|
+
rescue Errno::ECONNRESET
|
1011
|
+
raise AGIHangupError.new(nil, "Channel Hungup during command execution")
|
1012
|
+
else
|
1013
|
+
@logger.debug{"AGI Received from Asterisk: #{response}"}
|
1014
|
+
end
|
1015
|
+
return response
|
1016
|
+
end
|
1017
|
+
|
1018
|
+
def _checkresult(response)
|
1019
|
+
# returns what will be interpreted as asterisks native response
|
1020
|
+
if response.nil? then
|
1021
|
+
return false
|
1022
|
+
end
|
1023
|
+
@last_response = response.chomp
|
1024
|
+
if ( response =~ %r{^200} && response =~ %r{result=(0[\d*#]+)}i ) then
|
1025
|
+
return $1.to_s
|
1026
|
+
elsif ( response =~ %r{^200} && response =~ %r{result=(-?[\d*#]+)}i ) then
|
1027
|
+
return $1.to_i
|
1028
|
+
elsif ( response =~ %r{^200} && response =~ %r{\(timeout\)} ) then
|
1029
|
+
raise AGITimeoutError.new(@last_response, "Timed out waiting for response from User")
|
1030
|
+
else
|
1031
|
+
raise AGICommandError.new(@last_response, "Invalid or nil response from Asterisk: \"#{response}\"")
|
1032
|
+
end
|
1033
|
+
end
|
1034
|
+
|
1035
|
+
def parse_response
|
1036
|
+
if @last_response =~ %r{\((.*)\)} then
|
1037
|
+
return $1
|
1038
|
+
else
|
1039
|
+
return nil
|
1040
|
+
end
|
1041
|
+
end
|
1042
|
+
end
|
1043
|
+
|
1044
|
+
=begin
|
1045
|
+
Copyright (c) 2007, Vonage Holdings
|
1046
|
+
|
1047
|
+
All rights reserved.
|
1048
|
+
|
1049
|
+
Redistribution and use in source and binary forms, with or without
|
1050
|
+
modification, are permitted provided that the following conditions are met:
|
1051
|
+
|
1052
|
+
* Redistributions of source code must retain the above copyright
|
1053
|
+
notice, this list of conditions and the following disclaimer.
|
1054
|
+
* Redistributions in binary form must reproduce the above copyright
|
1055
|
+
notice, this list of conditions and the following disclaimer in the
|
1056
|
+
documentation and/or other materials provided with the distribution.
|
1057
|
+
* Neither the name of Vonage Holdings nor the names of its
|
1058
|
+
contributors may be used to endorse or promote products derived from this
|
1059
|
+
software without specific prior written permission.
|
1060
|
+
|
1061
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
1062
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
1063
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
1064
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
1065
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
1066
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
1067
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
1068
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
1069
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
1070
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
1071
|
+
POSSIBILITY OF SUCH DAMAGE.
|
1072
|
+
=end
|