ragi 1.0.0
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/CHANGELOG +12 -0
- data/RAGI Overview_files/filelist.xml +5 -0
- data/RAGI Overview_files/image001.gif +0 -0
- data/README +114 -0
- data/call_connection.rb +515 -0
- data/call_handler.rb +362 -0
- data/call_initiate.rb +188 -0
- data/call_server.rb +147 -0
- data/config.rb +68 -0
- data/example-call-audio.mp3 +0 -0
- data/ragi.gempsec +23 -0
- data/sample-apps/simon/simon_handler.rb +94 -0
- data/sample-apps/simon/sounds/README +9 -0
- data/sample-apps/simon/sounds/simon-1.gsm +0 -0
- data/sample-apps/simon/sounds/simon-2.gsm +0 -0
- data/sample-apps/simon/sounds/simon-3.gsm +0 -0
- data/sample-apps/simon/sounds/simon-4.gsm +0 -0
- data/sample-apps/simon/sounds/simon-5.gsm +0 -0
- data/sample-apps/simon/sounds/simon-6.gsm +0 -0
- data/sample-apps/simon/sounds/simon-7.gsm +0 -0
- data/sample-apps/simon/sounds/simon-8.gsm +0 -0
- data/sample-apps/simon/sounds/simon-9.gsm +0 -0
- data/sample-apps/simon/sounds/simon-again.gsm +0 -0
- data/sample-apps/simon/sounds/simon-beep-again.gsm +0 -0
- data/sample-apps/simon/sounds/simon-beep-beep.gsm +0 -0
- data/sample-apps/simon/sounds/simon-beep-gameover.gsm +0 -0
- data/sample-apps/simon/sounds/simon-beep-high.gsm +0 -0
- data/sample-apps/simon/sounds/simon-beep-low.gsm +0 -0
- data/sample-apps/simon/sounds/simon-beep-medium.gsm +0 -0
- data/sample-apps/simon/sounds/simon-beep-score.gsm +0 -0
- data/sample-apps/simon/sounds/simon-beep-welcome.gsm +0 -0
- data/sample-apps/simon/sounds/simon-beep.gsm +0 -0
- data/sample-apps/simon/sounds/simon-gameover.gsm +0 -0
- data/sample-apps/simon/sounds/simon-goodbye.gsm +0 -0
- data/sample-apps/simon/sounds/simon-high.gsm +0 -0
- data/sample-apps/simon/sounds/simon-low.gsm +0 -0
- data/sample-apps/simon/sounds/simon-medium.gsm +0 -0
- data/sample-apps/simon/sounds/simon-score.gsm +0 -0
- data/sample-apps/simon/sounds/simon-welcome.gsm +0 -0
- data/start_ragi.rb +41 -0
- data/test.rb +109 -0
- metadata +77 -0
data/call_handler.rb
ADDED
@@ -0,0 +1,362 @@
|
|
1
|
+
#
|
2
|
+
# RAGI - Ruby classes for implementing an AGI server for Asterisk
|
3
|
+
# The BSD License for RAGI follows.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2005, SNAPVINE LLC (www.snapvine.com)
|
6
|
+
# All rights reserved.
|
7
|
+
|
8
|
+
# Redistribution and use in source and binary forms, with or without
|
9
|
+
# modification, are permitted provided that the following conditions are met:
|
10
|
+
#
|
11
|
+
# * Redistributions of source code must retain the above copyright notice,
|
12
|
+
# this list of conditions and the following disclaimer.
|
13
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
14
|
+
# this list of conditions and the following disclaimer in the
|
15
|
+
# documentation and/or other materials provided with the distribution.
|
16
|
+
# * Neither the name of SNAPVINE nor the names of its contributors
|
17
|
+
# may be used to endorse or promote products derived from this software
|
18
|
+
# without specific prior written permission.
|
19
|
+
#
|
20
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
21
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
22
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
23
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
24
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
25
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
26
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
27
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
28
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
29
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
30
|
+
|
31
|
+
require 'uri'
|
32
|
+
require 'ragi/call_connection'
|
33
|
+
|
34
|
+
module RAGI
|
35
|
+
|
36
|
+
# == Overview
|
37
|
+
#
|
38
|
+
# This class is the base class for all RAGI call handler objects. To create
|
39
|
+
# a call handler, derive a class from this class with the suffix "Handler"
|
40
|
+
# and place it in the file app/handlers/<name>_handler.rb.
|
41
|
+
#
|
42
|
+
# == Actions
|
43
|
+
#
|
44
|
+
# All public
|
45
|
+
# methods on the class are by default exposed as actions to the callserver.
|
46
|
+
# An action can be called by passing a URL like
|
47
|
+
#
|
48
|
+
# agi://<server>/<handler>/<action>
|
49
|
+
#
|
50
|
+
# to asterisk. If the action is not specified, then the call server will
|
51
|
+
# choose one of the two following default actions:
|
52
|
+
#
|
53
|
+
# dialout - for connections initiated by the call server
|
54
|
+
# dialup - for connections initiated from outside
|
55
|
+
#
|
56
|
+
# == Helpers
|
57
|
+
#
|
58
|
+
# Helpers are modules that can be loaded as mixins into the call handlers.
|
59
|
+
# As mixins the implementation of these modules will have access to all the
|
60
|
+
# state of the call handler including the session, params, connection, etc.
|
61
|
+
#
|
62
|
+
# To include a helper, declare it with a line like this in your call handler:
|
63
|
+
#
|
64
|
+
# helper :FooBar
|
65
|
+
#
|
66
|
+
# The call handler will mixin a module named FooBarHelper, loading the file
|
67
|
+
# foo_bar_helper.rb if necessary. The standard location for such helper
|
68
|
+
# files is app/helper.
|
69
|
+
#
|
70
|
+
# == Parameters
|
71
|
+
#
|
72
|
+
# There are a variety of ways that parameters can be passed to a call
|
73
|
+
# handler. Each of these variables is available through the params hash.
|
74
|
+
# To retrieve a value from the params hash:
|
75
|
+
#
|
76
|
+
# @params['Foo']
|
77
|
+
#
|
78
|
+
# For a call coming in over RAGI you can retrieve AGI session variables.
|
79
|
+
# These are the variables passed to teh connection when it is established.
|
80
|
+
# For example, to reference the caller ID:
|
81
|
+
#
|
82
|
+
# @params[RAGI::CALLERID]
|
83
|
+
#
|
84
|
+
# You can also retrieve query parameters from the AGI URL. For instance, if
|
85
|
+
# the AGI URL is something like /foo/bar?widget=gizbot, you can retrieve the
|
86
|
+
# widget value:
|
87
|
+
#
|
88
|
+
# if @params[:widget] == 'gizbot' then
|
89
|
+
#
|
90
|
+
# If the call was initiated through RAGI::place_call, you can access the
|
91
|
+
# parameters passed to the initiate call. For instance, if you initiate call
|
92
|
+
# looks like this:
|
93
|
+
#
|
94
|
+
# RAGI.place_call('2065551212', '/foo/bar',
|
95
|
+
# :hash_params => { :param => 123,
|
96
|
+
# "foo" => "bar" })
|
97
|
+
#
|
98
|
+
# You can reference these parameters like this:
|
99
|
+
#
|
100
|
+
# if @params[:param] == 123 && @params["foo"] == "bar" then
|
101
|
+
#
|
102
|
+
# Finally, when you redirect to another handler or process a subhandler, all
|
103
|
+
# the parameters for your call handler are passed along with any additional
|
104
|
+
# parameters you can specify. For instance, this redirect call:
|
105
|
+
#
|
106
|
+
# redirect(:handler => :foo_bar,
|
107
|
+
# :param => 123,
|
108
|
+
# "foo" => "bar")
|
109
|
+
#
|
110
|
+
# You can reference these parameters like this:
|
111
|
+
#
|
112
|
+
# if @params[:param] == 123 && @params["foo"] == "bar" then
|
113
|
+
#
|
114
|
+
# == Sessions
|
115
|
+
#
|
116
|
+
# A session object is created for each RAGI connection. The session object
|
117
|
+
# is simply a has object that allows you to store objects in memory during
|
118
|
+
# the call. Unlike the web, session objects are not maintained across
|
119
|
+
# connection. You can place objects in the functions using the hash object:
|
120
|
+
#
|
121
|
+
# @session[:foo] = :bar
|
122
|
+
#
|
123
|
+
# Later you can retrieve the contents using the hash object:
|
124
|
+
#
|
125
|
+
# if(@session[:foo] == :bar) then
|
126
|
+
#
|
127
|
+
# == Redirection
|
128
|
+
#
|
129
|
+
# A call handler can redirect to another call handler. When you redirect,
|
130
|
+
# controll of the call is passed to the other call handler when you exit
|
131
|
+
# your call handler. The session, parameters and connection objects remain
|
132
|
+
# the same. You can specify handlers & actions for the redirection. If the
|
133
|
+
# handler is not specified then the existing handler is reused. In addition,
|
134
|
+
# you can specify additional parameters to be added for the redirected call
|
135
|
+
# handler.
|
136
|
+
#
|
137
|
+
# redirect(:handler => :foo,
|
138
|
+
# :action => :bar,
|
139
|
+
# :param => 123,
|
140
|
+
# "foo" => "bar")
|
141
|
+
#
|
142
|
+
# Handlers & actions can be referenced by symbol or string. Handlers can
|
143
|
+
# also reference an explicit class or a class instance.
|
144
|
+
|
145
|
+
class CallHandler
|
146
|
+
attr_accessor :connection, :session, :redirect_route
|
147
|
+
|
148
|
+
#-----------------------------------------------------------------------------
|
149
|
+
# PUBLIC INSTANCE METHODS
|
150
|
+
#-----------------------------------------------------------------------------
|
151
|
+
|
152
|
+
# call this method to redirect to another handler. after calling this
|
153
|
+
# method you should return to the caller. You will not receive
|
154
|
+
# control of the call back when this handler is complete.
|
155
|
+
|
156
|
+
def redirect(options)
|
157
|
+
@redirect_route = @route.dup.update(options)
|
158
|
+
end
|
159
|
+
|
160
|
+
# call this method to call another handler from within your handler. when
|
161
|
+
# the other handler has finished control will be returned to the caller.
|
162
|
+
|
163
|
+
def process(options)
|
164
|
+
route = @route.dup.update(options)
|
165
|
+
|
166
|
+
if CallHandler.match_class(self.class, route[:handler]) then
|
167
|
+
RAGI::LOGGER.warn("Warning, creating new instance of handler #{route[:handler]}.")
|
168
|
+
end
|
169
|
+
|
170
|
+
CallHandler.process(route, @connection, self.session)
|
171
|
+
end
|
172
|
+
|
173
|
+
#-----------------------------------------------------------------------------
|
174
|
+
# PUBLIC CLASS METHODS
|
175
|
+
#-----------------------------------------------------------------------------
|
176
|
+
|
177
|
+
# call this method to attach helpers to your call handler. Helpers are
|
178
|
+
# defined and located in much the same way as helpers on
|
179
|
+
# ActiveController objects.
|
180
|
+
|
181
|
+
def self.helper(*args)
|
182
|
+
args.flatten.each do |arg|
|
183
|
+
case arg
|
184
|
+
when Module
|
185
|
+
add_template_helper(arg)
|
186
|
+
when String, Symbol
|
187
|
+
file_name = arg.to_s.underscore + '_helper'
|
188
|
+
class_name = file_name.camelize
|
189
|
+
|
190
|
+
begin
|
191
|
+
require_dependency("helpers/#{file_name}")
|
192
|
+
rescue LoadError => load_error
|
193
|
+
requiree = / -- (.*?)(\.rb)?$/.match(load_error).to_a[1]
|
194
|
+
msg = (requiree == file_name) ? "Missing helper file helpers/#{file_name}.rb" : "Can't load file: #{requiree}"
|
195
|
+
raise LoadError.new(msg).copy_blame!(load_error)
|
196
|
+
end
|
197
|
+
|
198
|
+
add_template_helper(class_name.constantize)
|
199
|
+
else
|
200
|
+
raise ArgumentError, 'helper expects String, Symbol, or Module argument'
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Based on the specified URN, determine the handler & action to use for
|
206
|
+
# the incoming call.
|
207
|
+
|
208
|
+
def self.route(path)
|
209
|
+
route = {}
|
210
|
+
|
211
|
+
uri = URI.parse(path)
|
212
|
+
|
213
|
+
path = uri.path
|
214
|
+
path = path[1..-1] if path[0] = '/'
|
215
|
+
|
216
|
+
# todo: deal with paths like foo/bar/1
|
217
|
+
if path.index '/' then
|
218
|
+
route[:handler] = path[0..path.index('/')-1]
|
219
|
+
route[:action] = path[path.index('/')+1..-1]
|
220
|
+
else
|
221
|
+
route[:handler] = path
|
222
|
+
route[:action] = :dialup
|
223
|
+
# todo: check the connection object to see if this is a dialout connection
|
224
|
+
end
|
225
|
+
# todo: extract uri.query into parameters on the route
|
226
|
+
route
|
227
|
+
end
|
228
|
+
|
229
|
+
# Based on the specified route, load and initialize a handler. we then
|
230
|
+
# call the appropriate action based on the route. if the handler species
|
231
|
+
# that we should redirect, then do that.
|
232
|
+
|
233
|
+
def self.process(route, connection, session = {})
|
234
|
+
handler = nil
|
235
|
+
|
236
|
+
while route do
|
237
|
+
|
238
|
+
# check to see if the handler actually needs to be created
|
239
|
+
|
240
|
+
if !handler ||
|
241
|
+
!match_class(handler.class, route[:handler]) then
|
242
|
+
|
243
|
+
# to create the handler we either create an instance of the
|
244
|
+
# referenced class or we have to load a dependency and create the
|
245
|
+
# class referenced.
|
246
|
+
|
247
|
+
case route[:handler]
|
248
|
+
when Class
|
249
|
+
puts "running ragi class"
|
250
|
+
handler = route[:handler].new
|
251
|
+
when String, Symbol
|
252
|
+
puts "running ragi string lookup"
|
253
|
+
file_name = route[:handler].to_s.underscore + '_handler'
|
254
|
+
puts "filename: #{file_name}"
|
255
|
+
class_name = file_name.camelize
|
256
|
+
|
257
|
+
begin
|
258
|
+
require_dependency("handlers/#{file_name}")
|
259
|
+
rescue LoadError => load_error
|
260
|
+
requiree = / -- (.*?)(\.rb)?$/.match(load_error).to_a[1]
|
261
|
+
msg = (requiree == file_name) ? "Missing handler file handlers/#{file_name}.rb" : "Can't load file: #{requiree}"
|
262
|
+
raise LoadError.new(msg).copy_blame!(load_error)
|
263
|
+
end
|
264
|
+
|
265
|
+
handler = class_name.constantize.new
|
266
|
+
else
|
267
|
+
# todo: test support for passing handler as an instance
|
268
|
+
handler = route[:handler]
|
269
|
+
end
|
270
|
+
handler.init_session(route, connection, session)
|
271
|
+
end
|
272
|
+
|
273
|
+
handler.send(route[:action])
|
274
|
+
|
275
|
+
# if a redirection route was set, then we should loop
|
276
|
+
route = handler.redirect_route
|
277
|
+
handler.redirect_route = nil
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
#-----------------------------------------------------------------------------
|
282
|
+
# CONNECTION HELPERS
|
283
|
+
#-----------------------------------------------------------------------------
|
284
|
+
|
285
|
+
# We mirror all the methods of the callConnection object
|
286
|
+
def method_missing(meth, *attrs, &block)
|
287
|
+
if @connection.respond_to?(meth)
|
288
|
+
@connection.__send__(meth, *attrs, &block)
|
289
|
+
else
|
290
|
+
super
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
#-----------------------------------------------------------------------------
|
295
|
+
# INTERNAL METHODS
|
296
|
+
#-----------------------------------------------------------------------------
|
297
|
+
|
298
|
+
# InitSession is called by the framework to initialize a new call handler
|
299
|
+
# as it gets constructed. This method should never be called from outside
|
300
|
+
# the framework.
|
301
|
+
|
302
|
+
def init_session(route, connection, session = {} )
|
303
|
+
@route = route
|
304
|
+
@connection = connection
|
305
|
+
@session = session
|
306
|
+
@redirect_route = nil
|
307
|
+
@call_status = connection.get_call_status
|
308
|
+
|
309
|
+
# Load parameters from the connection
|
310
|
+
@params = connection.params.dup
|
311
|
+
|
312
|
+
# Extract and add the hash data
|
313
|
+
hashData = connection.get_hash_data
|
314
|
+
if hashData then
|
315
|
+
hashData.each do |name, val|
|
316
|
+
@params[name] = val
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
# Add parameters from the route object
|
321
|
+
route.reject { |name, val| [:handler, :action].include? name }.each do |name, val|
|
322
|
+
@params[name] = val
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# Add the template module into us
|
327
|
+
|
328
|
+
def self.add_template_helper(mod)
|
329
|
+
self.class_eval "include #{mod}"
|
330
|
+
end
|
331
|
+
|
332
|
+
# Normalizes the class name passed in. For strings and symbols this will
|
333
|
+
# add the '_handler' string if it is not already present then camelize the
|
334
|
+
# string ('foo_handler' => 'FooHandler'). For classes this will simply
|
335
|
+
# ask the class it's name
|
336
|
+
|
337
|
+
def self.normalize_class_name(obj)
|
338
|
+
case obj
|
339
|
+
when Class
|
340
|
+
obj.to_s
|
341
|
+
when String, Symbol
|
342
|
+
obj = obj.to_s
|
343
|
+
if !obj.downcase.include? 'handler' then
|
344
|
+
obj = obj.to_s.underscore + '_handler'
|
345
|
+
end
|
346
|
+
obj.camelize
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
# Compare two normalized classes to see if they are the same. The following
|
351
|
+
# should all return as equivalent:
|
352
|
+
#
|
353
|
+
# FooBarHelper
|
354
|
+
# "FooBar"
|
355
|
+
# :foo_bar
|
356
|
+
# "foo_bar"
|
357
|
+
|
358
|
+
def self.match_class(a,b)
|
359
|
+
normalize_class_name(a) == normalize_class_name(b)
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
data/call_initiate.rb
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
#
|
2
|
+
# RAGI - Ruby classes for implementing an AGI server for Asterisk
|
3
|
+
# The BSD License for RAGI follows.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2005, SNAPVINE LLC (www.snapvine.com)
|
6
|
+
# All rights reserved.
|
7
|
+
|
8
|
+
# Redistribution and use in source and binary forms, with or without
|
9
|
+
# modification, are permitted provided that the following conditions are met:
|
10
|
+
#
|
11
|
+
# * Redistributions of source code must retain the above copyright notice,
|
12
|
+
# this list of conditions and the following disclaimer.
|
13
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
14
|
+
# this list of conditions and the following disclaimer in the
|
15
|
+
# documentation and/or other materials provided with the distribution.
|
16
|
+
# * Neither the name of SNAPVINE nor the names of its contributors
|
17
|
+
# may be used to endorse or promote products derived from this software
|
18
|
+
# without specific prior written permission.
|
19
|
+
#
|
20
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
21
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
22
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
23
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
24
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
25
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
26
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
27
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
28
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
29
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
30
|
+
#
|
31
|
+
# Class: callInitiate.rb
|
32
|
+
# This class provides a convenient way to place outbound calls through Asterisk.
|
33
|
+
# When answered, calls initiated with callInitiate are redirected back to a
|
34
|
+
# RAGI callHandler for processing
|
35
|
+
#
|
36
|
+
|
37
|
+
require 'cgi'
|
38
|
+
require 'yaml'
|
39
|
+
|
40
|
+
module RAGI
|
41
|
+
class UsageError < StandardError; end
|
42
|
+
class CmdNotFoundError < StandardError; end
|
43
|
+
class ApplicationError < StandardError; end
|
44
|
+
|
45
|
+
DEFAULT_CALL_OPTIONS = {
|
46
|
+
:caller_id => "10",
|
47
|
+
:max_retries => 0,
|
48
|
+
:retry_time => 5,
|
49
|
+
:wait_time => 45
|
50
|
+
}
|
51
|
+
|
52
|
+
class << self
|
53
|
+
|
54
|
+
# The place_call method allows the caller to initiate a call to a given
|
55
|
+
# phonenumber, handling it with the specified URN. In addition, the caller
|
56
|
+
# can specify additional parameters:
|
57
|
+
#
|
58
|
+
# :caller_id - a string containing the caller id to use, e.g.
|
59
|
+
# "8005551212". There is no name, just a number.
|
60
|
+
#
|
61
|
+
# :hash_params - a hash containing parameters that will be made available
|
62
|
+
# to the call handler
|
63
|
+
#
|
64
|
+
# :call_date - specify the time the call should occur, specify nil
|
65
|
+
# to make the call occur immediately
|
66
|
+
#
|
67
|
+
# :unique_id - optional uniqueID useful if you need to delete a
|
68
|
+
# scheduled call later.
|
69
|
+
#
|
70
|
+
# :max_retries - how many times to retry if the call doesn't go through
|
71
|
+
#
|
72
|
+
# :retry_time - time to wait between tries in seconds
|
73
|
+
#
|
74
|
+
# :wait_time - time to wait in seconds for the call to answer,
|
75
|
+
# inclusive of time spent connecting to the PSTN
|
76
|
+
# termination provider.
|
77
|
+
|
78
|
+
def place_call(phone_number, urn, options = {})
|
79
|
+
options = DEFAULT_CALL_OPTIONS.clone.update(options)
|
80
|
+
|
81
|
+
options[:agi_server] ||= RAGI::globalConfig["agiServer"]
|
82
|
+
|
83
|
+
RAGI::CallInitiate.place_call(phone_number,
|
84
|
+
options[:caller_id],
|
85
|
+
urn,
|
86
|
+
options[:hash_params],
|
87
|
+
options[:call_date],
|
88
|
+
options[:unique_id],
|
89
|
+
options[:max_retries],
|
90
|
+
options[:retry_time],
|
91
|
+
options[:wait_time],
|
92
|
+
options[:set_vars])
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class CallInitiate
|
97
|
+
|
98
|
+
# This function is called by RAGI.place_call to actually do the work.
|
99
|
+
def self.place_call(phoneNumber, callerID, urn, hashData, callDate, uniqueID, maxRetries, retryTime, waitTime, extraChannelVars)
|
100
|
+
|
101
|
+
placeCallNow = false
|
102
|
+
if (callDate == nil)
|
103
|
+
placeCallNow = true
|
104
|
+
callDate = Time.now
|
105
|
+
end
|
106
|
+
|
107
|
+
if (urn[0..0] != '/') then
|
108
|
+
raise ApplicationError, "Relative URNs cannot be used (found #{urn})"
|
109
|
+
end
|
110
|
+
|
111
|
+
agiServer = RAGI::globalConfig["agiServer"]
|
112
|
+
RAGI.LOGGER.debug("Initiating call with agi server: #{agiServer}")
|
113
|
+
|
114
|
+
fileName = getfilename(phoneNumber, callDate, uniqueID)
|
115
|
+
|
116
|
+
wakeupFile = File.join(RAGI::globalConfig["wakeupCallPath"], fileName.to_s)
|
117
|
+
outgoingFile = File.join(RAGI::globalConfig["outgoingCallPath"], fileName.to_s)
|
118
|
+
callfile = File.new(fileName.to_s, "w+")
|
119
|
+
|
120
|
+
s = ""
|
121
|
+
s << ";This file was generated by RAGI's callInitiate class\r\n"
|
122
|
+
s << ";File generated date: #{Time.now.strftime('%m-%d-%Y at %H:%M -- %A')}\r\n"
|
123
|
+
s << ";Call date: #{Time.new.strftime('%m-%d-%Y at %H:%M -- %A')}\r\n\r\n"
|
124
|
+
s << "Channel: Local/outbound@dialout\r\n"
|
125
|
+
s << "Callerid: <#{callerID}>\r\n"
|
126
|
+
s << "MaxRetries: #{maxRetries}\r\n"
|
127
|
+
s << "RetryTime: #{retryTime}\r\n"
|
128
|
+
s << "WaitTime: #{waitTime}\r\n"
|
129
|
+
s << "Context: dialout\r\n"
|
130
|
+
s << ";magic extension for outbound calls via RAGI callInitiate\r\n"
|
131
|
+
s << "Extension: outbound-handler\r\n"
|
132
|
+
s << "Priority: 1\r\n"
|
133
|
+
|
134
|
+
#put in the fundamental call handling variables
|
135
|
+
s << "SetVar: CallInitiate_phonenumber=#{phoneNumber}\r\n"
|
136
|
+
s << "SetVar: CallInitiate_callerid=#{callerID}\r\n"
|
137
|
+
|
138
|
+
s << "\r\nSetVar: AGI_URL=#{urn}\r\n"
|
139
|
+
s << "\r\nSetVar: AGI_SERVER=#{agiServer}\r\n"
|
140
|
+
|
141
|
+
if (extraChannelVars)
|
142
|
+
extraChannelVars.each do |name, value|
|
143
|
+
s << "\r\nSetVar: #{name}=#{value}\r\n"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
marshalData = CGI.escape(YAML.dump(hashData))
|
148
|
+
|
149
|
+
s << "\r\nSetVar: CallInitiate_hashdata=#{marshalData}\r\n\r\n"
|
150
|
+
|
151
|
+
callfile.print(s)
|
152
|
+
|
153
|
+
#Note: asterisk call files need these terminating line feeds or else it crashes.
|
154
|
+
callfile.print("\r\n\r\n\r\n")
|
155
|
+
callfile.close
|
156
|
+
|
157
|
+
# Setup permissions so that asterisk can read this thing no matter what...
|
158
|
+
File.chmod(0777, fileName)
|
159
|
+
if (placeCallNow == true)
|
160
|
+
FileUtils.mv fileName, outgoingFile #place call now
|
161
|
+
# todo Can't rely on the file still being there. Asterisk may have grabbed it
|
162
|
+
File.chmod(0777, outgoingFile)
|
163
|
+
else
|
164
|
+
FileUtils.mv fileName, wakeupFile #schedule call for later
|
165
|
+
File.chmod(0777, wakeupFile)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def self.delete_call(phoneNumber, callDate, uniqueID)
|
170
|
+
#assert: callDate is not nil
|
171
|
+
fileName = getfilename(phoneNumber, callDate, uniqueID)
|
172
|
+
wakeupFile = File.join(RAGI::globalConfig["wakeupCallPath"], fileName)
|
173
|
+
FileUtils.rm wakeupFile, :force => true
|
174
|
+
end
|
175
|
+
|
176
|
+
# Returns the hashtable representation of str if string is encoded by hashEncode
|
177
|
+
def self.decode_call_params(hashStr)
|
178
|
+
if hashStr then
|
179
|
+
YAML.load(CGI.unescape(hashStr))
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
private
|
184
|
+
def self.getfilename(phoneNumber, callDate, uniqueID)
|
185
|
+
"#{callDate.strftime('%H%M')}.#{phoneNumber}.#{uniqueID}.call"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|