Add. Use `route_to_bridge` module to build routes for ring groups. (#2907)
* Add. Use `route_to_bridge` module to build routes fro ring groups. This commit has several improvements 1. Select only needed fields. (do not select quite big XML text strings) 2. Filter routes also by context name 3. Filter dialplans also by hostname 4. Handle conditions based not only `destination_number` 5. Handle `break` and `continue` attributes for extensions 6. Escape vars inside dial-string 7. Add log messages similar as FS dialplan do * Add. `route_to_bridge` set inline vars so it possible use then in next conditions. Add. `route_to_bridge` can execute basic api commands from allowed lists. `route_to_bridge` expand all known vars. If var is unknown then it pass as is. Fix. `export nolocal:` action. * Fix. Short variable names * Add. some comments * Fix. Do not try execute empty string This produce error messages `[ERR] switch_cpp.cpp:759 No application specified` * Fix. Export nolocal values.
This commit is contained in:
parent
0ccbad7d53
commit
b0422af3e2
|
|
@ -30,6 +30,9 @@
|
||||||
--include the log
|
--include the log
|
||||||
local log = require "resources.functions.log".ring_group
|
local log = require "resources.functions.log".ring_group
|
||||||
|
|
||||||
|
-- include libs
|
||||||
|
local route_to_bridge = require "resources.functions.route_to_bridge"
|
||||||
|
|
||||||
--connect to the database
|
--connect to the database
|
||||||
local Database = require "resources.functions.database";
|
local Database = require "resources.functions.database";
|
||||||
dbh = Database.new('system');
|
dbh = Database.new('system');
|
||||||
|
|
@ -118,6 +121,7 @@
|
||||||
uuid = session:getVariable("uuid");
|
uuid = session:getVariable("uuid");
|
||||||
context = session:getVariable("context");
|
context = session:getVariable("context");
|
||||||
call_direction = session:getVariable("call_direction");
|
call_direction = session:getVariable("call_direction");
|
||||||
|
accountcode = session:getVariable("accountcode");
|
||||||
end
|
end
|
||||||
|
|
||||||
--default to local if nil
|
--default to local if nil
|
||||||
|
|
@ -170,6 +174,9 @@
|
||||||
-- error();
|
-- error();
|
||||||
--end
|
--end
|
||||||
|
|
||||||
|
--get current switchname
|
||||||
|
hostname = trim(api:execute("switchname", ""))
|
||||||
|
|
||||||
--get the ring group
|
--get the ring group
|
||||||
ring_group_forward_enabled = "";
|
ring_group_forward_enabled = "";
|
||||||
ring_group_forward_destination = "";
|
ring_group_forward_destination = "";
|
||||||
|
|
@ -187,7 +194,7 @@
|
||||||
missed_call_app = row["ring_group_missed_call_app"];
|
missed_call_app = row["ring_group_missed_call_app"];
|
||||||
missed_call_data = row["ring_group_missed_call_data"];
|
missed_call_data = row["ring_group_missed_call_data"];
|
||||||
end);
|
end);
|
||||||
|
|
||||||
--get the ring group user
|
--get the ring group user
|
||||||
sql = "SELECT r.*, u.user_uuid FROM v_ring_groups as r, v_ring_group_users as u ";
|
sql = "SELECT r.*, u.user_uuid FROM v_ring_groups as r, v_ring_group_users as u ";
|
||||||
sql = sql .. "where r.ring_group_uuid = :ring_group_uuid ";
|
sql = sql .. "where r.ring_group_uuid = :ring_group_uuid ";
|
||||||
|
|
@ -304,7 +311,7 @@
|
||||||
--process the ring group
|
--process the ring group
|
||||||
if (ring_group_forward_enabled == "true" and string.len(ring_group_forward_destination) > 0) then
|
if (ring_group_forward_enabled == "true" and string.len(ring_group_forward_destination) > 0) then
|
||||||
--forward the ring group
|
--forward the ring group
|
||||||
session:setVariable("toll_allow",ring_group_forward_toll_allow);
|
session:setVariable("toll_allow",ring_group_forward_toll_allow);
|
||||||
session:execute("transfer", ring_group_forward_destination.." XML "..context);
|
session:execute("transfer", ring_group_forward_destination.." XML "..context);
|
||||||
else
|
else
|
||||||
--get the strategy of the ring group, if random, we use random() to order the destinations
|
--get the strategy of the ring group, if random, we use random() to order the destinations
|
||||||
|
|
@ -422,33 +429,9 @@
|
||||||
|
|
||||||
--get the dialplan data and save it to a table
|
--get the dialplan data and save it to a table
|
||||||
if (external) then
|
if (external) then
|
||||||
sql = [[select * from v_dialplans as d, v_dialplan_details as s
|
dialplans = route_to_bridge.preload_dialplan(
|
||||||
where (d.domain_uuid = :domain_uuid or d.domain_uuid is null)
|
dbh, domain_uuid, {hostname = hostname, context = context}
|
||||||
and d.app_uuid = '8c914ec3-9fc0-8ab5-4cda-6c9288bdc9a3'
|
)
|
||||||
and d.dialplan_enabled = 'true'
|
|
||||||
and d.dialplan_uuid = s.dialplan_uuid
|
|
||||||
order by
|
|
||||||
d.dialplan_order asc,
|
|
||||||
d.dialplan_name asc,
|
|
||||||
d.dialplan_uuid asc,
|
|
||||||
s.dialplan_detail_group asc,
|
|
||||||
CASE s.dialplan_detail_tag
|
|
||||||
WHEN 'condition' THEN 1
|
|
||||||
WHEN 'action' THEN 2
|
|
||||||
WHEN 'anti-action' THEN 3
|
|
||||||
ELSE 100 END,
|
|
||||||
s.dialplan_detail_order asc
|
|
||||||
]];
|
|
||||||
params = {domain_uuid = domain_uuid};
|
|
||||||
if debug["sql"] then
|
|
||||||
freeswitch.consoleLog("notice", "[ring group] SQL:" .. sql .. "; params:" .. json.encode(params) .. "\n");
|
|
||||||
end
|
|
||||||
dialplans = {};
|
|
||||||
x = 1;
|
|
||||||
assert(dbh:query(sql, params, function(row)
|
|
||||||
dialplans[x] = row;
|
|
||||||
x = x + 1;
|
|
||||||
end));
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--process the destinations
|
--process the destinations
|
||||||
|
|
@ -580,77 +563,65 @@
|
||||||
extension_uuid = trim(api:executeString(cmd));
|
extension_uuid = trim(api:executeString(cmd));
|
||||||
--send to user
|
--send to user
|
||||||
local dial_string_to_user = "[sip_invite_domain="..domain_name..",call_direction="..call_direction..","..group_confirm.."leg_timeout="..destination_timeout..","..delay_name.."="..destination_delay..",dialed_extension=" .. row.destination_number .. ",extension_uuid="..extension_uuid .. row.record_session .. "]user/" .. row.destination_number .. "@" .. domain_name;
|
local dial_string_to_user = "[sip_invite_domain="..domain_name..",call_direction="..call_direction..","..group_confirm.."leg_timeout="..destination_timeout..","..delay_name.."="..destination_delay..",dialed_extension=" .. row.destination_number .. ",extension_uuid="..extension_uuid .. row.record_session .. "]user/" .. row.destination_number .. "@" .. domain_name;
|
||||||
dial_string = dial_string_to_user;
|
dial_string = dial_string_to_user;
|
||||||
elseif (tonumber(destination_number) == nil) then
|
elseif (tonumber(destination_number) == nil) then
|
||||||
--sip uri
|
--sip uri
|
||||||
dial_string = "[sip_invite_domain="..domain_name..",call_direction="..call_direction..","..group_confirm.."leg_timeout="..destination_timeout..","..delay_name.."="..destination_delay.."]" .. row.destination_number;
|
dial_string = "[sip_invite_domain="..domain_name..",call_direction="..call_direction..","..group_confirm.."leg_timeout="..destination_timeout..","..delay_name.."="..destination_delay.."]" .. row.destination_number;
|
||||||
else
|
else
|
||||||
--external number
|
--external number
|
||||||
y = 0;
|
dial_string = nil
|
||||||
dial_string = '';
|
|
||||||
previous_dialplan_uuid = '';
|
local session_mt = {__index = function(_, k) return session:getVariable(k) end}
|
||||||
regex_match = false;
|
local params = setmetatable({
|
||||||
for k, r in pairs(dialplans) do
|
__api__ = api,
|
||||||
if (y > 0) then
|
destination_number = destination_number,
|
||||||
if (previous_dialplan_uuid ~= r.dialplan_uuid) then
|
user_exists = 'false',
|
||||||
regex_match = false;
|
call_direction = 'outbound',
|
||||||
bridge_match = false;
|
domain_name = domain_name,
|
||||||
square = square .. "]";
|
domain_uuid = domain_uuid,
|
||||||
y = 0;
|
destination_timeout = destination_timeout,
|
||||||
|
destination_delay = destination_delay,
|
||||||
|
}, session_mt)
|
||||||
|
|
||||||
|
local confirm = string.gsub(group_confirm, ',$', '') -- remove `,` from end of string
|
||||||
|
local route = route_to_bridge.apply_vars({ -- predefined actions
|
||||||
|
"domain_name=${domain_name}",
|
||||||
|
"domain_uuid=${domain_uuid}",
|
||||||
|
"sip_invite_domain=${domain_name}",
|
||||||
|
"leg_timeout=${destination_timeout}",
|
||||||
|
delay_name .. "=${destination_delay}",
|
||||||
|
"ignore_early_media=true",
|
||||||
|
confirm,
|
||||||
|
}, params)
|
||||||
|
|
||||||
|
route = route_to_bridge(dialplans, domain_uuid, params, route)
|
||||||
|
|
||||||
|
if route and route.bridge then
|
||||||
|
local remove_actions = {
|
||||||
|
["effective_caller_id_name="] = true;
|
||||||
|
["effective_caller_id_number="] = true;
|
||||||
|
['sip_h_X-accountcode='] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
-- cleanup variables
|
||||||
|
local i = 1 while i < #route do
|
||||||
|
-- remove vars from prev variant
|
||||||
|
if remove_actions[ route[i] ] then
|
||||||
|
table.remove(route, i)
|
||||||
|
i = i - 1
|
||||||
|
-- remove vars with unresolved vars
|
||||||
|
elseif string.find(route[i], '%${.+}') then
|
||||||
|
table.remove(route, i)
|
||||||
|
i = i - 1
|
||||||
|
-- remove vars with empty values
|
||||||
|
elseif string.find(route[i], '=$') then
|
||||||
|
table.remove(route, i)
|
||||||
|
i = i - 1
|
||||||
end
|
end
|
||||||
|
i = i + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
if (r.dialplan_detail_tag == "condition") then
|
dial_string = '[' .. table.concat(route, ',') .. ']' .. route.bridge
|
||||||
if (r.dialplan_detail_type == "destination_number") then
|
|
||||||
dial_string = "regex m:~"..destination_number.."~"..r.dialplan_detail_data
|
|
||||||
if (api:execute("regex", "m:~"..destination_number.."~"..r.dialplan_detail_data) == "true") then
|
|
||||||
--get the regex result
|
|
||||||
destination_result = trim(api:execute("regex", "m:~"..destination_number.."~"..r.dialplan_detail_data.."~$1"));
|
|
||||||
--set match equal to true
|
|
||||||
regex_match = true;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
--regex_match = true;
|
|
||||||
--dial_string = r.dialplan_detail_data;
|
|
||||||
if (r.dialplan_detail_tag == "action") then
|
|
||||||
if (regex_match) then
|
|
||||||
--dial_string = 'match';
|
|
||||||
--replace $1
|
|
||||||
dialplan_detail_data = r.dialplan_detail_data:gsub("$1", destination_result);
|
|
||||||
--if the session is set then process the actions
|
|
||||||
if (y == 0) then
|
|
||||||
square = "[domain_name="..domain_name..",domain_uuid="..domain_uuid..",sip_invite_domain="..domain_name..",call_direction=outbound,"..group_confirm.."leg_timeout="..destination_timeout..","..delay_name.."="..destination_delay..",ignore_early_media=true,";
|
|
||||||
end
|
|
||||||
if (r.dialplan_detail_type == "set") then
|
|
||||||
--session:execute("eval", dialplan_detail_data);
|
|
||||||
if (dialplan_detail_data == "sip_h_X-accountcode=${accountcode}") then
|
|
||||||
if (session) then
|
|
||||||
accountcode = session:getVariable("accountcode");
|
|
||||||
if (accountcode) then
|
|
||||||
square = square .. "sip_h_X-accountcode="..accountcode..",";
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif (dialplan_detail_data == "effective_caller_id_name=${outbound_caller_id_name}") then
|
|
||||||
elseif (dialplan_detail_data == "effective_caller_id_number=${outbound_caller_id_number}") then
|
|
||||||
else
|
|
||||||
square = square .. dialplan_detail_data..",";
|
|
||||||
end
|
|
||||||
elseif (r.dialplan_detail_type == "bridge") then
|
|
||||||
if (bridge_match) then
|
|
||||||
dial_string = dial_string .. delimiter .. square .."]"..dialplan_detail_data;
|
|
||||||
square = "[";
|
|
||||||
else
|
|
||||||
dial_string = square .."]"..dialplan_detail_data;
|
|
||||||
end
|
|
||||||
bridge_match = true;
|
|
||||||
break;
|
|
||||||
end
|
|
||||||
--increment the value
|
|
||||||
y = y + 1;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
previous_dialplan_uuid = r.dialplan_uuid;
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -776,12 +747,16 @@
|
||||||
or session:getVariable("originate_disposition") == "failure"
|
or session:getVariable("originate_disposition") == "failure"
|
||||||
) then
|
) then
|
||||||
--execute the time out action
|
--execute the time out action
|
||||||
session:execute(ring_group_timeout_app, ring_group_timeout_data);
|
if ring_group_timeout_app and #ring_group_timeout_app > 0 then
|
||||||
|
session:execute(ring_group_timeout_app, ring_group_timeout_data);
|
||||||
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if (ring_group_timeout_app ~= nil) then
|
if (ring_group_timeout_app ~= nil) then
|
||||||
--execute the time out action
|
--execute the time out action
|
||||||
session:execute(ring_group_timeout_app, ring_group_timeout_data);
|
if ring_group_timeout_app and #ring_group_timeout_app > 0 then
|
||||||
|
session:execute(ring_group_timeout_app, ring_group_timeout_data);
|
||||||
|
end
|
||||||
else
|
else
|
||||||
local sql = "SELECT ring_group_timeout_app, ring_group_timeout_data FROM v_ring_groups ";
|
local sql = "SELECT ring_group_timeout_app, ring_group_timeout_data FROM v_ring_groups ";
|
||||||
sql = sql .. "where ring_group_uuid = :ring_group_uuid";
|
sql = sql .. "where ring_group_uuid = :ring_group_uuid";
|
||||||
|
|
@ -791,7 +766,9 @@
|
||||||
end
|
end
|
||||||
dbh:query(sql, params, function(row)
|
dbh:query(sql, params, function(row)
|
||||||
--execute the time out action
|
--execute the time out action
|
||||||
session:execute(row.ring_group_timeout_app, row.ring_group_timeout_data);
|
if row.ring_group_timeout_app and #row.ring_group_timeout_app > 0 then
|
||||||
|
session:execute(row.ring_group_timeout_app, row.ring_group_timeout_data);
|
||||||
|
end
|
||||||
end);
|
end);
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
local log = require "resources.functions.log".route_to_bridge
|
local log = require "resources.functions.log".route_to_bridge
|
||||||
|
require "resources.functions.split"
|
||||||
|
|
||||||
|
local allows_functions = {
|
||||||
|
['user_data'] = true,
|
||||||
|
}
|
||||||
|
|
||||||
local pcre_match
|
local pcre_match
|
||||||
|
|
||||||
|
|
@ -48,20 +53,35 @@ local function pcre_self_test()
|
||||||
io.write(' - ok\n')
|
io.write(' - ok\n')
|
||||||
end
|
end
|
||||||
|
|
||||||
local select_routes_sql = [[
|
local select_outbound_dialplan_sql = [[
|
||||||
select *
|
SELECT
|
||||||
from v_dialplans
|
d.dialplan_uuid,
|
||||||
where (domain_uuid = :domain_uuid or domain_uuid is null)
|
d.dialplan_context,
|
||||||
and (hostname = :hostname or hostname is null)
|
d.dialplan_continue,
|
||||||
and app_uuid = '8c914ec3-9fc0-8ab5-4cda-6c9288bdc9a3'
|
s.dialplan_detail_group,
|
||||||
and dialplan_enabled = 'true'
|
s.dialplan_detail_break,
|
||||||
order by dialplan_order asc
|
s.dialplan_detail_data,
|
||||||
]]
|
s.dialplan_detail_inline,
|
||||||
|
s.dialplan_detail_tag,
|
||||||
local select_extensions_sql = [[
|
s.dialplan_detail_type
|
||||||
select * from v_dialplan_details
|
FROM v_dialplans as d, v_dialplan_details as s
|
||||||
where dialplan_uuid = :dialplan_uuid
|
WHERE (d.domain_uuid = :domain_uuid OR d.domain_uuid IS NULL)
|
||||||
order by dialplan_detail_group asc, dialplan_detail_order asc
|
AND (d.hostname = :hostname OR d.hostname IS NULL)
|
||||||
|
AND d.app_uuid = '8c914ec3-9fc0-8ab5-4cda-6c9288bdc9a3'
|
||||||
|
AND d.dialplan_enabled = 'true'
|
||||||
|
AND d.dialplan_uuid = s.dialplan_uuid
|
||||||
|
ORDER BY
|
||||||
|
d.dialplan_order ASC,
|
||||||
|
d.dialplan_name ASC,
|
||||||
|
d.dialplan_uuid ASC,
|
||||||
|
s.dialplan_detail_group ASC,
|
||||||
|
CASE s.dialplan_detail_tag
|
||||||
|
WHEN 'condition' THEN 1
|
||||||
|
WHEN 'action' THEN 2
|
||||||
|
WHEN 'anti-action' THEN 3
|
||||||
|
ELSE 100
|
||||||
|
END,
|
||||||
|
s.dialplan_detail_order ASC
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local function append(t, v)
|
local function append(t, v)
|
||||||
|
|
@ -87,6 +107,11 @@ local function check_conditions(group, fields)
|
||||||
last = (n == #group.conditions)
|
last = (n == #group.conditions)
|
||||||
|
|
||||||
local value = fields[condition.type]
|
local value = fields[condition.type]
|
||||||
|
if (not value) and (condition_type ~= '') then -- try var name
|
||||||
|
local condition_type = string.match(condition.type, '^%${(.*)}$')
|
||||||
|
if condition_type then value = fields[condition_type] end
|
||||||
|
end
|
||||||
|
|
||||||
if (not value) and (condition.type ~= '') then -- skip unkonw fields
|
if (not value) and (condition.type ~= '') then -- skip unkonw fields
|
||||||
log.errf('Unsupportded condition: %s', condition.type)
|
log.errf('Unsupportded condition: %s', condition.type)
|
||||||
matches, pass = {}, false
|
matches, pass = {}, false
|
||||||
|
|
@ -101,20 +126,20 @@ local function check_conditions(group, fields)
|
||||||
end
|
end
|
||||||
|
|
||||||
break_on = condition.break_on
|
break_on = condition.break_on
|
||||||
if break_on == 'always' then break end
|
if break_on == 'always' then break
|
||||||
if break_on ~= 'never' then
|
elseif break_on ~= 'never' then
|
||||||
if pass and break_on == 'on-true' then break end
|
if pass then if break_on == 'on-true' then break end
|
||||||
if not pass and (break_on == 'on-false' or break_on == '') then break end
|
elseif break_on == 'on-false' or break_on == '' then break end
|
||||||
end
|
end
|
||||||
|
|
||||||
break_on = nil
|
break_on = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- we shuld execute action/anti-action only if we check ALL conditions
|
-- we should execute action/anti-action only if we check ALL conditions
|
||||||
local act
|
local act
|
||||||
if last then act = pass and 'action' or 'anti-action' end
|
if last then act = pass and 'action' or 'anti-action' end
|
||||||
|
|
||||||
-- we shuld break
|
-- we should break
|
||||||
return act, not not break_on, matches
|
return act, not not break_on, matches
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -124,23 +149,67 @@ local function apply_match(s, match)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function apply_var(s, fields)
|
||||||
|
local str = string.gsub(s, "%$?%${([^$%(%){}= ]-)}", function(var)
|
||||||
|
return fields[var]
|
||||||
|
end)
|
||||||
|
|
||||||
|
if fields.__api__ then
|
||||||
|
local api = fields.__api__
|
||||||
|
-- try call functions like ('set result=${user_data(args)}')
|
||||||
|
str = string.gsub(str, "%${([^$%(%){}= ]+)%s*%((.-)%)%s*}", function(fn, par)
|
||||||
|
if allows_functions[fn] then
|
||||||
|
return api:execute(fn, par) or ''
|
||||||
|
end
|
||||||
|
log.warningf('try call not allowed function %s', tostring(fn))
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- try call functions like 'set result=${user_data args}'
|
||||||
|
str = string.gsub(str, "%${([^$%(%){}= ]+)%s+(%S.-)%s*}", function(fn, par)
|
||||||
|
if allows_functions[fn] then
|
||||||
|
return api:execute(fn, par) or ''
|
||||||
|
end
|
||||||
|
log.warningf('try call not allowed function %s', tostring(fn))
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
if string.find(str, '%${.+}') then
|
||||||
|
log.warningf('can not resolve vars inside `%s`', tostring(str))
|
||||||
|
end
|
||||||
|
return str
|
||||||
|
end
|
||||||
|
|
||||||
local function group_to_bridge(actions, group, fields)
|
local function group_to_bridge(actions, group, fields)
|
||||||
local action, do_break, matches = check_conditions(group, fields)
|
local action_type, do_break, matches = check_conditions(group, fields)
|
||||||
if action then
|
if action_type then
|
||||||
local t = (action == 'action') and group.actions or group.anti_actions
|
local t = (action_type == 'action') and group.actions or group.anti_actions
|
||||||
for _, element in ipairs(t) do
|
for _, action in ipairs(t) do
|
||||||
local value = element.data
|
local value = action.data
|
||||||
if element.type == 'export' and string.sub(value, 1, 8) == 'nolocal:' then
|
|
||||||
value = string.sub(value, 9)
|
-- we only support set/export actions
|
||||||
|
if action.type == 'export' or action.type == 'set' then
|
||||||
|
local key
|
||||||
|
|
||||||
|
key, value = split_first(value, '=', true)
|
||||||
|
if key then
|
||||||
|
local bleg_only = (action.type == 'export') and (string.sub(key, 1, 8) == 'nolocal:')
|
||||||
|
if bleg_only then key = string.sub(key, 9) end
|
||||||
|
|
||||||
|
value = apply_match(value, matches)
|
||||||
|
value = apply_var(value, fields)
|
||||||
|
|
||||||
|
if action.inline and not bleg_only then
|
||||||
|
fields[key] = value
|
||||||
|
end
|
||||||
|
|
||||||
|
--! @todo do value escape?
|
||||||
|
append(actions, key .. '=' .. value)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- we only support action/export
|
if action.type == 'bridge' then
|
||||||
if element.type == 'export' or element.type == 'set' then
|
|
||||||
value = apply_match(value, matches)
|
value = apply_match(value, matches)
|
||||||
append(actions, value)
|
value = apply_var(value, fields)
|
||||||
end
|
|
||||||
|
|
||||||
if element.type == 'bridge' then
|
|
||||||
actions.bridge = apply_match(value, matches)
|
actions.bridge = apply_match(value, matches)
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
@ -158,6 +227,69 @@ local function extension_to_bridge(extension, actions, fields)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function self_test()
|
local function self_test()
|
||||||
|
local unpack = unpack or table.unpack
|
||||||
|
|
||||||
|
local function assert_equal(expected, actions)
|
||||||
|
for i = 1, math.max(#expected, #actions) do
|
||||||
|
local e, v, msg = expected[i], actions[i]
|
||||||
|
if not e then
|
||||||
|
msg = string.format("unexpected value #%d - `%s`", i, v)
|
||||||
|
elseif not v then
|
||||||
|
msg = string.format("expected value `%s` at position #%d, but got no value", e, i)
|
||||||
|
elseif e ~= v then
|
||||||
|
msg = string.format("expected value `%s` at position #%d but got: `%s`", e, i, v)
|
||||||
|
end
|
||||||
|
assert(not msg, msg)
|
||||||
|
end
|
||||||
|
|
||||||
|
for name, e in pairs(expected) do
|
||||||
|
local v, msg = actions[name]
|
||||||
|
if not v then
|
||||||
|
msg = string.format("%s expected as `%s`, but got no value", name, e)
|
||||||
|
elseif e ~= v then
|
||||||
|
msg = string.format("expected value for %s is `%s`, but got: `%s`", name, e, v)
|
||||||
|
end
|
||||||
|
assert(not msg, msg)
|
||||||
|
end
|
||||||
|
|
||||||
|
for name, v in pairs(actions) do
|
||||||
|
local e, msg = expected[name]
|
||||||
|
if not e then
|
||||||
|
msg = string.format("expected value %s = `%s`", name, v)
|
||||||
|
end
|
||||||
|
assert(not msg, msg)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_grout_to_bridge(group, params, ret, expected)
|
||||||
|
local actions = {}
|
||||||
|
local result = group_to_bridge(actions, group, params)
|
||||||
|
if result ~= ret then
|
||||||
|
local msg = string.format('expected `%s` but got `%s`', tostring(ret), tostring(result))
|
||||||
|
assert(false, msg)
|
||||||
|
end
|
||||||
|
assert_equal(expected, actions)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- mock for API
|
||||||
|
local function API(t)
|
||||||
|
local api = {
|
||||||
|
execute = function(self, cmd, args)
|
||||||
|
cmd = assert(t[cmd])
|
||||||
|
return cmd[args]
|
||||||
|
end;
|
||||||
|
}
|
||||||
|
return api
|
||||||
|
end
|
||||||
|
|
||||||
|
local old_log = log
|
||||||
|
log = {
|
||||||
|
errf = function() end;
|
||||||
|
warningf = function() end;
|
||||||
|
debugf = function() end;
|
||||||
|
}
|
||||||
|
|
||||||
pcre_self_test()
|
pcre_self_test()
|
||||||
|
|
||||||
local test_conditions = {
|
local test_conditions = {
|
||||||
|
|
@ -251,53 +383,296 @@ local function self_test()
|
||||||
assert(do_break == result[2])
|
assert(do_break == result[2])
|
||||||
io.write(' - ok\n')
|
io.write(' - ok\n')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local test_actions = {
|
||||||
|
{ -- should not touch unknown vars
|
||||||
|
{actions={
|
||||||
|
{type='set', data='a=${b}'}
|
||||||
|
};
|
||||||
|
conditions={{type='', data='', break_on='on-true'}};
|
||||||
|
},
|
||||||
|
{ -- parameters
|
||||||
|
},
|
||||||
|
{ -- result
|
||||||
|
'a=${b}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ -- should call execute command with braces
|
||||||
|
{actions={
|
||||||
|
{type='set', data='a=${user_data(a b c)}'}
|
||||||
|
};
|
||||||
|
conditions={{type='', data='', break_on='on-true'}};
|
||||||
|
},
|
||||||
|
{ -- parameters
|
||||||
|
__api__ = API{user_data={['a b c'] = 'value'}}
|
||||||
|
},
|
||||||
|
{ -- result
|
||||||
|
'a=value'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ -- should call execute command with spaces
|
||||||
|
{actions={
|
||||||
|
{type='set', data='a=${user_data a b c }'}
|
||||||
|
};
|
||||||
|
conditions={{type='', data='', break_on='on-true'}};
|
||||||
|
},
|
||||||
|
{ -- parameters
|
||||||
|
__api__ = API{user_data={['a b c'] = 'value'}}
|
||||||
|
},
|
||||||
|
{ -- result
|
||||||
|
'a=value'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ -- should not call not allowed function
|
||||||
|
{actions={
|
||||||
|
{type='set', data='a=${user_exists( a b c )}'}
|
||||||
|
};
|
||||||
|
conditions={{type='', data='', break_on='on-true'}};
|
||||||
|
},
|
||||||
|
{ -- parameters
|
||||||
|
__api__ = API{user_data={['a b c'] = 'value'}}
|
||||||
|
},
|
||||||
|
{ -- result
|
||||||
|
'a=${user_exists( a b c )}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ -- should set inline vars
|
||||||
|
{actions={
|
||||||
|
{type='set', data='a=hello', inline=true},
|
||||||
|
{type='set', data='b=${a}'},
|
||||||
|
};
|
||||||
|
conditions={{type='', data='', break_on='on-true'}};
|
||||||
|
},
|
||||||
|
{ -- parameters
|
||||||
|
__api__ = API{user_data={['a b c'] = 'value'}}
|
||||||
|
},
|
||||||
|
{ -- result
|
||||||
|
'a=hello',
|
||||||
|
'b=hello',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ -- should not set not inline vars
|
||||||
|
{actions={
|
||||||
|
{type='set', data='a=hello'},
|
||||||
|
{type='set', data='b=${a}'},
|
||||||
|
};
|
||||||
|
conditions={{type='', data='', break_on='on-true'}};
|
||||||
|
},
|
||||||
|
{ -- parameters
|
||||||
|
__api__ = API{user_data={['a b c'] = 'value'}}
|
||||||
|
},
|
||||||
|
{ -- result
|
||||||
|
'a=hello',
|
||||||
|
'b=${a}',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ -- should expand vars inside call
|
||||||
|
{actions={
|
||||||
|
{type='set', data='a=${user_data(${a}${b})}'},
|
||||||
|
};
|
||||||
|
conditions={{type='', data='', break_on='on-true'}};
|
||||||
|
},
|
||||||
|
{ -- parameters
|
||||||
|
__api__ = API{user_data={['helloworld'] = 'value'}},
|
||||||
|
a = 'hello',
|
||||||
|
b = 'world',
|
||||||
|
},
|
||||||
|
{ -- result
|
||||||
|
'a=value',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ -- should export nolocal
|
||||||
|
{actions={
|
||||||
|
{type='export', data='a=nolocal:value', inline=true},
|
||||||
|
{type='export', data='b=${a}'},
|
||||||
|
};
|
||||||
|
conditions={{type='', data='', break_on='on-true'}};
|
||||||
|
},
|
||||||
|
{ -- parameters
|
||||||
|
},
|
||||||
|
{ -- result
|
||||||
|
'a=value',
|
||||||
|
'b=${a}',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ -- should handle bridge as last action
|
||||||
|
{actions={
|
||||||
|
{type='bridge', data='sofia/gateway/${a}'},
|
||||||
|
{type='set', data='a=123', inline=true},
|
||||||
|
};
|
||||||
|
conditions={{type='', data='', break_on='on-true'}};
|
||||||
|
},
|
||||||
|
{ -- parameters
|
||||||
|
a='gw'
|
||||||
|
},
|
||||||
|
{ -- result
|
||||||
|
bridge = 'sofia/gateway/gw'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ -- should ingnore `nolocal` for set
|
||||||
|
{actions={
|
||||||
|
{type='set', data='a=nolocal:123', inline=true},
|
||||||
|
{type='export', data='b=${a}'},
|
||||||
|
};
|
||||||
|
conditions={{type='', data='', break_on='on-true'}};
|
||||||
|
},
|
||||||
|
{ -- parameters
|
||||||
|
},
|
||||||
|
{ -- result
|
||||||
|
'a=nolocal:123';
|
||||||
|
'b=nolocal:123';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ -- should ingnore unsupportded actions
|
||||||
|
{actions={
|
||||||
|
{type='ring_ready', data=''},
|
||||||
|
{type='answer', data=''},
|
||||||
|
};
|
||||||
|
conditions={{type='', data='', break_on='on-true'}};
|
||||||
|
},
|
||||||
|
{ -- parameters
|
||||||
|
},
|
||||||
|
{ -- result
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test_case in ipairs(test_actions) do
|
||||||
|
local group, params, expected = unpack(test_case)
|
||||||
|
io.write('Test execute #' .. i)
|
||||||
|
test_grout_to_bridge(group, params, true, expected)
|
||||||
|
io.write(' - ok\n')
|
||||||
|
end
|
||||||
|
|
||||||
|
log = old_log
|
||||||
end
|
end
|
||||||
|
|
||||||
local function outbound_route_to_bridge(dbh, domain_uuid, fields)
|
-- Returns array of set/export actions and bridge command.
|
||||||
local actions, dial_string = {}
|
--
|
||||||
require "resources.functions.trim";
|
-- This function does not set any var to session.
|
||||||
local hostname = trim(api:execute("switchname", ""));
|
--
|
||||||
|
-- @param dbh database connection
|
||||||
|
-- @param domain_uuid
|
||||||
|
-- @param fields list of avaliable channel variables.
|
||||||
|
-- if `context` provided then dialplan will be filtered by this var
|
||||||
|
-- `__api__` key can be used to pass freeswitch.API object for execute
|
||||||
|
-- some functions in actions (e.g. `s=${user_data ...}`)
|
||||||
|
-- @param actions optional list of predefined actions
|
||||||
|
-- @return array part of table will contain list of actions.
|
||||||
|
-- `bridge` key will contain bridge statement
|
||||||
|
local function outbound_route_to_bridge(dbh, domain_uuid, fields, actions)
|
||||||
|
actions = actions or {}
|
||||||
|
|
||||||
local params = {}
|
local hostname = fields.hostname
|
||||||
dbh:query(select_routes_sql, {domain_uuid=domain_uuid,hostname=hostname}, function(route)
|
if not hostname then
|
||||||
local extension = {}
|
require "resources.functions.trim";
|
||||||
params.dialplan_uuid = route.dialplan_uuid
|
hostname = trim(api:execute("switchname", ""))
|
||||||
dbh:query(select_extensions_sql, params, function(ext)
|
end
|
||||||
local group_no = tonumber(ext.dialplan_detail_group)
|
|
||||||
local tag = ext.dialplan_detail_tag
|
|
||||||
local element = {
|
|
||||||
type = ext.dialplan_detail_type;
|
|
||||||
data = ext.dialplan_detail_data;
|
|
||||||
break_on = ext.dialplan_detail_break;
|
|
||||||
inline = ext.dialplan_detail_inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
local group = extension[ group_no ] or {
|
-- try filter by context
|
||||||
conditions = {};
|
local context = fields.context
|
||||||
actions = {};
|
if context == '' then context = nil end
|
||||||
anti_actions = {};
|
|
||||||
}
|
|
||||||
extension[ group_no ] = group
|
|
||||||
|
|
||||||
if tag == 'condition' then append(group.conditions, element) end
|
local current_dialplan_uuid, extension
|
||||||
if tag == 'action' then append(group.actions, element) end
|
dbh:query(select_outbound_dialplan_sql, {domain_uuid=domain_uuid, hostname=hostname}, function(route)
|
||||||
if tag == 'anti-action' then append(group.anti_actions, element) end
|
if context and context ~= route.dialplan_context then
|
||||||
end)
|
-- skip dialplan for wrong contexts
|
||||||
|
return
|
||||||
local n = #actions
|
|
||||||
|
|
||||||
extension_to_bridge(extension, actions, fields)
|
|
||||||
|
|
||||||
if actions.bridge or (n > #actions and route.dialplan_continue == 'false') then
|
|
||||||
return 1
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if current_dialplan_uuid ~= route.dialplan_uuid then
|
||||||
|
if extension then
|
||||||
|
local n = #actions
|
||||||
|
extension_to_bridge(extension, actions, fields)
|
||||||
|
-- if we found bridge or add any action and there no continue flag
|
||||||
|
if actions.bridge or (n > #actions and route.dialplan_continue == 'false') then
|
||||||
|
extension = nil
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
extension = {}
|
||||||
|
current_dialplan_uuid = route.dialplan_uuid
|
||||||
|
end
|
||||||
|
|
||||||
|
local group_no = tonumber(route.dialplan_detail_group)
|
||||||
|
local tag = route.dialplan_detail_tag
|
||||||
|
local element = {
|
||||||
|
type = route.dialplan_detail_type;
|
||||||
|
data = route.dialplan_detail_data;
|
||||||
|
break_on = route.dialplan_detail_break;
|
||||||
|
inline = route.dialplan_detail_inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
local group = extension[ group_no ] or {
|
||||||
|
conditions = {};
|
||||||
|
actions = {};
|
||||||
|
anti_actions = {};
|
||||||
|
}
|
||||||
|
extension[ group_no ] = group
|
||||||
|
|
||||||
|
if tag == 'condition' then append(group.conditions, element) end
|
||||||
|
if tag == 'action' then append(group.actions, element) end
|
||||||
|
if tag == 'anti-action' then append(group.anti_actions, element) end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
if extension and next(extension) then
|
||||||
|
extension_to_bridge(extension, actions, fields)
|
||||||
|
end
|
||||||
|
|
||||||
if actions.bridge then return actions end
|
if actions.bridge then return actions end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function apply_vars(actions, fields)
|
||||||
|
for i, action in ipairs(actions) do
|
||||||
|
actions[i] = apply_var(action, fields)
|
||||||
|
end
|
||||||
|
return actions
|
||||||
|
end
|
||||||
|
|
||||||
|
local function wrap_dbh(t)
|
||||||
|
local i = 0
|
||||||
|
return {query = function(self, sql, params, callback)
|
||||||
|
while true do
|
||||||
|
i = i + 1
|
||||||
|
local row = t[i]
|
||||||
|
if not row then break end
|
||||||
|
|
||||||
|
local result = callback(row)
|
||||||
|
if result == 1 then break end
|
||||||
|
end
|
||||||
|
end}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Load all extension for outbound routes and
|
||||||
|
-- returns object which can be used instead real DBH object to build
|
||||||
|
-- dialplan for specific destination_number
|
||||||
|
local function preload_dialplan(dbh, domain_uuid, fields)
|
||||||
|
local hostname = fields and fields.hostname
|
||||||
|
if not hostname then
|
||||||
|
require "resources.functions.trim";
|
||||||
|
hostname = trim(api:execute("switchname", ""))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- try filter by context
|
||||||
|
local context = fields and fields.context
|
||||||
|
if context == '' then context = nil end
|
||||||
|
|
||||||
|
local dialplan = {}
|
||||||
|
dbh:query(select_outbound_dialplan_sql, {domain_uuid=domain_uuid, hostname=hostname}, function(route)
|
||||||
|
if context and context ~= route.dialplan_context then
|
||||||
|
-- skip dialplan for wrong contexts
|
||||||
|
return
|
||||||
|
end
|
||||||
|
dialplan[#dialplan + 1] = route
|
||||||
|
end)
|
||||||
|
|
||||||
|
return wrap_dbh(dialplan), dialplan
|
||||||
|
end
|
||||||
|
|
||||||
return setmetatable({
|
return setmetatable({
|
||||||
__self_test = self_test;
|
__self_test = self_test;
|
||||||
|
apply_vars = apply_vars;
|
||||||
|
preload_dialplan = preload_dialplan;
|
||||||
}, {__call = function(_, ...)
|
}, {__call = function(_, ...)
|
||||||
return outbound_route_to_bridge(...)
|
return outbound_route_to_bridge(...)
|
||||||
end})
|
end})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue