Move scripts to app/switch/resources/scripts

This commit is contained in:
markjcrane
2023-06-24 08:32:56 -06:00
parent 0d39719318
commit 88faf89219
199 changed files with 129 additions and 136 deletions
+48
View File
@@ -0,0 +1,48 @@
-- Part of FusionPBX
-- Copyright (C) 2013 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--include config.lua
require "resources.functions.config";
--get the argv values
script_name = argv[0];
app_name = argv[1];
--example use command
--luarun app.lua app_name 'a' 'b 123' 'c'
--for loop through arguments
arguments = "";
for key,value in pairs(argv) do
if (key > 1) then
arguments = arguments .. " '" .. value .. "'";
--freeswitch.consoleLog("notice", "[app.lua] argv["..key.."]: "..argv[key].."\n");
end
end
--route the request to the application
--freeswitch.consoleLog("notice", "["..app_name.."]".. scripts_dir .. "/app/" .. app_name .. "/index.lua\n");
loadfile(scripts_dir .. "/app/" .. app_name .. "/index.lua")(argv);
@@ -0,0 +1,272 @@
--set default variables
max_digits = 15;
digit_timeout = 5000;
debug["sql"] = true;
--general functions
require "resources.functions.trim";
--connect to the database
local Database = require "resources.functions.database";
dbh = Database.new('system');
--include json library
local json
if (debug["sql"]) then
json = require "resources.functions.lunajson"
end
local presence_in = require "resources.functions.presence_in"
--set the api
api = freeswitch.API();
--get the argv values
action = argv[2];
--get the session variables
if (session:ready()) then
session:answer();
session:sleep('1000');
end
--get the session variables
if (session:ready()) then
--general variables
domain_uuid = session:getVariable("domain_uuid");
domain_name = session:getVariable("domain_name");
context = session:getVariable("context");
uuid = session:get_uuid();
agent_authorized = session:getVariable("agent_authorized");
agent_action = session:getVariable("agent_action");
agent_id = session:getVariable("agent_id");
agent_name = session:getVariable("agent_name");
agent_password = session:getVariable("agent_password");
--set the sounds path for the language, dialect and voice
default_language = session:getVariable("default_language");
default_dialect = session:getVariable("default_dialect");
default_voice = session:getVariable("default_voice");
if (not default_language) then default_language = 'en'; end
if (not default_dialect) then default_dialect = 'us'; end
if (not default_voice) then default_voice = 'callie'; end
end
--set default as access denied
if (agent_authorized == nil or agent_authorized ~= 'true') then
agent_authorized = 'false';
end
--define the sounds directory
sounds_dir = session:getVariable("sounds_dir");
sounds_dir = sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice;
--get the agent_id from the caller
if (agent_id == nil and agent_name == nil) then
min_digits = 2;
max_digits = 20;
max_tries = 3;
agent_id = session:playAndGetDigits(min_digits, max_digits, max_tries, digit_timeout, "#", "phrase:voicemail_enter_id:#", "", "\\d+");
end
--get the pin number from the caller
if (agent_password == nil and agent_authorized ~= 'true') then
min_digits = 3;
max_digits = 20;
max_tries = 3;
agent_password = session:playAndGetDigits(min_digits, max_digits, max_tries, digit_timeout, "#", "phrase:voicemail_enter_pass:#", "", "\\d+");
end
--get the agent password
local params = {domain_uuid = domain_uuid, agent_id = agent_id, agent_name = agent_name}
local sql = "SELECT * FROM v_call_center_agents ";
sql = sql .. "WHERE domain_uuid = :domain_uuid ";
if (agent_id ~= nil) then
sql = sql .. "AND (agent_id = :agent_id or agent_name = :agent_id) ";
else
sql = sql .. "AND agent_name = :agent_name ";
end
if (agent_authorized ~= 'true') then
sql = sql .. "AND agent_password = :agent_password ";
params.agent_password = agent_password;
end
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[user status] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
--set the variables
agent_uuid = row.call_center_agent_uuid;
agent_name = row.agent_name;
agent_id = row.agent_id;
user_uuid = row.user_uuid;
--authorize the user
agent_authorized = 'true';
end);
--show the results
if (agent_id) then
freeswitch.consoleLog("notice", "[user status][login] agent id: " .. agent_id .. " authorized: " .. agent_authorized .. "\n");
end
if (agent_password and debug["password"]) then
freeswitch.consoleLog("notice", "[user status][login] agent password: " .. agent_password .. "\n");
end
--get the user_uuid
if (agent_authorized == 'true') then
--get the agent status from mod_callcenter
cmd = "callcenter_config agent get status "..agent_uuid.."";
freeswitch.consoleLog("notice", "[user status][login] "..cmd.."\n");
user_status = trim(api:executeString(cmd));
--get the user info
if (agent_action == nil) then
if (user_status == "Available") then
action = "logout";
status = 'Logged Out';
else
action = "login";
status = 'Available';
end
elseif (agent_action == "break") then
if (user_status == "On Break") then
action = "login";
status = 'Available';
else
action = "break";
status = 'On Break';
end
elseif (agent_action == "login") then
action = "login";
status = 'Available';
elseif (agent_action == "logout") then
action = "logout";
status = 'Logged Out';
end
--send a login or logout to mod_callcenter
cmd = "sched_api +5 none callcenter_config agent set status "..agent_uuid.." '"..status.."'";
freeswitch.consoleLog("notice", "[user status][login] "..cmd.."\n");
result = api:executeString(cmd);
--update the user status
if (user_uuid ~= nil and user_uuid ~= '') then
local sql = "SELECT user_status FROM v_users ";
sql = sql .. "WHERE user_uuid = :user_uuid ";
sql = sql .. "AND domain_uuid = :domain_uuid ";
local params = {user_uuid = user_uuid, domain_uuid = domain_uuid};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[call_center] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
--set the user_status in the users table
local sql = "UPDATE v_users SET ";
sql = sql .. "user_status = :status ";
sql = sql .. "WHERE user_uuid = :user_uuid ";
local params = {status = status, user_uuid = user_uuid};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[call_center] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params);
end);
end
--set the presence to terminated - turn the lamp off:
if (action == "logout" or action == "break") then
event = freeswitch.Event("PRESENCE_IN");
event:addHeader("proto", "agent");
event:addHeader("event_type", "presence");
event:addHeader("alt_event_type", "dialog");
event:addHeader("Presence-Call-Direction", "outbound");
event:addHeader("state", "Active (1 waiting)");
event:addHeader("from", agent_name.."@"..domain_name);
event:addHeader("login", agent_name.."@"..domain_name);
event:addHeader("unique-id", uuid);
event:addHeader("answer-state", "terminated");
event:fire();
event = freeswitch.Event("PRESENCE_IN");
event:addHeader("proto", "agent");
event:addHeader("event_type", "presence");
event:addHeader("alt_event_type", "dialog");
event:addHeader("Presence-Call-Direction", "outbound");
event:addHeader("state", "Active (1 waiting)");
event:addHeader("from", agent_id.."@"..domain_name);
event:addHeader("login", agent_id.."@"..domain_name);
event:addHeader("unique-id", uuid);
event:addHeader("answer-state", "terminated");
event:fire();
end
--set presence in - turn lamp on
if (action == "login") then
event = freeswitch.Event("PRESENCE_IN");
event:addHeader("proto", "agent");
event:addHeader("login", agent_name.."@"..domain_name);
event:addHeader("from", agent_name.."@"..domain_name);
event:addHeader("status", "Active (1 waiting)");
event:addHeader("rpid", "unknown");
event:addHeader("event_type", "presence");
event:addHeader("alt_event_type", "dialog");
event:addHeader("event_count", "1");
event:addHeader("unique-id", uuid);
event:addHeader("Presence-Call-Direction", "outbound");
event:addHeader("answer-state", "confirmed");
event:fire();
event = freeswitch.Event("PRESENCE_IN");
event:addHeader("proto", "agent");
event:addHeader("login", agent_id.."@"..domain_name);
event:addHeader("from", agent_id.."@"..domain_name);
event:addHeader("status", "Active (1 waiting)");
event:addHeader("rpid", "unknown");
event:addHeader("event_type", "presence");
event:addHeader("alt_event_type", "dialog");
event:addHeader("event_count", "1");
event:addHeader("unique-id", uuid);
event:addHeader("Presence-Call-Direction", "outbound");
event:addHeader("answer-state", "confirmed");
event:fire();
end
if (action == "login") then
blf_status = "false"
end
if string.find(agent_name, 'agent+', nil, true) ~= 1 then
presence_in.turn_lamp( blf_status,
'agent+'..agent_name.."@"..domain_name,
uuid
);
end
end
--unauthorized
if (agent_authorized == 'false') then
result = session:streamFile(sounds_dir.."/voicemail/vm-fail_auth.wav");
status = "Invalid ID or Password";
end
--set the status and presence
if (session:ready()) then
if (action == "login") then
session:execute("playback", sounds_dir.."/ivr/ivr-you_are_now_logged_in.wav");
end
if (action == "logout") then
session:execute("playback", sounds_dir.."/ivr/ivr-you_are_now_logged_out.wav");
end
if (action == "break") then
session:execute("playback", sounds_dir.."/ivr/ivr-thank_you.wav");
end
end
--send the status to the display
if (status ~= nil) then
reply = api:executeString("uuid_display "..uuid.." '"..status.."'");
end
--set the session sleep to give time to see the display
if (session:ready()) then
session:execute("sleep", "2000");
end
@@ -0,0 +1,19 @@
--subscribe to the events
events = freeswitch.EventConsumer("CUSTOM","avmd::beep");
--prepare the api object
api = freeswitch.API();
--get the events
for event in (function() return events:pop(1) end) do
--serialize the data for the console
--freeswitch.consoleLog("notice","event:" .. event:serialize("xml") .. "\n");
--freeswitch.consoleLog("notice","event:" .. event:serialize("json") .. "\n");
--get the uuid
local uuid = event:getHeader("Unique-ID");
freeswitch.consoleLog("[avmd] uuid: ", uuid .. "\n");
--end the call
reply = api:executeString("uuid_kill "..uuid.." NORMAL_CLEARING");
end
@@ -0,0 +1,271 @@
--
-- FusionPBX
-- Version: MPL 1.1
--
-- The contents of this file are subject to the Mozilla Public License Version
-- 1.1 (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
-- http://www.mozilla.org/MPL/
--
-- Software distributed under the License is distributed on an "AS IS" basis,
-- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-- for the specific language governing rights and limitations under the
-- License.
--
-- The Original Code is FusionPBX
--
-- The Initial Developer of the Original Code is
-- Mark J Crane <markjcrane@fusionpbx.com>
-- Copyright (C) 2019
-- the Initial Developer. All Rights Reserved.
--
-- Contributor(s):
-- Mark J Crane <markjcrane@fusionpbx.com>
--set the debug level
debug["sql"] = false;
--includes
local cache = require"resources.functions.cache";
local log = require"resources.functions.log"["call_block"];
--include json library
local json
if (debug["sql"]) then
json = require "resources.functions.lunajson";
end
--include functions
require "resources.functions.trim";
require "resources.functions.explode";
require "resources.functions.file_exists";
--get the variables
if (session:ready()) then
--session:setAutoHangup(false);
domain_uuid = session:getVariable("domain_uuid");
call_direction = session:getVariable("call_direction");
caller_id_name = session:getVariable("caller_id_name");
caller_id_number = session:getVariable("caller_id_number");
destination_number = session:getVariable("destination_number");
context = session:getVariable("context");
call_block = session:getVariable("call_block");
extension_uuid = session:getVariable("extension_uuid");
hold_music = session:getVariable("hold_music");
end
--set default variables
api = freeswitch.API();
--set the dialplan cache key
local call_block_cache_key = "call_block:" .. caller_id_number;
--get the cache
cached_value, err = cache.get(call_block_cache_key);
if (debug['cache']) then
if cached_value then
log.notice(call_block_cache_key.." source: cache");
elseif err ~= 'NOT FOUND' then
log.notice("error cache: " .. err);
end
end
--disable the cache
cached_value = nil;
--domain_uuid is null then use a random uuid to prevent an error and allow domain_uuid is null SQL to still run
if (domain_uuid == nil) then
domain_uuid = '74040b66-ca70-4a80-84b5-8cb2139c07dd';
end
--run call block one time
if (call_block == nil and call_block ~= 'true') then
--set the cache
if (not cached_value) then
--connect to the database
local Database = require "resources.functions.database";
dbh = Database.new('system');
--include json library
local json
if (debug["sql"]) then
json = require "resources.functions.lunajson";
end
--exits the script if we didn't connect properly
assert(dbh:connected());
--check to see if the call should be blocked
sql = "select * from v_call_block\n";
sql = sql .. "where (domain_uuid = :domain_uuid or domain_uuid is null) \n";
sql = sql .. "and call_block_enabled = 'true' \n";
sql = sql .. "and call_block_direction = :call_block_direction \n";
sql = sql .. "and ( \n";
sql = sql .. " (\n";
sql = sql .. " call_block_name = :call_block_name \n";
sql = sql .. " and ( \n";
sql = sql .. " '+' || call_block_country_code || call_block_number = :call_block_number \n";
sql = sql .. " or call_block_country_code || call_block_number = :call_block_number \n";
sql = sql .. " or call_block_number = :call_block_number \n";
sql = sql .. " ) \n";
sql = sql .. " ) \n";
sql = sql .. " or (\n";
sql = sql .. " call_block_name is null \n";
sql = sql .. " and ( \n";
sql = sql .. " '+' || call_block_country_code || call_block_number = :call_block_number \n";
sql = sql .. " or call_block_country_code || call_block_number = :call_block_number \n";
sql = sql .. " or call_block_number = :call_block_number \n";
sql = sql .. " ) \n";
sql = sql .. " ) \n";
sql = sql .. " or (call_block_name = :call_block_name and call_block_number is null) \n";
sql = sql .. ") \n";
if (extension_uuid == nil) then
sql = sql .. "and extension_uuid is null ";
else
sql = sql .. "and (extension_uuid is null or extension_uuid = :extension_uuid) ";
end
if (extension_uuid ~= nil) then
if (call_direction == 'inbound') then
params = {domain_uuid = domain_uuid, call_block_direction = call_direction, call_block_name = caller_id_name, call_block_number = caller_id_number, extension_uuid = extension_uuid};
end
if (call_direction == 'outbound') then
params = {domain_uuid = domain_uuid, call_block_direction = call_direction, call_block_name = caller_id_name, call_block_number = destination_number, extension_uuid = extension_uuid};
end
else
if (call_direction == 'inbound') then
params = {domain_uuid = domain_uuid, call_block_direction = call_direction, call_block_name = caller_id_name, call_block_number = caller_id_number};
end
if (call_direction == 'outbound') then
params = {domain_uuid = domain_uuid, call_block_direction = call_direction, call_block_name = caller_id_name, call_block_number = destination_number};
end
end
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[dialplan] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
local found = false;
dbh:query(sql, params, function(row)
found = true;
--get the values from the database
call_block_uuid = row.call_block_uuid;
call_block_app = row.call_block_app;
call_block_data = row.call_block_data;
call_block_count = row.call_block_count;
extension_uuid = row.extension_uuid;
--cached_value = domain_uuid..','..caller_id_number;
end);
--set call block default to false
call_block = false;
if (call_block_app ~= nil) then
call_block = true;
if (session:ready()) then
session:execute('set', 'call_block=true');
end
end
--call block action
if (call_block_app ~= nil and call_block_app == 'busy') then
if (session:ready()) then
session:execute("respond", '486');
session:execute('set', 'call_block_uuid='..call_block_uuid);
session:execute('set', 'call_block_app=busy');
freeswitch.consoleLog("notice", "[call_block] caller id number " .. caller_id_number .. " action: Busy\n");
end
end
if (call_block_app ~= nil and call_block_app == 'hold') then
if (session:ready()) then
session:execute('set', 'call_block_uuid='..call_block_uuid);
session:execute('set', 'call_block_app=hold');
session:execute("playback", hold_music);
freeswitch.consoleLog("notice", "[call_block] caller id number " .. caller_id_number .. " action: Hold\n");
end
end
if (call_block_app ~= nil and call_block_app == 'reject') then
if (session:ready()) then
session:execute("respond", '600');
session:execute('set', 'call_block_uuid='..call_block_uuid);
session:execute('set', 'call_block_app=reject');
freeswitch.consoleLog("notice", "[call_block] caller id number " .. caller_id_number .. " action: Reject\n");
end
end
if (call_block_app ~= nil and call_block_data ~= nil) then
if (call_block_app == 'extension') then
if (session:ready()) then
session:execute('set', 'call_block_uuid='..call_block_uuid);
session:execute('set', 'call_block_app='..call_block_app);
session:execute('set', 'call_block_data='..call_block_data);
session:execute("transfer", call_block_data..' XML '.. context);
freeswitch.consoleLog("notice", "[call_block] caller id number " .. caller_id_number .. " action: extension ".. call_block_data.."\n");
end
end
if (call_block_app == 'ivr') then
if (session:ready()) then
session:execute('set', 'call_block_uuid='..call_block_uuid);
session:execute('set', 'call_block_app='..call_block_app);
session:execute('set', 'call_block_data='..call_block_data);
session:execute("transfer", call_block_data..' XML '.. context);
freeswitch.consoleLog("notice", "[call_block] caller id number " .. caller_id_number .. " action: extension ".. call_block_data.."\n");
end
end
if (call_block_app == 'ring_group') then
if (session:ready()) then
session:execute('set', 'call_block_uuid='..call_block_uuid);
session:execute('set', 'call_block_app='..call_block_app);
session:execute('set', 'call_block_data='..call_block_data);
session:execute("transfer", call_block_data..' XML '.. context);
freeswitch.consoleLog("notice", "[call_block] caller id number " .. caller_id_number .. " action: extension ".. call_block_data.."\n");
end
end
if (call_block_app == 'voicemail') then
if (session:ready()) then
session:execute('set', 'call_block_uuid='..call_block_uuid);
session:execute('set', 'call_block_app='..call_block_app);
session:execute('set', 'call_block_data='..call_block_data);
session:execute("transfer", '*99'..call_block_data..' XML '.. context);
freeswitch.consoleLog("notice", "[call_block] caller id number " .. caller_id_number .. " action: voicemail *99".. call_block_data.."\n");
end
end
end
--update the call block count
if (call_block) then
sql = "update v_call_block ";
sql = sql .. "set call_block_count = :call_block_count ";
sql = sql .. "where call_block_uuid = :call_block_uuid ";
local params = {call_block_uuid = call_block_uuid, call_block_count = #call_block_count + 1};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[dialplan] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params);
end
--close the database connection
dbh:release();
--set the cache
if (cached_value ~= nil) then
local ok, err = cache.set(call_block_cache_key, cached_value, '3600');
end
if debug["cache"] then
if ok then
freeswitch.consoleLog("notice", "[call_block] " .. call_block_cache_key .. " stored in the cache\n");
else
freeswitch.consoleLog("warning", "[call_block] " .. call_block_cache_key .. " can not be stored in the cache: " .. tostring(err) .. "\n");
end
end
--send to the console
if (debug["cache"]) then
freeswitch.consoleLog("notice", "[call_block] " .. call_block_cache_key .. " source: database\n");
end
else
--send to the console
if (debug["cache"]) then
freeswitch.consoleLog("notice", "[call_block] " .. call_block_cache_key .. " source: cache\n");
end
end
end
@@ -0,0 +1,834 @@
-- conference_center/index.lua
-- Part of FusionPBX
-- Copyright (C) 2013 - 2021 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED AS ''IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--
-- Contributor(s):
-- Mark J Crane <markjcrane@fusionpbx.com>
-- Luis Daniel Lucio Quiroz <dlucio@okay.com.mx>
--set variables
flags = "";
max_tries = 3;
digit_timeout = 5000;
--debug
debug["sql"] = false;
--includes
require "resources.functions.config";
local Database = require "resources.functions.database";
local Settings = require "resources.functions.lazy_settings"
dbh = Database.new('system');
local settings = Settings.new(dbh, domain_name, domain_uuid);
--get the settings
session_enabled = settings:get('conference_center', 'session_enabled', 'boolean');
account_code_enabled = settings:get('conference_center', 'account_code_enabled', 'boolean');
http_protocol = settings:get('domain', 'http_protocol', 'text') or "https";
--include json library
local json
if (debug["sql"]) then
json = require "resources.functions.lunajson"
end
--prepare the api object
api = freeswitch.API();
--general functions
require "resources.functions.base64";
require "resources.functions.trim";
require "resources.functions.file_exists";
require "resources.functions.explode";
require "resources.functions.format_seconds";
require "resources.functions.mkdir";
--get the session variables
uuid = session:getVariable("uuid");
--answer the call
session:answer();
--get record_ext
record_ext = session:getVariable("record_ext");
if (not record_ext) then
record_ext = "wav";
end
--define a function to send email
function send_email(email, attachment, default_language, default_dialect)
--require the email address to send the email
if (string.len(email) > 2) then
--format the message length and date
--message_length_formatted = format_seconds(message_length);
--if (debug["info"]) then
-- freeswitch.consoleLog("notice", "[conference_center] message length: " .. message_length .. "\n");
--end
local conference_date_end = os.date("%A, %d %b %Y %I:%M %p");
--os.time();
--prepare the files
file_subject = scripts_dir.."/app/conference_center/resources/templates/"..default_language.."/"..default_dialect.."email_subject.tpl";
file_body = scripts_dir.."/app/conference_center/resources/templates/"..default_language.."/"..default_dialect.."/email_body.tpl";
if (not file_exists(file_subject)) then
file_subject = scripts_dir.."/app/conference_center/resources/templates/en/us/email_subject.tpl";
file_body = scripts_dir.."/app/conference_center/resources/templates/en/us/email_body.tpl";
end
--get the moderator_pin
local sql = "SELECT moderator_pin FROM v_conference_rooms WHERE conference_room_uuid = :conference_room_uuid";
local params = {conference_room_uuid = conference_room_uuid}
freeswitch.consoleLog("notice", "[voicemail] sql: " .. sql .. "; params:" .. json.encode(params) .. "\n");
dbh:query(sql, params, function(row)
moderator_pin = string.lower(row["moderator_pin"]);
end);
--get the link_address
link_address = http_protocol.."://"..domain_name..project_path;
--prepare the headers
headers = {}
headers["X-FusionPBX-Domain-UUID"] = domain_uuid;
headers["X-FusionPBX-Domain-Name"] = domain_name;
headers["X-FusionPBX-Call-UUID"] = 'na';
headers["X-FusionPBX-Email-Type"] = 'conference';
--remove quotes from caller id name and number
caller_id_name = caller_id_name:gsub("'", "&#39;");
caller_id_name = caller_id_name:gsub([["]], "&#34;");
caller_id_number = caller_id_number:gsub("'", "&#39;");
caller_id_number = caller_id_number:gsub([["]], "&#34;");
--prepare the subject
local f = io.open(file_subject, "r");
local subject = f:read("*all");
f:close();
subject = subject:gsub("${moderator_pin}", moderator_pin);
subject = subject:gsub("${conference_date_end}", conference_date_end);
--subject = subject:gsub("${conference_duration}", message_length_formatted);
subject = subject:gsub("${domain_name}", domain_name);
subject = subject:gsub("${link_address}", link_address);
subject = trim(subject);
subject = '=?utf-8?B?'..base64.enc(subject)..'?=';
--prepare the body
local f = io.open(file_body, "r");
local body = f:read("*all");
f:close();
body = body:gsub("${moderator_pin}", moderator_pin);
body = body:gsub("${conference_date_end}", conference_date_end);
body = body:gsub("${conference_uuid}", conference_session_uuid);
--body = body:gsub("${conference_duration}", message_length_formatted);
body = body:gsub("${domain_name}", domain_name);
body = body:gsub("${link_address}", link_address);
body = body:gsub(" ", "&nbsp;");
body = body:gsub("%s+", "");
body = body:gsub("&nbsp;", " ");
body = body:gsub("\n", "");
body = body:gsub("\n", "");
body = trim(body);
--send the email
if (string.len(attachment) > 4) then
send_mail(headers,
email,
email,
{subject, body},
attachment
);
else
send_mail(headers,
email,
email,
{subject, body}
);
end
if (debug["info"]) then
freeswitch.consoleLog("notice", "[voicemail] cmd: " .. email .. " " .. subject .. " " .. body .. "\n");
end
end
end
--define the session hangup
function session_hangup_hook()
--get the session variables
conference_session_detail_uuid = api:executeString("create_uuid");
--conference_name = session:getVariable("conference_name");
conference_session_uuid = session:getVariable("conference_uuid");
--conference_recording = session:getVariable("conference_recording");
conference_moderator = session:getVariable("conference_moderator");
default_language = session:getVariable("default_language");
default_dialect = session:getVariable("default_dialect");
--recording = session:getVariable("recording");
domain_name = session:getVariable("domain_name");
--set the end epoch
end_epoch = os.time();
--connect to the database
dbh = Database.new('system');
--get the conference sessions
if (conference_session_uuid) then
local sql = [[SELECT count(*) as num_rows
FROM v_conference_sessions
WHERE conference_session_uuid = :conference_session_uuid]];
local params = {conference_session_uuid = conference_session_uuid};
dbh:query(sql, params, function(row)
num_rows = string.lower(row["num_rows"]);
end);
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[conference center] SQL: " .. sql .. "; params:" .. json.encode(params) .. "; Rows: "..num_rows.."\n");
end
if (tonumber(num_rows) == 0) then
local sql = {}
table.insert(sql, "INSERT INTO v_conference_sessions ");
table.insert(sql, "(");
table.insert(sql, "conference_session_uuid, ");
table.insert(sql, "domain_uuid, ");
table.insert(sql, "meeting_uuid, ");
--if (conference_recording) then
-- table.insert(sql, "recording, ");
--end
--if (wait_mod) then
-- table.insert(sql, "wait_mod, ");
--end
--table.insert(sql, "start_epoch, ");
table.insert(sql, "profile ");
table.insert(sql, ") ");
table.insert(sql, "VALUES ");
table.insert(sql, "( ");
table.insert(sql, ":conference_session_uuid, ");
table.insert(sql, ":domain_uuid, ");
table.insert(sql, ":meeting_uuid, ");
--if (conference_recording) then
-- table.insert(sql, ":conference_recording, ");
--end
--if (wait_mod) then
-- table.insert(sql, ":wait_mod, ");
--end
--table.insert(sql, ":start_epoch, ");
table.insert(sql, ":profile ");
table.insert(sql, ") ");
sql = table.concat(sql, "\n");
local params = {
conference_session_uuid = conference_session_uuid;
domain_uuid = domain_uuid;
meeting_uuid = conference_room_uuid;
-- conference_recording = conference_recording;
-- wait_mod = wait_mod;
-- start_epoch = start_epoch;
profile = profile;
};
dbh:query(sql, params);
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[conference center] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
end
end
--add the conference sessions details
if (conference_session_uuid) then
local sql = {}
table.insert(sql, "INSERT INTO v_conference_session_details ");
table.insert(sql, "(");
table.insert(sql, "conference_session_detail_uuid, ");
table.insert(sql, "domain_uuid, ");
table.insert(sql, "conference_session_uuid, ");
table.insert(sql, "meeting_uuid, ");
table.insert(sql, "username, ");
table.insert(sql, "caller_id_name, ");
table.insert(sql, "caller_id_number, ");
table.insert(sql, "network_addr, ");
table.insert(sql, "uuid, ");
if (conference_moderator) then
table.insert(sql, "moderator, ");
end
table.insert(sql, "start_epoch, ");
table.insert(sql, "end_epoch ");
table.insert(sql, ") ");
table.insert(sql, "VALUES ");
table.insert(sql, "( ");
table.insert(sql, ":conference_session_detail_uuid, ");
table.insert(sql, ":domain_uuid, ");
table.insert(sql, ":conference_session_uuid, ");
table.insert(sql, ":meeting_uuid, ");
table.insert(sql, ":username, ");
table.insert(sql, ":caller_id_name, ");
table.insert(sql, ":caller_id_number, ");
table.insert(sql, ":network_addr, ");
table.insert(sql, ":uuid, ");
if (conference_moderator) then
table.insert(sql, ":conference_moderator, ");
end
table.insert(sql, ":start_epoch, ");
table.insert(sql, ":end_epoch ");
table.insert(sql, ") ");
sql = table.concat(sql, "\n");
local params = {
conference_session_detail_uuid = conference_session_detail_uuid;
domain_uuid = domain_uuid;
conference_session_uuid = conference_session_uuid;
meeting_uuid = conference_room_uuid;
username = username;
caller_id_name = caller_id_name;
caller_id_number = caller_id_number;
network_addr = network_addr;
uuid = uuid;
conference_moderator = conference_moderator;
start_epoch = start_epoch;
end_epoch = end_epoch;
};
dbh:query(sql, params);
end
--if the conference is empty
if (conference_session_uuid) then
cmd = "conference "..conference_room_uuid.."@"..domain_name.." xml_list";
result = trim(api:executeString(cmd));
if (string.sub(result, -9) == "not found") then
--get the conference start_epoch
local sql = [[SELECT start_epoch
FROM v_conference_session_details
WHERE conference_session_uuid = :conference_session_uuid
ORDER BY start_epoch ASC
LIMIT 1]];
local params = {conference_session_uuid = conference_session_uuid};
dbh:query(sql, params, function(row)
start_epoch = string.lower(row["start_epoch"]);
end);
--freeswitch.consoleLog("notice", "[conference center] <conference_start_epoch> sql: " .. sql .. "; params:" .. json.encode(params) .. "\n");
--set the conference_recording
conference_recording = recordings_dir.."/archive/"..os.date("%Y", start_epoch).."/"..os.date("%b", start_epoch).."/"..os.date("%d", start_epoch) .."/"..conference_session_uuid .. "." .. record_ext;
freeswitch.consoleLog("notice", "[conference center] conference_recording: "..conference_recording.."\n");
--conference has ended set the end_epoch
local sql = {}
table.insert(sql, "update v_conference_sessions set ");
table.insert(sql, "recording = :conference_recording, ");
table.insert(sql, "start_epoch = :start_epoch, ");
table.insert(sql, "end_epoch = :end_epoch ");
table.insert(sql, "where conference_session_uuid = :conference_session_uuid ");
sql = table.concat(sql, "\n");
local params = {
conference_recording = conference_recording;
start_epoch = start_epoch;
end_epoch = end_epoch;
conference_session_uuid = conference_session_uuid;
};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[conference center] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params);
--convert the wav to an mp3
if (record == "true") then
--cmd = "sox "..conference_recording..".wav -r 16000 -c 1 "..conference_recording..".mp3";
cmd = "/usr/bin/lame -b 32 --resample 8 -a "..conference_recording..".wav "..conference_recording..".mp3";
freeswitch.consoleLog("notice", "[conference center] cmd: " .. cmd .. "\n");
os.execute(cmd);
--if (file_exists(conference_recording..".mp3")) then
-- cmd = "rm "..conference_recording..".wav";
-- os.execute(cmd);
--end
end
end
end
--close the database connection
if (conference_session_uuid) then
dbh:release();
end
end
--make sure the session is ready
if (session:ready()) then
--answer the call
session:preAnswer();
--set the session sleep
session:sleep(1000);
--get session variables
sounds_dir = session:getVariable("sounds_dir");
hold_music = session:getVariable("hold_music");
domain_name = session:getVariable("domain_name");
pin_number = session:getVariable("pin_number");
domain_uuid = session:getVariable("domain_uuid");
destination_number = session:getVariable("destination_number");
caller_id_number = session:getVariable("caller_id_number");
--freeswitch.consoleLog("notice", "[conference center] destination_number: " .. destination_number .. "\n");
--freeswitch.consoleLog("notice", "[conference center] caller_id_number: " .. caller_id_number .. "\n");
--add the domain name to the recordings directory
recordings_dir = recordings_dir .. "/"..domain_name;
--set the sounds path for the language, dialect and voice
default_language = session:getVariable("default_language");
default_dialect = session:getVariable("default_dialect");
default_voice = session:getVariable("default_voice");
if (not default_language) then default_language = 'en'; end
if (not default_dialect) then default_dialect = 'us'; end
if (not default_voice) then default_voice = 'callie'; end
--get the domain_uuid
if (domain_name ~= nil and domain_uuid == nil) then
local sql = "SELECT domain_uuid FROM v_domains ";
sql = sql .. "WHERE domain_name = :domain_name ";
local params = {domain_name = domain_name};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[conference center] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(rows)
domain_uuid = string.lower(rows["domain_uuid"]);
end);
end
--conference center details
local sql = [[SELECT * FROM v_conference_centers
WHERE domain_uuid = :domain_uuid
AND conference_center_extension = :destination_number]];
local params = {domain_uuid = domain_uuid, destination_number = destination_number};
dbh:query(sql, params, function(row)
conference_center_uuid = string.lower(row["conference_center_uuid"]);
conference_center_greeting = row["conference_center_greeting"];
end);
if (conference_center_greeting == '') then
conference_center_greeting = sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/conference/conf-pin.wav";
end
--connect to the switch database
local dbh_switch = Database.new('switch')
--check if someone has already joined the conference
local_hostname = trim(api:execute("switchname", ""));
freeswitch.consoleLog("notice", "[conference center] local_hostname is " .. local_hostname .. "\n");
sql = "SELECT hostname FROM channels WHERE application = 'conference' "
.. "AND dest = :destination_number AND cid_num <> :caller_id_number LIMIT 1";
params = {destination_number = destination_number, caller_id_number = caller_id_number};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[conference center] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh_switch:query(sql, params, function(rows)
conference_hostname = rows["hostname"];
end);
--close the database connection
dbh_switch:release();
--if conference hosntame exist, then we bridge there
if (conference_hostname ~= nil) then
freeswitch.consoleLog("notice", "[conference center] conference_hostname is " .. conference_hostname .. "\n");
if (conference_hostname ~= local_hostname) then
session:execute("bridge","sofia/internal/" .. destination_number .. "@" .. domain_name .. ";fs_path=sip:" .. conference_hostname);
end
end
--call not bridged, so we answer
session:answer();
--set the hangup hook function
if (session_enabled == 'true') then
freeswitch.consoleLog("notice", "[conference center] session_enabled: true\n");
session:setHangupHook("session_hangup_hook");
else
freeswitch.consoleLog("notice", "[conference center] session_enabled: false\n");
end
--sounds
enter_sound = "tone_stream://v=-20;%(100,1000,100);v=-20;%(90,60,440);%(90,60,620)";
exit_sound = "tone_stream://v=-20;%(90,60,620);/%(90,60,440)";
--get the variables
username = session:getVariable("username");
caller_id_name = session:getVariable("caller_id_name");
caller_id_number = session:getVariable("caller_id_number");
callee_id_name = session:getVariable("callee_id_name");
callee_id_number = session:getVariable("callee_id_number");
dialplan = session:getVariable("dialplan");
network_addr = session:getVariable("network_addr");
uuid = session:getVariable("uuid");
--context = session:getVariable("context");
chan_name = session:getVariable("chan_name");
--define the function get_pin_number
function get_pin_number(domain_uuid, prompt_audio_file)
--if the pin number is provided then require it
if (not pin_number) then
min_digits = 2;
max_digits = 20;
max_tries = 1;
digit_timeout = 5000;
pin_number = session:playAndGetDigits(min_digits, max_digits, max_tries, digit_timeout, "#", prompt_audio_file, "", "\\d+");
--remove non numerics
pin_number = pin_number:gsub("%p","")
end
--use the pin_number to find the conference room
if (pin_number ~= "") then
local sql = [[SELECT * FROM v_conference_rooms
WHERE domain_uuid = :domain_uuid
AND (moderator_pin = :pin_number or participant_pin = :pin_number)
AND enabled = 'true'
AND (
( start_datetime <> '' AND start_datetime is not null AND start_datetime <= :timestamp ) OR
( start_datetime = '' OR start_datetime is null )
)
AND (
( stop_datetime <> '' AND stop_datetime is not null AND stop_datetime > :timestamp ) OR
( stop_datetime = '' OR stop_datetime is null )
) ]];
local params = {
domain_uuid = domain_uuid;
pin_number = pin_number;
timestamp = os.date("%Y-%m-%d %X");
};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[conference center] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
conference_room_uuid = string.lower(row["conference_room_uuid"]);
end);
end
--if the conference room was not found then return nil
if (conference_room_uuid == nil) then
return nil;
else
return pin_number;
end
end
--get the pin
pin_number = session:getVariable("pin_number");
pin_number = get_pin_number(domain_uuid, conference_center_greeting);
if (pin_number == nil) then
pin_number = get_pin_number(domain_uuid, conference_center_greeting);
end
if (pin_number == nil) then
session:streamFile(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/conference/conf-bad-pin.wav");
pin_number = get_pin_number(domain_uuid, conference_center_greeting);
end
if (pin_number == nil) then
session:streamFile(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/conference/conf-bad-pin.wav");
pin_number = get_pin_number(domain_uuid, conference_center_greeting);
end
if (pin_number ~= nil) then
local sql = [[SELECT * FROM v_conference_rooms
WHERE domain_uuid = :domain_uuid
AND conference_center_uuid = :conference_center_uuid
AND (moderator_pin = :pin_number or participant_pin = :pin_number)
AND enabled = 'true'
]];
local params = {
domain_uuid = domain_uuid;
conference_center_uuid = conference_center_uuid;
pin_number = pin_number;
};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[conference center] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
conference_room_uuid = string.lower(row["conference_room_uuid"]);
conference_room_name = string.lower(row["conference_room_name"]);
--meeting_uuid = string.lower(row["meeting_uuid"]);
record = string.lower(row["record"]);
profile = string.lower(row["profile"]);
max_members = row["max_members"];
wait_mod = row["wait_mod"];
moderator_endconf = row["moderator_endconf"];
moderator_pin = row["moderator_pin"];
participant_pin = row["participant_pin"];
announce_name = row["announce_name"];
announce_count = row["announce_count"];
announce_recording = row["announce_recording"];
mute = row["mute"];
sounds = row["sounds"];
created = row["created"];
created_by = row["created_by"];
enabled = row["enabled"];
description = row["description"];
return pin_number;
end);
freeswitch.consoleLog("INFO","conference_room_uuid: " .. conference_room_uuid .. "\n");
end
--set the member type
if (pin_number == moderator_pin) then
member_type = "moderator";
end
if (pin_number == participant_pin) then
member_type = "participant";
end
--get the account code
if (account_code_enabled == 'true' and member_type == 'moderator') then
--request the account code
min_digits = 2;
max_digits = 20;
account_code = session:playAndGetDigits(min_digits, max_digits, max_tries, digit_timeout, "#", "phrase:voicemail_enter_id:#", "", "\\d+");
--user_id = session:playAndGetDigits(min_digits, max_digits, max_tries, digit_timeout, "#", sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/ivr/ivr-please_enter_extension_followed_by_pound.wav", "", "\\d+");
--update the account code
local sql = {}
table.insert(sql, "update v_conference_rooms ");
table.insert(sql, "set account_code = :account_code ");
table.insert(sql, "where conference_center_uuid = :conference_center_uuid ");
sql = table.concat(sql, "\n");
local params = {
conference_center_uuid = conference_center_uuid;
account_code = account_code;
};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[conference center] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params);
end
--close the database connection
dbh:release();
--set the conference_room_uuid
if (conference_room_uuid) then
session:setVariable("conference_room_uuid", conference_room_uuid);
end
--set the conference_room_name
if (conference_room_name) then
session:setVariable("conference_room_name", conference_room_name);
end
--set the meeting uuid
if (meeting_uuid) then
session:setVariable("meeting_uuid", meeting_uuid);
end
--check if the conference exists
if (conference_room_uuid and domain_name) then
cmd = "conference "..conference_room_uuid.."@"..domain_name.." xml_list";
result = trim(api:executeString(cmd));
if (string.sub(result, -9) == "not found") then
conference_exists = false;
else
conference_exists = true;
end
end
--check if the conference is locked
if (string.find(result, [[locked="true"]]) == nil) then
conference_locked = false;
else
conference_locked = true;
end
--set a conference parameter
if (max_members ~= nil) then
if (tonumber(max_members) > 0) then
--max members must be 2 or more
session:execute("set","conference_max_members="..max_members);
if (conference_exists) then
cmd = "conference "..conference_room_uuid.."@"..domain_name.." get count";
count = trim(api:executeString(cmd));
if (count ~= nil) then
if (tonumber(count) >= tonumber(max_members)) then
session:execute("playback", sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/conference/conf-locked.wav");
session:hangup("CALL_REJECTED");
end
end
end
end
end
--announce the caller
if (conference_locked) then
announce = "false";
end
if (announce_name == "true") then
--prompt for the name of the caller
session:execute("playback", sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/ivr/ivr-say_name.wav");
session:execute("playback", "tone_stream://v=-7;%%(500,0,500.0)");
--record the response
max_len_seconds = 5;
silence_threshold = "500";
silence_secs = "3";
session:recordFile(temp_dir:gsub("\\","/") .. "/conference-"..uuid..".wav", max_len_seconds, silence_threshold, silence_secs);
end
--play a message that the conference is being a recorded
--if (record == "true") then
--session:execute("playback", sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/ivr/ivr-recording_started.wav");
--end
--wait for moderator
if (wait_mod == "true") then
if (conference_exists) then
--continue
else
if (member_type == "participant") then
profile = "wait-mod";
end
end
end
--set the exit sound
if (sounds == "true") then
session:execute("set","conference_exit_sound="..exit_sound);
else
session:execute("set","conference_enter_sound=none");
session:execute("set","conference_exit_sound=none");
end
--set flags and moderator controls
if (wait_mod == "true") then
if (member_type == "participant") then
flags = flags .. "wait-mod";
end
end
if (mute == "true") then
if (member_type == "participant") then
flags = flags .. "|mute";
end
end
if (member_type == "moderator") then
--set as the moderator
flags = flags .. "|moderator";
--when the moderator leaves end the conference
if (moderator_endconf == "true") then
flags = flags .. "|endconf";
end
end
--get the conference xml_list
cmd = "conference "..conference_room_uuid.."@"..domain_name.." xml_list";
freeswitch.consoleLog("INFO","" .. cmd .. "\n");
result = trim(api:executeString(cmd));
--get the content to the <conference> tag
result = string.match(result,[[<conference (.-)>]],1);
--get the uuid out of the xml tag contents
if (result ~= nil) then
conference_session_uuid = string.match(result,[[uuid="(.-)"]],1);
end
--log entry
if (conference_session_uuid ~= nil) then
freeswitch.consoleLog("INFO","conference_session_uuid: " .. conference_session_uuid .. "\n");
end
--set the start epoch
start_epoch = os.time();
--set the recording variable
if (conference_session_uuid ~= nil) then
if (record == "true") then
recordings_dir_2 = recordings_dir.."/archive/"..os.date("%Y", start_epoch).."/"..os.date("%b", start_epoch).."/"..os.date("%d", start_epoch);
mkdir(recordings_dir_2);
recording = recordings_dir_2.."/"..conference_session_uuid;
session:execute("set","recording="..recording);
session:execute("set","conference_session_uuid="..conference_session_uuid);
end
end
--record the conference
if (announce_recording == "true") then
if (record == "true") then
--play a message that the conference is being a recorded
session:execute("playback", sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/ivr/ivr-recording_started.wav");
--play a message that the conference is being a recorded
--cmd = "conference "..meeting_uuid.."@"..domain_name.." play "..sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/ivr/ivr-recording_started.wav";
--freeswitch.consoleLog("notice", "[conference center] ".. cmd .."\n");
--response = api:executeString(cmd);
end
end
--announce the caller
if (announce_name == "true") then
--announce the caller - play the recording
cmd = "conference "..conference_room_uuid.."@"..domain_name.." play " .. temp_dir:gsub("\\", "/") .. "/conference-"..uuid..".wav";
--freeswitch.consoleLog("notice", "[conference center] ".. cmd .."\n");
response = api:executeString(cmd);
--play has entered the conference
cmd = "conference "..conference_room_uuid.."@"..domain_name.." play "..sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/conference/conf-has_joined.wav";
--freeswitch.consoleLog("notice", "[conference center] ".. cmd .."\n");
response = api:executeString(cmd);
else
if (not conference_locked) then
if (sounds == "true") then
cmd = "conference "..conference_room_uuid.."@"..domain_name.." play "..enter_sound;
response = api:executeString(cmd);
end
end
end
--get the conference member count
cmd = "conference "..conference_room_uuid.."@"..domain_name.." list count";
--freeswitch.consoleLog("notice", "[conference center] cmd: ".. cmd .."\n");
member_count = api:executeString(cmd);
if (string.sub(trim(member_count), -9) == "not found") then
member_count = "0";
end
--play member count
if (announce_count == "true") then
if (member_count == "1") then
--there is one other member in this conference
session:execute("playback", sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/conference/conf-one_other_member_conference.wav");
elseif (member_count == "0") then
--conference profile defines the alone sound file
else
--say the count
session:execute("say", default_language.." number pronounced "..member_count);
--members in this conference
session:execute("playback", sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/conference/conf-members_in_conference.wav");
end
end
--record the conference
if (record == "true") then
if (wait_mod == "true" and member_type ~= "moderator") then
--don't start recording yet
else
cmd="sched_api +3 none lua "..scripts_dir.."/app/conference_center/resources/scripts/start_recording.lua "..conference_room_uuid.." "..domain_name.." "..record_ext;
api:executeString(cmd);
end
end
--use default profile if not provided
if (profile == null) then
profile = 'default';
end
--send the call to the conference
cmd = conference_room_uuid.."@"..domain_name.."@"..profile.."+flags{".. flags .."}";
freeswitch.consoleLog("INFO","[conference center] conference " .. cmd .. "\n");
session:execute("conference", cmd);
end
@@ -0,0 +1,16 @@
--get the argv values
script_name = argv[0];
--options all, last, non_moderator, member_id
data = argv[1];
--prepare the api object
api = freeswitch.API();
--get the session variables
conference_name = session:getVariable("conference_name");
--send the conferenc mute command
cmd = "conference " .. conference_name .. " mute " .. data;
response = api:executeString(cmd);
@@ -0,0 +1,15 @@
--options are true and false and toggle
data = argv[1];
if (data == "toggle") then
data = session:getVariable("hand_raised")
if(data == "true")then
session:setVariable("hand_raised","false")
else
session:setVariable("hand_raised","true")
end
elseif(data == "true")then
session:setVariable("hand_raised","true")
elseif(data == "false")then
session:setVariable("hand_raised","false")
end
@@ -0,0 +1,52 @@
--get the scripts directory and include the config.lua
require "resources.functions.config";
--additional includes
require "resources.functions.file_exists";
require "resources.functions.trim";
require "resources.functions.mkdir";
--get the argv values
script_name = argv[0];
--options all, last, non_moderator, member_id
meeting_uuid = argv[1];
domain_name = argv[2];
record_ext = argv[3];
--prepare the api object
api = freeswitch.API();
--check if the conference exists
cmd = "conference "..meeting_uuid.."@"..domain_name.." xml_list";
freeswitch.consoleLog("INFO","" .. cmd .. "\n");
result = trim(api:executeString(cmd));
if (string.sub(result, -9) == "not found") then
conference_exists = false;
else
conference_exists = true;
end
--start the recording
if (conference_exists) then
--get the conference session uuid
result = string.match(result,[[<conference (.-)>]],1);
conference_session_uuid = string.match(result,[[uuid="(.-)"]],1);
freeswitch.consoleLog("INFO","[start-recording] conference_session_uuid: " .. conference_session_uuid .. "\n");
--get the current time
start_epoch = os.time();
--add the domain name to the recordings directory
recordings_dir = recordings_dir .. "/"..domain_name;
recordings_dir = recordings_dir.."/archive/"..os.date("%Y", start_epoch).."/"..os.date("%b", start_epoch).."/"..os.date("%d", start_epoch);
mkdir(recordings_dir);
recording = recordings_dir.."/"..conference_session_uuid;
--send a command to record the conference
if (not file_exists(recording.."."..record_ext)) then
cmd = "conference "..meeting_uuid.."@"..domain_name.." record "..recording.."."..record_ext;
freeswitch.consoleLog("notice", "[start-recording] cmd: " .. cmd .. "\n");
response = api:executeString(cmd);
end
end
@@ -0,0 +1,16 @@
--get the argv values
script_name = argv[0];
--options all, last, non_moderator, member_id
data = argv[1];
--prepare the api object
api = freeswitch.API();
--get the session variables
conference_name = session:getVariable("conference_name");
--send the conferenc mute command
cmd = "conference " .. conference_name .. " unmute " .. data;
response = api:executeString(cmd);
@@ -0,0 +1,7 @@
<font face="arial">
<p>The conference moderator pin is ${moderator_pin} and it ended on ${conference_date_end}.</p>
<p>You can download your conference recording by clicking <a href='${link_address}/app/conference_centers/conference_session_details.php?uuid=${conference_uuid}'>here</a>.</p>
<hr noshade="noshade" size="1"/>
Moderator PIN: ${moderator_pin}
Ended: ${conference_date_end}<br/>
</font>
@@ -0,0 +1 @@
New recording available for conference ${moderator_pin} ${conference_date_end}.
@@ -0,0 +1,107 @@
-- FusionPBX
-- Version: MPL 1.1
-- The contents of this file are subject to the Mozilla Public License Version
-- 1.1 (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
-- http://www.mozilla.org/MPL/
-- Software distributed under the License is distributed on an "AS IS" basis,
-- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-- for the specific language governing rights and limitations under the
-- License.
-- The Original Code is FusionPBX
-- The Initial Developer of the Original Code is
-- Mark J Crane <markjcrane@fusionpbx.com>
-- Portions created by the Initial Developer are Copyright (C) 2014
-- the Initial Developer. All Rights Reserved.
--add functions
require "resources.functions.file_exists";
require "resources.functions.trim";
--set the api object
api = freeswitch.API();
--windows (/ad show only directories)
--dir "C:\program files\fusionpbx" /b
--unix
-- dir /usr/local/freeswitch/scripts -1
--set local variables
uuid = session:getVariable("uuid");
context = session:getVariable("context");
destination_number = session:getVariable("destination_number");
call_direction = session:getVariable("call_direction");
domain_name = session:getVariable("domain_name");
--determine the call direction
if (call_direction == nil) then
--get the call directory
if (context == "public") then
call_direction = "inbound";
else
if (string.sub(context, 0, 9) == "outbound@") then
call_direction = "outbound";
else
if (string.len(destination_number) > 6) then
call_direction = "outbound";
else
call_direction = "local";
end
end
end
--set the call direction as a session variable
session:setVariable("call_direction", call_direction);
--freeswitch.consoleLog("notice", "[app:dialplan] set call_direction " .. call_direction .. "\n");
end
--determine the directory to include
if (context == "public") then
dialplan_dir = "inbound";
else
if (string.sub(context, 0, 9) == "outbound@") then
dialplan_dir = "outbound";
else
if (string.len(destination_number) > 6) then
dialplan_dir = "outbound";
else
dialplan_dir = "local";
end
end
end
--include before
result = assert (api:executeString("dir " ..scripts_dir.."/app/dialplan/resources/before /b -1"));
for file in result:lines() do
if (string.sub(file, -4) == ".lua") then
if file_exists(scripts_dir.."/app/dialplan/resources/before/"..file) then
dofile(scripts_dir.."/app/dialplan/resources/before/"..file);
end
--freeswitch.consoleLog("notice", "[app:dialplan] lua: before/" .. file .. "\n");
end
end
--include the dialplans
result = assert (api:executeString("dir " ..scripts_dir.."/app/dialplan/resources/"..dialplan_dir.." /b -1"));
for file in result:lines() do
if (string.sub(file, -4) == ".lua") then
if file_exists(scripts_dir.."/app/dialplan/resources/"..dialplan_dir.."/"..file) then
dofile(scripts_dir.."/app/dialplan/resources/"..dialplan_dir.."/"..file);
end
--freeswitch.consoleLog("notice", "[app:dialplan] lua: "..dialplan_dir.."/" .. file .. "\n");
end
end
--include after
result = assert (api:executeString("dir " ..scripts_dir.."/app/dialplan/resources/after /b -1"));
for file in result:lines() do
if (string.sub(file, -4) == ".lua") then
if file_exists(scripts_dir.."/app/dialplan/resources/after/"..file) then
dofile(scripts_dir.."/app/dialplan/resources/after/"..file);
end
--freeswitch.consoleLog("notice", "[app:dialplan] lua: after/" .. file .. "\n");
end
end
@@ -0,0 +1,87 @@
--set the user_exists to true or false
if (context ~= "public") then
--set the default
record_ext = "wav";
--get record_ext
if (session:getVariable("record_ext")) then
record_ext = session:getVariable("record_ext");
end
--set the recording path
path = recordings_dir
if (domain_count > 1) then
path = path.."/"..domain_name;
end
path = path.."/archive/"..(os.date("%Y")).."/"..(os.date("%b")).."/"..(os.date("%d"));
--add functions
require "resources.functions.mkdir";
--make sure the recordings directory exists
mkdir(path);
--check whether to record the to user
if (user_exists == "true") then
if (session:ready()) then
--get user_variable -> record
cmd = "user_data ".. destination_number .."@"..domain_name.." var user_record";
user_record = trim(api:executeString(cmd));
--freeswitch.consoleLog("notice", "[app:dialplan] " .. cmd .. "\n");
--set the record_session variable
--uuid_record,<uuid> [start|stop|mask|unmask] <path> [<limit>],Record session audio,mod_commands
if (user_record == "all") then
record_session = true;
end
if (user_record == "inbound" and call_direction == "inbound") then
record_session = true;
end
if (user_record == "outbound" and call_direction == "outbound") then
record_session = true;
end
if (user_record == "local" and call_direction == "local") then
record_session = true;
end
end
end
--check whether to record the from user
if (not record_session) then
--get the from user
sip_from_user = session:getVariable("sip_from_user");
sip_from_uri = session:getVariable("sip_from_uri");
sip_from_host = session:getVariable("sip_from_host");
--get user exists true or false
cmd = "user_exists id ".. sip_from_user .." "..sip_from_host;
user_exists = trim(api:executeString(cmd));
--get user_variable -> record
cmd = "user_data ".. sip_from_user .."@"..sip_from_host.." var user_record";
freeswitch.consoleLog("notice", "[app:dialplan] " .. cmd .. "\n");
user_record = trim(api:executeString(cmd));
--set the record_session variable
--uuid_record,<uuid> [start|stop|mask|unmask] <path> [<limit>],Record session audio,mod_commands
if (user_record == "all") then
record_session = true;
end
if (user_record == "inbound" and call_direction == "inbound") then
record_session = true;
end
if (user_record == "outbound" and call_direction == "outbound") then
record_session = true;
end
if (user_record == "local" and call_direction == "local") then
record_session = true;
end
end
--record the session
if (record_session) then
cmd = "uuid_record "..uuid.." start "..path.."/"..uuid.."."..record_ext;
session:execute("set", "api_on_answer="..cmd);
--freeswitch.consoleLog("notice", "[app:dialplan] "..cmd.."\n");
end
end
@@ -0,0 +1,9 @@
--set the user_exists to true or false
if (context ~= "public") then
if (user_exists == nil) then
--get user exists true or false
cmd = "user_exists id ".. destination_number .." "..domain_name;
user_exists = trim(api:executeString(cmd));
--freeswitch.consoleLog("notice", "[app:dialplan] "..cmd.." user_exists: "..user_exists.."\n");
end
end
@@ -0,0 +1,116 @@
-- FusionPBX
-- Version: MPL 1.1
-- The contents of this file are subject to the Mozilla Public License Version
-- 1.1 (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
-- http://www.mozilla.org/MPL/
-- Software distributed under the License is distributed on an "AS IS" basis,
-- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-- for the specific language governing rights and limitations under the
-- License.
-- The Original Code is FusionPBX
-- The Initial Developer of the Original Code is
-- Mark J Crane <markjcrane@fusionpbx.com>
-- Portions created by the Initial Developer are Copyright (C) 2014-2019
-- the Initial Developer. All Rights Reserved.
--set defaults
expire = {}
expire["get_domain"] = "3600";
source = "";
--include cache library
local cache = require "resources.functions.cache"
--get the variables
local destination_number = session:getVariable("destination_number");
--remove the plus if it exists
if (string.sub(destination_number, 0, 1) == "+") then
destination_number = string.sub(destination_number, 2, (string.len(destination_number)));
end
--connect to the database
require "resources.functions.database_handle";
dbh = database_handle('system');
--get the cache
if (cache.support() and destination_number) then
local key, err = "app:dialplan:inbound:get_domain:" .. destination_number;
cache, err = cache.get(key);
end
--get the ring group destinations
if (cache == "-ERR NOT FOUND") then
sql = "SELECT d.domain_uuid, d.domain_name, n.destination_number, n.destination_context "
sql = sql .. "FROM v_destinations as n, v_domains as d "
sql = sql .. "WHERE n.destination_number = '"..destination_number.."' "
sql = sql .. "AND n.destination_type = 'inbound' "
sql = sql .. "AND n.domain_uuid = d.domain_uuid "
--freeswitch.consoleLog("notice", "SQL:" .. sql .. "\n");
dbh:query(sql, function(row)
--set the local variables
domain_uuid = row.domain_uuid;
domain_name = row.domain_name;
--local destination_number = row.destination_number;
--local destination_context = row.destination_context;
--set the cache
domain = "domain_uuid=" .. domain_uuid .. "&domain_name=" .. domain_name;
if cache.support() then
local key = app:dialplan:inbound:get_domain:" .. destination_number .. " '"..domain.."' "..expire["get_domain"];
if debug['cache'] then
freeswitch.consoleLog("notice", "[dialplan][cache] set key: " .. key .. "\n")
end
local ok, err = cache.set(key, XML_STRING, expire["directory"])
if debug["cache"] and not ok then
freeswitch.consoleLog("warning", "[dialplan][cache] set key: " .. key .. " fail: " .. tostring(err) .. "\n");
end
end
--set the source
source = "database";
end);
else
--add the function
require "resources.functions.explode";
--parse the cache
array = explode("&", cache);
--define the array/table and variables
local var = {}
local key = "";
local value = "";
--parse the cache
key_pairs = explode("&", cache);
for k,v in pairs(key_pairs) do
f = explode("=", v);
key = f[1];
value = f[2];
var[key] = value;
end
--set the variables
domain_uuid = var["domain_uuid"];
domain_name = var["domain_name"];
--set the source
source = "cache";
end
if (domain_name ~= nil) then
--set the call direction as a session variable
session:setVariable("domain_name", domain_name);
session:setVariable("domain", domain_name);
session:setVariable("domain_uuid", domain_uuid);
--send information to the console
freeswitch.consoleLog("notice", "[app:dialplan:inbound:get_domain] " .. cache .. " source: ".. source .."\n");
end
@@ -0,0 +1,137 @@
--
-- FusionPBX
-- Version: MPL 1.1
--
-- The contents of this file are subject to the Mozilla Public License Version
-- 1.1 (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
-- http://www.mozilla.org/MPL/
--
-- Software distributed under the License is distributed on an "AS IS" basis,
-- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-- for the specific language governing rights and limitations under the
-- License.
--
-- The Original Code is FusionPBX
--
-- The Initial Developer of the Original Code is
-- Mark J Crane <markjcrane@fusionpbx.com>
-- Copyright (C) 2010-2015
-- the Initial Developer. All Rights Reserved.
--
-- Contributor(s):
-- KonradSC <konrd@yahoo.com>
--
-- Instructions:
-- Simply add an action to your emergency outbound route. Make sure
-- the order is a lower number than your bridge statement.
--
-- Tag: action
-- Type: lua
-- Data: app.lua emergency_notify [to_email_address] [from_email_address]
--
--debug
debug["info"] = false;
debug["sql"] = false;
--include config.lua
require "resources.functions.config";
require "resources.functions.explode";
require "resources.functions.trim";
require "resources.functions.base64";
--get arguments
to_email = argv[2];
from_email = argv[3];
--check the missed calls
function send_mail()
--set the sounds path for the language, dialect and voice
default_language = session:getVariable("default_language");
default_dialect = session:getVariable("default_dialect");
default_voice = session:getVariable("default_voice");
if (not default_language) then default_language = 'en'; end
if (not default_dialect) then default_dialect = 'us'; end
if (not default_voice) then default_voice = 'callie'; end
--prepare the files
file_subject = scripts_dir.."/app/emergency_notify/resources/templates/"..default_language.."/"..default_dialect.."/email_subject.tpl";
file_body = scripts_dir.."/app/emergency_notify/resources/templates/"..default_language.."/"..default_dialect.."/email_body.tpl";
if (not file_exists(file_subject)) then
file_subject = scripts_dir.."/app/emergency_notify/resources/templates/en/us/email_subject.tpl";
file_body = scripts_dir.."/app/emergency_notify/resources/templates/en/us/email_body.tpl";
end
--prepare the headers
local headers = {}
headers["X-FusionPBX-Domain-UUID"] = domain_uuid;
headers["X-FusionPBX-Domain-Name"] = domain_name;
headers["X-FusionPBX-Call-UUID"] = uuid;
headers["X-FusionPBX-Email-Type"] = 'emergency_call';
--remove quotes from caller id name and number
caller_id_name = caller_id_name:gsub("'", "&#39;");
caller_id_name = caller_id_name:gsub([["]], "&#34;");
caller_id_number = caller_id_number:gsub("'", "&#39;");
caller_id_number = caller_id_number:gsub([["]], "&#34;");
--prepare the subject
local f = io.open(file_subject, "r");
local subject = f:read("*all");
f:close();
subject = subject:gsub("${caller_id_name}", caller_id_name);
subject = subject:gsub("${caller_id_number}", caller_id_number);
subject = subject:gsub("${sip_to_user}", sip_to_user);
subject = subject:gsub("${caller_destination}", caller_destination);
subject = trim(subject);
subject = '=?utf-8?B?'..base64.encode(subject)..'?=';
--prepare the body
local f = io.open(file_body, "r");
local body = f:read("*all");
f:close();
body = body:gsub("${caller_id_name}", caller_id_name);
body = body:gsub("${caller_id_number}", caller_id_number);
body = body:gsub("${sip_to_user}", sip_to_user);
body = body:gsub("${caller_destination}", caller_destination);
body = body:gsub(" ", "&nbsp;");
body = body:gsub("%s+", "");
body = body:gsub("&nbsp;", " ");
body = body:gsub("\n", "");
body = body:gsub("\n", "");
body = trim(body);
--send the email
send_mail(headers,
from_email,
to_email,
{subject, body}
);
end
--handle originate_disposition
if (session ~= nil and session:ready()) then
uuid = session:getVariable("uuid");
domain_uuid = session:getVariable("domain_uuid");
domain_name = session:getVariable("domain_name");
context = session:getVariable("context");
caller_id_name = session:getVariable("outbound_caller_id_name");
caller_id_number = session:getVariable("caller_id_number");
sip_to_user = session:getVariable("sip_to_user");
caller_destination = session:getVariable("caller_destination");
if (debug["info"] == true) then
freeswitch.consoleLog("INFO", "[emergency_notify] caller_id_number: " .. tostring(caller_id_number) .. "\n");
freeswitch.consoleLog("INFO", "[emergency_notify] caller_id_number: " .. tostring(caller_id_number) .. "\n");
freeswitch.consoleLog("INFO", "[emergency_notify] caller_destination: " .. tostring(caller_destination) .. "\n");
freeswitch.consoleLog("INFO", "[emergency_notify] to_email: " .. tostring(to_email) .. "\n");
freeswitch.consoleLog("INFO", "[emergency_notify] from_email: " .. tostring(from_email) .. "\n");
end
send_mail();
end
@@ -0,0 +1 @@
${caller_id_name} &lt;${caller_id_number}&gt; made an emergency call to ${sip_to_user}
@@ -0,0 +1 @@
${caller_id_name} <${caller_id_number}> Made An Emergency Call
@@ -0,0 +1,192 @@
--
-- event_notify
-- Version: MPL 1.1
--
-- The contents of this file are subject to the Mozilla Public License Version
-- 1.1 (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
-- http://www.mozilla.org/MPL/
--
-- Software distributed under the License is distributed on an "AS IS" basis,
-- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-- for the specific language governing rights and limitations under the
-- License.
--
-- The Original Code is FusionPBX - event_notify
--
-- The Initial Developer of the Original Code is
-- Mark J Crane <markjcrane@fusionpbx.com>
-- Copyright (C) 2013 - 2018
-- the Initial Developer. All Rights Reserved.
--
-- Contributor(s):
-- Mark J Crane <markjcrane@fusionpbx.com>
-- Errol Samuels <voiptology@gmail.com>
--define the explode function
require "resources.functions.explode";
--usage
--luarun app.lua event_notify internal reboot 1003@domain.fusionpbx.com yealink
--set the args as variables
profile = argv[2];
command = argv[3];
user = argv[4];
vendor = argv[5];
--log the args
--freeswitch.consoleLog("notice", "[event_notify] profile "..profile.."\n");
--freeswitch.consoleLog("notice", "[event_notify] command "..command.."\n");
--freeswitch.consoleLog("notice", "[event_notify] user "..user.."\n");
--freeswitch.consoleLog("notice", "[event_notify] vendor "..vendor.."\n");
--get the user and domain name from the user argv user@domain
user_table = explode("@",user);
user = user_table[1];
domain = user_table[2];
--create the event notify object
local event = freeswitch.Event('NOTIFY');
--add the headers
event:addHeader('profile', profile);
event:addHeader('user', user);
event:addHeader('host', domain);
event:addHeader('content-type', 'application/simple-message-summary');
--aastra
if (vendor == "aastra") then
if (command == "reboot") then
event:addHeader('event-string', 'check-sync;reboot=true');
end
if (command == "check_sync") then
event:addHeader('event-string', 'check-sync;reboot=true');
end
end
--cisco
if (vendor == "cisco") then
if (command == "reboot") then
event:addHeader('event-string', 'check-sync');
end
if (command == "check_sync") then
event:addHeader('event-string', 'check-sync');
end
end
--cisco-spa
if (vendor == "cisco-spa") then
if (command == "reboot") then
event:addHeader('event-string', 'reboot=true');
end
if (command == "check_sync") then
event:addHeader('event-string', 'reboot=true');
end
end
--digium
if (vendor == "digium") then
if (command == "reboot") then
event:addHeader('event-string', 'check-sync');
end
if (command == "check_sync") then
event:addHeader('event-string', 'check-sync');
end
end
--fanvil
if (vendor == "fanvil") then
if (command == "reboot") then
event:addHeader('event-string', 'check-sync;reboot=true');
end
if (command == "check_sync") then
event:addHeader('event-string', 'resync');
end
end
--grandstream
if (vendor == "grandstream") then
if (command == "reboot") then
event:addHeader('event-string', 'check-sync;reboot=true');
end
if (command == "check_sync") then
event:addHeader('event-string', 'resync');
end
end
--htek
if (vendor == "htek") then
if (command == "reboot") then
event:addHeader('event-string', 'check-sync;reboot=true');
end
if (command == "check_sync") then
event:addHeader('event-string', 'resync');
end
end
--sangoma
if (vendor == "sangoma") then
if (command == "reboot") then
event:addHeader('event-string', 'check-sync;reboot=true');
end
if (command == "check_sync") then
event:addHeader('event-string', 'resync');
end
end
--linksys
if (vendor == "linksys") then
if (command == "reboot") then
event:addHeader('event-string', 'reboot=true');
end
if (command == "check_sync") then
event:addHeader('event-string', 'reboot=true');
end
end
--panasonic
if (vendor == "panasonic") then
if (command == "reboot") then
event:addHeader('event-string', 'check-sync;reboot=true');
end
if (command == "check_sync") then
event:addHeader('event-string', 'check-sync;reboot=true');
end
end
--polycom
if (vendor == "polycom") then
if (command == "reboot") then
event:addHeader('event-string', 'check-sync');
end
if (command == "check_sync") then
event:addHeader('event-string', 'check-sync');
end
end
--snom
if (vendor == "snom") then
if (command == "reboot") then
event:addHeader('event-string', 'check-sync;reboot=true');
end
if (command == "check_sync") then
event:addHeader('event-string', 'check-sync;reboot=false');
end
end
--yealink
if (vendor == "yealink") then
if (command == "reboot") then
event:addHeader('event-string', 'check-sync;reboot=true');
end
if (command == "check_sync") then
event:addHeader('event-string', 'check-sync;reboot=false');
end
end
--send the event
event:fire();
--log the event
freeswitch.consoleLog("notice", "[event_notify] command "..command.." "..user.."@"..domain.." vendor "..tostring(vendor).."\n");
@@ -0,0 +1,258 @@
--
-- FusionPBX
-- Version: MPL 1.1
--
-- The contents of this file are subject to the Mozilla Public License Version
-- 1.1 (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
-- http://www.mozilla.org/MPL/
--
-- Software distributed under the License is distributed on an "AS IS" basis,
-- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-- for the specific language governing rights and limitations under the
-- License.
--
-- The Original Code is FusionPBX
--
-- The Initial Developer of the Original Code is
-- Mark J Crane <markjcrane@fusionpbx.com>
-- Copyright (C) 2010-2018
-- the Initial Developer. All Rights Reserved.
--
-- Contributor(s):
-- Salvatore Caruso <salvatore.caruso@nems.it>
-- Riccardo Granchi <riccardo.granchi@nems.it>
-- Luis Daniel Lucio Quiroz <dlucio@okay.com.mx>
--debug
debug["info"] = false;
debug["sql"] = false;
--include config.lua
require "resources.functions.config";
require "resources.functions.explode";
require "resources.functions.trim";
require "resources.functions.base64";
--load libraries
require 'resources.functions.send_mail'
--check the missed calls
function missed()
if (missed_call_app ~= nil and missed_call_data ~= nil) then
if (missed_call_app == "email") then
--set the sounds path for the language, dialect and voice
default_language = session:getVariable("default_language");
default_dialect = session:getVariable("default_dialect");
default_voice = session:getVariable("default_voice");
if (not default_language) then default_language = 'en'; end
if (not default_dialect) then default_dialect = 'us'; end
if (not default_voice) then default_voice = 'callie'; end
--connect to the database
local Database = require "resources.functions.database";
local dbh = Database.new('system');
--get the templates
local sql = "SELECT * FROM v_email_templates ";
sql = sql .. "WHERE (domain_uuid = :domain_uuid or domain_uuid is null) ";
sql = sql .. "AND template_language = :template_language ";
sql = sql .. "AND template_category = 'missed' "
sql = sql .. "AND template_enabled = 'true' "
sql = sql .. "ORDER BY domain_uuid DESC "
local params = {domain_uuid = domain_uuid, template_language = default_language.."-"..default_dialect};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
subject = row["template_subject"];
body = row["template_body"];
end);
--prepare the headers
local headers = {}
headers["X-FusionPBX-Domain-UUID"] = domain_uuid;
headers["X-FusionPBX-Domain-Name"] = domain_name;
headers["X-FusionPBX-Call-UUID"] = uuid;
headers["X-FusionPBX-Email-Type"] = 'missed';
--remove quotes from caller id name and number
caller_id_name = caller_id_name:gsub("'", "&#39;");
caller_id_name = caller_id_name:gsub([["]], "&#34;");
caller_id_number = caller_id_number:gsub("'", "&#39;");
caller_id_number = caller_id_number:gsub([["]], "&#34;");
--prepare the subject
subject = subject:gsub("${caller_id_name}", caller_id_name);
subject = subject:gsub("${caller_id_number}", caller_id_number);
subject = subject:gsub("${sip_to_user}", sip_to_user);
subject = subject:gsub("${dialed_user}", dialed_user);
subject = trim(subject);
subject = '=?utf-8?B?'..base64.encode(subject)..'?=';
--prepare the body
body = body:gsub("${caller_id_name}", caller_id_name);
body = body:gsub("${caller_id_number}", caller_id_number);
body = body:gsub("${sip_to_user}", sip_to_user);
body = body:gsub("${dialed_user}", dialed_user);
body = body:gsub(" ", "&nbsp;");
body = body:gsub("%s+", "");
body = body:gsub("&nbsp;", " ");
body = body:gsub("\n", "");
body = body:gsub("\n", "");
body = trim(body);
--send the email
send_mail(headers,
nil,
missed_call_data,
{subject, body}
);
if (debug["info"]) then
freeswitch.consoleLog("notice", "[missed call] " .. caller_id_number .. "->" .. sip_to_user .. "emailed to " .. missed_call_data .. "\n");
end
end
end
end
--handle originate_disposition
if (session ~= nil and session:ready()) then
uuid = session:getVariable("uuid");
domain_uuid = session:getVariable("domain_uuid");
domain_name = session:getVariable("domain_name");
context = session:getVariable("context");
originate_disposition = session:getVariable("originate_disposition");
originate_causes = session:getVariable("originate_causes");
hangup_on_subscriber_absent = session:getVariable("hangup_on_subscriber_absent");
hangup_on_call_reject = session:getVariable("hangup_on_call_reject");
caller_id_name = session:getVariable("caller_id_name");
caller_id_number = session:getVariable("caller_id_number");
call_direction = session:getVariable("call_direction");
if (caller_direction == "local") then
caller_id_name = session:getVariable("effective_caller_id_name");
end
sip_to_user = session:getVariable("sip_to_user");
dialed_user = session:getVariable("dialed_user");
missed_call_app = session:getVariable("missed_call_app");
missed_call_data = session:getVariable("missed_call_data");
sip_code = session:getVariable("last_bridge_proto_specific_hangup_cause");
if (debug["info"] == true) then
freeswitch.consoleLog("INFO", "[failure_handler] originate_causes: " .. tostring(originate_causes) .. "\n");
freeswitch.consoleLog("INFO", "[failure_handler] originate_disposition: " .. tostring(originate_disposition) .. "\n");
freeswitch.consoleLog("INFO", "[failure_handler] hangup_on_subscriber_absent: " .. tostring(hangup_on_subscriber_absent) .. "\n");
freeswitch.consoleLog("INFO", "[failure_handler] hangup_on_call_reject: " .. tostring(hangup_on_call_reject) .. "\n");
freeswitch.consoleLog("INFO", "[failure_handler] sip_code: " .. tostring(sip_code) .. "\n");
end
if (originate_causes ~= nil) then
array = explode("|",originate_causes);
if (string.find(array[1], "USER_BUSY")) or (sip_code == "sip:486") then
originate_disposition = "USER_BUSY";
session:setVariable("originate_disposition", originate_disposition);
end
end
if (originate_disposition ~= nil) then
if (originate_disposition == 'USER_BUSY') then
--handle USER_BUSY
dialed_extension = session:getVariable("dialed_extension");
last_busy_dialed_extension = session:getVariable("last_busy_dialed_extension");
if (debug["info"] ) then
freeswitch.consoleLog("INFO", "[failure_handler] last_busy_dialed_extension: " .. tostring(last_busy_dialed_extension) .. "\n");
end
--transfer to the forward busy destination
if (dialed_extension ~= nil and dialed_extension ~= last_busy_dialed_extension) then
forward_busy_enabled = session:getVariable("forward_busy_enabled");
if (forward_busy_enabled == "true") then
forward_busy_destination = session:getVariable("forward_busy_destination");
if (forward_busy_destination ~= nil and string.len(forward_busy_destination) > 0) then
--handle USER_BUSY - forwarding to number
session:setVariable("last_busy_dialed_extension", dialed_extension);
if (forward_busy_destination == nil) then
freeswitch.consoleLog("NOTICE", "[failure_handler] forwarding on busy to hangup\n");
session:hangup("USER_BUSY");
else
freeswitch.consoleLog("NOTICE", "[failure_handler] forwarding on busy to: " .. forward_busy_destination .. "\n");
session:transfer(forward_busy_destination, "XML", context);
end
else
--send missed call notification
missed();
--handle USER_BUSY - hangup
freeswitch.consoleLog("NOTICE", "[failure_handler] forward on busy with empty destination: hangup(USER_BUSY)\n");
session:hangup("USER_BUSY");
end
end
end
elseif (originate_disposition == "NO_ANSWER") or (sip_code == "sip:480") then
--handle NO_ANSWER
forward_no_answer_enabled = session:getVariable("forward_no_answer_enabled");
if (forward_no_answer_enabled == "true") then
forward_no_answer_destination = session:getVariable("forward_no_answer_destination");
if (forward_no_answer_destination == nil) then
freeswitch.consoleLog("NOTICE", "[failure_handler] forwarding no answer to hangup\n");
session:hangup("NO_ANSWER");
else
freeswitch.consoleLog("NOTICE", "[failure_handler] forwarding no answer to: " .. forward_no_answer_destination .. "\n");
session:transfer(forward_no_answer_destination, "XML", context);
end
else
--send missed call notification
missed();
end
if (debug["info"] ) then
freeswitch.consoleLog("NOTICE", "[failure_handler] - NO_ANSWER\n");
end
elseif (originate_disposition == "USER_NOT_REGISTERED") then
--handle USER_NOT_REGISTERED
forward_user_not_registered_enabled = session:getVariable("forward_user_not_registered_enabled");
if (forward_user_not_registered_enabled == "true") then
forward_user_not_registered_destination = session:getVariable("forward_user_not_registered_destination");
if (forward_user_not_registered_destination == nil) then
freeswitch.consoleLog("NOTICE", "[failure_handler] forwarding user not registered to hangup\n");
session:hangup("NO_ANSWER");
else
freeswitch.consoleLog("NOTICE", "[failure_handler] forwarding user not registerd to: " .. forward_user_not_registered_destination .. "\n");
session:transfer(forward_user_not_registered_destination, "XML", context);
end
else
--send missed call notification
missed();
end
--send missed call notification
--missed();
--handle USER_NOT_REGISTERED
if (debug["info"] ) then
freeswitch.consoleLog("NOTICE", "[failure_handler] - USER_NOT_REGISTERED - Doing nothing\n");
end
elseif (originate_disposition == "SUBSCRIBER_ABSENT" and hangup_on_subscriber_absent == "true") then
--send missed call notification
missed();
--handle SUBSCRIBER_ABSENT
freeswitch.consoleLog("NOTICE", "[failure_handler] - SUBSCRIBER_ABSENT - hangup(UNALLOCATED_NUMBER)\n");
session:hangup("UNALLOCATED_NUMBER");
elseif (originate_disposition == "CALL_REJECTED" and hangup_on_call_reject =="true") then
--send missed call notification
missed();
--handle CALL_REJECT
freeswitch.consoleLog("NOTICE", "[failure_handler] - CALL_REJECT - hangup()\n");
session:hangup();
end
end
end
@@ -0,0 +1,11 @@
text = text or {};
text['message-send_success'] = {}
text['message-send_success']['en-us'] = "We are happy to report the fax was sent successfully. It has been attached for your records."
text['message-send_success']['ru-ru'] = "Мы рады сообщить, что факс был отправлен успешно. Он был прикреплен к вашим записям."
text['message-send_fail'] = {}
text['message-send_fail']['en-us'] = "We are sorry the fax failed to go through. It has been attached. Please check the number, and if it was correct you might consider emailing it instead."
text['message-send_fail']['ru-ru'] = "Нам жаль, что факс не прошел. Он был прикреплен. Пожалуйста, проверьте номер, и если он был правильным, вы можете отправить его с помощью электронной почты."
return text
@@ -0,0 +1,42 @@
local sleep_interval = 60;
--include config.lua
require "resources.functions.config";
--general functions
require "resources.functions.file_exists";
require "resources.functions.mkdir";
require "resources.functions.sleep";
local log = require "resources.functions.log".fax_queue_monitor
local Next = require "app.fax.resources.scripts.queue.next"
mkdir(scripts_dir .. "/run");
--define the run file
local run_file = scripts_dir .. "/run/fax_queue.tmp";
--used to stop the lua service
local file = assert(io.open(run_file, "w"));
file:write("remove this file to stop the script");
file:close()
log.notice("Start")
while true do
local ok, err = pcall(function()
Next.poll_once()
end)
if not ok then
log.errf("fail poll queue: %s", tostring(err))
end
if not file_exists(run_file) then
break;
end
sleep(sleep_interval * 1000)
end
log.notice("Stop")
@@ -0,0 +1 @@
require "app.fax.resources.scripts.queue.next".poll_once()
@@ -0,0 +1,446 @@
--
-- FusionPBX
-- Version: MPL 1.1
--
-- The contents of this file are subject to the Mozilla Public License Version
-- 1.1 (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
-- http://www.mozilla.org/MPL/
--
-- Software distributed under the License is distributed on an "AS IS" basis,
-- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-- for the specific language governing rights and limitations under the
-- License.
--
-- The Original Code is FusionPBX
--
-- The Initial Developer of the Original Code is
-- Mark J Crane <markjcrane@fusionpbx.com>
-- Copyright (C) 2015-2023
-- the Initial Developer. All Rights Reserved.
--
-- Contributor(s):
-- Mark J. Crane
--set the debug options
debug["sql"] = true;
--create the api object
api = freeswitch.API();
--include config.lua
require "resources.functions.config";
--connect to the database
local Database = require "resources.functions.database";
dbh = Database.new('system');
--include json library
local json
if (debug["sql"]) then
json = require "resources.functions.lunajson"
end
--additional includes
require "resources.functions.explode";
require "resources.functions.count";
require "resources.functions.send_mail";
--check if windows
local IS_WINDOWS = (package.config:sub(1,1) == '\\')
--define function quote
local function quote(s)
local q = IS_WINDOWS and '"' or "'"
if s:find('%s') or s:find(q, nil, true) then
s = q .. s:gsub(q, q..q) .. q
end
return s
end
--escape shell arguments to prevent command injection
local function shell_esc(x)
return (x:gsub('\\', '\\\\')
:gsub('\'', '\\\''))
end
--create the api object
api = freeswitch.API();
--set channel variables to lua variables
domain_uuid = env:getHeader("domain_uuid");
domain_name = env:getHeader("domain_name");
--get the domain_uuid using the domain name required for multi-tenant
if (domain_name ~= nil) then
sql = "SELECT domain_uuid FROM v_domains ";
sql = sql .. "WHERE domain_name = :domain_name ";
dbh:query(sql, {domain_name = domain_name}, function(rows)
domain_uuid = rows["domain_uuid"];
end);
end
--settings
require "resources.functions.settings";
settings = settings(domain_uuid);
storage_type = "";
storage_path = "";
if (settings['fax'] ~= nil) then
if (settings['fax']['storage_type'] ~= nil) then
if (settings['fax']['storage_type']['text'] ~= nil) then
storage_type = settings['fax']['storage_type']['text'];
end
end
if (settings['fax']['storage_path'] ~= nil) then
if (settings['fax']['storage_path']['text'] ~= nil) then
storage_path = settings['fax']['storage_path']['text'];
storage_path = storage_path:gsub("${domain_name}", domain_name);
storage_path = storage_path:gsub("${voicemail_id}", voicemail_id);
storage_path = storage_path:gsub("${voicemail_dir}", voicemail_dir);
end
end
end
--show all channel variables
serialized = env:serialize()
freeswitch.consoleLog("INFO","[fax]\n" .. serialized .. "\n")
--example channel variables relating to fax
--variable_fax_success: 0
--variable_fax_result_code: 49
--variable_fax_result_text: The%20call%20dropped%20prematurely
--variable_fax_ecm_used: off
--variable_fax_local_station_id: SpanDSP%20Fax%20Ident
--variable_fax_document_transferred_pages: 0
--variable_fax_document_total_pages: 0
--variable_fax_image_resolution: 0x0
--variable_fax_image_size: 0
--variable_fax_bad_rows: 0
--variable_fax_transfer_rate: 14400
--set channel variables to lua variables
uuid = env:getHeader("uuid");
fax_uuid = env:getHeader("fax_uuid");
fax_queue_uuid = env:getHeader("fax_queue_uuid");
fax_success = env:getHeader("fax_success");
fax_result_text = env:getHeader("fax_result_text");
fax_local_station_id = env:getHeader("fax_local_station_id");
fax_ecm_used = env:getHeader("fax_ecm_used");
fax_uri = env:getHeader("fax_uri");
fax_extension_number = env:getHeader("fax_extension_number");
caller_id_name = env:getHeader("caller_id_name");
caller_id_number = env:getHeader("caller_id_number");
fax_bad_rows = env:getHeader("fax_bad_rows");
fax_transfer_rate = env:getHeader("fax_transfer_rate");
sip_to_user = env:getHeader("sip_to_user");
bridge_hangup_cause = env:getHeader("bridge_hangup_cause");
fax_result_code = env:getHeader("fax_result_code");
fax_remote_station_id = env:getHeader("fax_remote_station_id");
fax_document_total_pages = env:getHeader("fax_document_total_pages");
hangup_cause_q850 = tonumber(env:getHeader("hangup_cause_q850"));
fax_file = env:getHeader("fax_file");
--prevent nil errors
if (fax_file == nil) then
fax_file = env:getHeader("fax_filename");
end
if (fax_uri == nil) then
fax_uri = "";
end
if (fax_remote_station_id == nil) then
fax_remote_station_id = "";
end
if (caller_id_name == nil) then
caller_id_name = env:getHeader("Caller-Caller-ID-Name");
end
if (caller_id_number == nil) then
caller_id_number = env:getHeader("Caller-Caller-ID-Number");
end
if (document_root == nil) then
document_root = '';
end
--set default values
if (not fax_success) then
fax_success = "0";
end
if (hangup_cause_q850 == "17") then
fax_success = "0";
fax_result_text = "USER_BUSY";
end
if (not fax_result_text) then
fax_result_text = "FS_NOT_SET";
end
--get the fax settings from the database
local sql = [[SELECT * FROM v_fax
WHERE fax_uuid = :fax_uuid
AND domain_uuid = :domain_uuid]];
local params = {fax_uuid = fax_uuid, domain_uuid = domain_uuid};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[fax] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
dialplan_uuid = row["dialplan_uuid"];
fax_extension = row["fax_extension"];
fax_accountcode = row["accountcode"];
fax_destination_number = row["fax_destination_number"];
fax_name = row["fax_name"];
fax_prefix = row["fax_prefix"];
fax_email = row["fax_email"];
fax_email_connection_type = row["fax_email_connection_type"];
fax_email_connection_host = row["fax_email_connection_host"];
fax_email_connection_port = row["fax_email_connection_port"];
fax_email_connection_security = row["fax_email_connection_security"];
fax_email_connection_validate = row["fax_email_connection_validate"];
fax_email_connection_username = row["fax_email_connection_username"];
fax_email_connection_password = row["fax_email_connection_password"];
fax_email_connection_mailbox = row["fax_email_connection_mailbox"];
fax_email_inbound_subject_tag = row["fax_email_inbound_subject_tag"];
fax_email_outbound_subject_tag = row["fax_email_outbound_subject_tag"];
fax_email_outbound_authorized_senders = row["fax_email_outbound_authorized_senders"];
fax_caller_id_name = row["fax_caller_id_name"];
fax_caller_id_number = row["fax_caller_id_number"];
fax_forward_number = row["fax_forward_number"];
fax_description = row["fax_description"];
end);
--get the values from the fax file
if (fax_file ~= nil) then
array = explode("/", fax_file);
fax_file_name = array[count(array)];
end
--fax to email
cmd = quote(shell_esc(php_dir).."/"..shell_esc(php_bin)).." "..quote(shell_esc(document_root).."/secure/fax_to_email.php").." ";
cmd = cmd .. "email="..quote(shell_esc(fax_email)).." ";
cmd = cmd .. "extension="..quote(shell_esc(fax_extension)).." ";
cmd = cmd .. "name="..quote(shell_esc(fax_file)).." ";
cmd = cmd .. "messages=" .. quote("result:"..shell_esc(fax_result_text).." sender:"..shell_esc(fax_remote_station_id).." pages:"..shell_esc(fax_document_total_pages)).." ";
cmd = cmd .. "domain="..quote(shell_esc(domain_name)).." ";
cmd = cmd .. "caller_id_name=" .. quote(shell_esc(caller_id_name) or '') .. " ";
cmd = cmd .. "caller_id_number=" .. quote(shell_esc(caller_id_number) or '') .. " ";
if #fax_forward_number > 0 then
cmd = cmd .. "fax_relay=true ";
else
cmd = cmd .. "fax_relay=false ";
end
if #fax_prefix > 0 then
cmd = cmd .. "fax_prefix=true ";
else
cmd = cmd .. "fax_prefix=false ";
end
freeswitch.consoleLog("notice", "[fax] command: " .. cmd .. "\n");
os.execute(cmd);
--add to fax logs
sql = "insert into v_fax_logs ";
sql = sql .. "(";
sql = sql .. "fax_log_uuid, ";
sql = sql .. "domain_uuid, ";
if (fax_uuid ~= nil) then
sql = sql .. "fax_uuid, ";
end
sql = sql .. "fax_success, ";
sql = sql .. "fax_result_code, ";
sql = sql .. "fax_result_text, ";
sql = sql .. "fax_file, ";
if (fax_ecm_used ~= nil) then
sql = sql .. "fax_ecm_used, ";
end
if (fax_local_station_id ~= nil) then
sql = sql .. "fax_local_station_id, ";
end
sql = sql .. "fax_document_transferred_pages, ";
sql = sql .. "fax_document_total_pages, ";
if (fax_image_resolution ~= nil) then
sql = sql .. "fax_image_resolution, ";
end
if (fax_image_size ~= nil) then
sql = sql .. "fax_image_size, ";
end
if (fax_bad_rows ~= nil) then
sql = sql .. "fax_bad_rows, ";
end
if (fax_transfer_rate ~= nil) then
sql = sql .. "fax_transfer_rate, ";
end
if (fax_uri ~= nil) then
sql = sql .. "fax_uri, ";
end
sql = sql .. "fax_date, ";
sql = sql .. "fax_epoch ";
sql = sql .. ") ";
sql = sql .. "values ";
sql = sql .. "(";
sql = sql .. ":uuid, ";
sql = sql .. ":domain_uuid, ";
if (fax_uuid ~= nil) then
sql = sql .. ":fax_uuid, ";
end
sql = sql .. ":fax_success, ";
sql = sql .. ":fax_result_code, ";
sql = sql .. ":fax_result_text, ";
sql = sql .. ":fax_file, ";
if (fax_ecm_used ~= nil) then
sql = sql .. ":fax_ecm_used, ";
end
if (fax_local_station_id ~= nil) then
sql = sql .. ":fax_local_station_id, ";
end
sql = sql .. ":fax_document_transferred_pages, ";
sql = sql .. ":fax_document_total_pages, ";
if (fax_image_resolution ~= nil) then
sql = sql .. ":fax_image_resolution, ";
end
if (fax_image_size ~= nil) then
sql = sql .. ":fax_image_size, ";
end
if (fax_bad_rows ~= nil) then
sql = sql .. ":fax_bad_rows, ";
end
if (fax_transfer_rate ~= nil) then
sql = sql .. ":fax_transfer_rate, ";
end
if (fax_uri ~= nil) then
sql = sql .. ":fax_uri, ";
end
if (database["type"] == "sqlite") then
sql = sql .. ":fax_date, ";
else
sql = sql .. "now(), ";
end
sql = sql .. ":fax_time ";
sql = sql .. ")";
local params = {
uuid = uuid;
domain_uuid = domain_uuid;
fax_uuid = fax_uuid;
fax_success = fax_success;
fax_result_code = fax_result_code;
fax_result_text = fax_result_text;
fax_file = fax_file;
fax_ecm_used = fax_ecm_used;
fax_local_station_id = fax_local_station_id;
fax_document_transferred_pages = fax_document_transferred_pages or '0';
fax_document_total_pages = fax_document_total_pages or '0';
fax_image_resolution = fax_image_resolution;
fax_image_size = fax_image_size;
fax_bad_rows = fax_bad_rows;
fax_transfer_rate = fax_transfer_rate;
fax_uri = fax_uri;
fax_date = os.date("%Y-%m-%d %X");
fax_time = os.time();
};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[fax] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params);
--add the fax files
if (fax_success ~= nil) then
if (fax_success =="1") then
if (storage_type == "base64") then
--include the file io
local file = require "resources.functions.file"
--read file content as base64 string
fax_base64 = assert(file.read_base64(fax_file));
end
local sql = {}
table.insert(sql, "insert into v_fax_files ");
table.insert(sql, "(");
table.insert(sql, "fax_file_uuid, ");
table.insert(sql, "fax_uuid, ");
table.insert(sql, "fax_mode, ");
table.insert(sql, "fax_file_type, ");
table.insert(sql, "fax_file_path, ");
if (caller_id_name ~= nil) then
table.insert(sql, "fax_caller_id_name, ");
end
if (caller_id_number ~= nil) then
table.insert(sql, "fax_caller_id_number, ");
end
table.insert(sql, "fax_date, ");
table.insert(sql, "fax_epoch, ");
if (storage_type == "base64") then
table.insert(sql, "fax_base64, ");
end
table.insert(sql, "domain_uuid");
table.insert(sql, ") ");
table.insert(sql, "values ");
table.insert(sql, "(");
table.insert(sql, ":uuid, ");
table.insert(sql, ":fax_uuid, ");
table.insert(sql, "'rx', ");
table.insert(sql, "'tif', ");
table.insert(sql, ":fax_file, ");
if (caller_id_name ~= nil) then
table.insert(sql, ":caller_id_name, ");
end
if (caller_id_number ~= nil) then
table.insert(sql, ":caller_id_number, ");
end
if (database["type"] == "sqlite") then
table.insert(sql, ":fax_date, ");
else
table.insert(sql, "now(), ");
end
table.insert(sql, ":fax_time, ");
if (storage_type == "base64") then
table.insert(sql, ":fax_base64, ");
end
table.insert(sql, ":domain_uuid");
table.insert(sql, ")");
sql = table.concat(sql, "\n");
local params = {
uuid = uuid;
domain_uuid = domain_uuid;
fax_uuid = fax_uuid;
fax_file = fax_file;
caller_id_name = caller_id_name;
caller_id_number = caller_id_number;
fax_base64 = fax_base64;
fax_date = os.date("%Y-%m-%d %X");
fax_time = os.time();
};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[fax] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
if (storage_type == "base64") then
local dbh = Database.new('system', 'base64');
dbh:query(sql, params);
dbh:release();
else
result = dbh:query(sql, params);
end
end
end
--send the selected variables to the console
if (fax_success ~= nil) then
freeswitch.consoleLog("INFO","fax_success: '" .. fax_success .. "'\n");
end
freeswitch.consoleLog("INFO","domain_uuid: '" .. domain_uuid .. "'\n");
freeswitch.consoleLog("INFO","domain_name: '" .. domain_name .. "'\n");
freeswitch.consoleLog("INFO","fax_uuid: '" .. fax_uuid .. "'\n");
freeswitch.consoleLog("INFO","fax_extension: '" .. fax_extension .. "'\n");
freeswitch.consoleLog("INFO","fax_result_text: '" .. fax_result_text .. "'\n");
freeswitch.consoleLog("INFO","fax_file: '" .. fax_file .. "'\n");
freeswitch.consoleLog("INFO","uuid: '" .. uuid .. "'\n");
--freeswitch.consoleLog("INFO","fax_ecm_used: '" .. fax_ecm_used .. "'\n");
freeswitch.consoleLog("INFO","fax_uri: '" .. fax_uri.. "'\n");
if (caller_id_name ~= nil) then
freeswitch.consoleLog("INFO","caller_id_name: " .. caller_id_name .. "\n");
end
if (caller_id_number ~= nil) then
freeswitch.consoleLog("INFO","caller_id_number: " .. caller_id_number .. "\n");
end
freeswitch.consoleLog("INFO","fax_result_code: ".. fax_result_code .."\n");
--freeswitch.consoleLog("INFO","mailfrom_address: ".. from_address .."\n");
--freeswitch.consoleLog("INFO","mailto_address: ".. email_address .."\n");
freeswitch.consoleLog("INFO","hangup_cause_q850: '" .. hangup_cause_q850 .. "'\n");
@@ -0,0 +1,452 @@
--
-- FusionPBX
-- Version: MPL 1.1
--
-- The contents of this file are subject to the Mozilla Public License Version
-- 1.1 (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
-- http://www.mozilla.org/MPL/
--
-- Software distributed under the License is distributed on an "AS IS" basis,
-- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-- for the specific language governing rights and limitations under the
-- License.
--
-- The Original Code is FusionPBX
--
-- The Initial Developer of the Original Code is
-- Mark J Crane <markjcrane@fusionpbx.com>
-- Copyright (C) 2015-2023
-- the Initial Developer. All Rights Reserved.
--
-- Contributor(s):
-- Mark J. Crane
--set the debug options
debug["sql"] = true;
--create the api object
api = freeswitch.API();
--include config.lua
require "resources.functions.config";
--connect to the database
local Database = require "resources.functions.database";
dbh = Database.new('system');
--include json library
local json
if (debug["sql"]) then
json = require "resources.functions.lunajson"
end
--additional includes
require "resources.functions.explode";
require "resources.functions.count";
require "resources.functions.send_mail";
--check if windows
local IS_WINDOWS = (package.config:sub(1,1) == '\\')
--define function quote
local function quote(s)
local q = IS_WINDOWS and '"' or "'"
if s:find('%s') or s:find(q, nil, true) then
s = q .. s:gsub(q, q..q) .. q
end
return s
end
--escape shell arguments to prevent command injection
local function shell_esc(x)
return (x:gsub('\\', '\\\\')
:gsub('\'', '\\\''))
end
--set channel variables to lua variables
domain_uuid = env:getHeader("domain_uuid");
domain_name = env:getHeader("domain_name");
--get the domain_uuid using the domain name required for multi-tenant
if (domain_name ~= nil) then
sql = "SELECT domain_uuid FROM v_domains ";
sql = sql .. "WHERE domain_name = :domain_name ";
dbh:query(sql, {domain_name = domain_name}, function(rows)
domain_uuid = rows["domain_uuid"];
end);
end
--settings
require "resources.functions.settings";
settings = settings(domain_uuid);
storage_type = "";
storage_path = "";
if (settings['fax'] ~= nil) then
if (settings['fax']['storage_type'] ~= nil) then
if (settings['fax']['storage_type']['text'] ~= nil) then
storage_type = settings['fax']['storage_type']['text'];
end
end
if (settings['fax']['storage_path'] ~= nil) then
if (settings['fax']['storage_path']['text'] ~= nil) then
storage_path = settings['fax']['storage_path']['text'];
storage_path = storage_path:gsub("${domain_name}", domain_name);
storage_path = storage_path:gsub("${voicemail_id}", voicemail_id);
storage_path = storage_path:gsub("${voicemail_dir}", voicemail_dir);
end
end
end
--show all channel variables
serialized = env:serialize()
freeswitch.consoleLog("INFO","[fax]\n" .. serialized .. "\n")
--example channel variables relating to fax
--variable_fax_success: 0
--variable_fax_result_code: 49
--variable_fax_result_text: The%20call%20dropped%20prematurely
--variable_fax_ecm_used: off
--variable_fax_local_station_id: SpanDSP%20Fax%20Ident
--variable_fax_document_transferred_pages: 0
--variable_fax_document_total_pages: 0
--variable_fax_image_resolution: 0x0
--variable_fax_image_size: 0
--variable_fax_bad_rows: 0
--variable_fax_transfer_rate: 14400
--set channel variables to lua variables
uuid = env:getHeader("uuid");
fax_uuid = env:getHeader("fax_uuid");
fax_queue_uuid = env:getHeader("fax_queue_uuid");
fax_success = env:getHeader("fax_success");
fax_result_text = env:getHeader("fax_result_text");
fax_local_station_id = env:getHeader("fax_local_station_id");
fax_image_resolution = env:getHeader("fax_image_resolution");
fax_image_size = env:getHeader("fax_image_size");
fax_ecm_used = env:getHeader("fax_ecm_used");
fax_uri = env:getHeader("fax_uri");
fax_extension_number = env:getHeader("fax_extension_number");
caller_id_name = env:getHeader("caller_id_name");
caller_id_number = env:getHeader("caller_id_number");
fax_bad_rows = env:getHeader("fax_bad_rows");
fax_transfer_rate = env:getHeader("fax_transfer_rate");
sip_to_user = env:getHeader("sip_to_user");
bridge_hangup_cause = env:getHeader("bridge_hangup_cause");
fax_result_code = env:getHeader("fax_result_code");
fax_remote_station_id = env:getHeader("fax_remote_station_id");
fax_document_transferred_pages = env:getHeader("fax_document_transferred_pages");
fax_document_total_pages = env:getHeader("fax_document_total_pages");
hangup_cause_q850 = tonumber(env:getHeader("hangup_cause_q850"));
fax_file = env:getHeader("fax_file");
fax_duration = env:getHeader("billsec");
--prevent nil errors
if (fax_file == nil) then
fax_file = env:getHeader("fax_filename");
end
if (fax_uri == nil) then
fax_uri = "";
end
if (fax_remote_station_id == nil) then
fax_remote_station_id = "";
end
if (caller_id_name == nil) then
caller_id_name = env:getHeader("Caller-Caller-ID-Name");
end
if (caller_id_number == nil) then
caller_id_number = env:getHeader("Caller-Caller-ID-Number");
end
if (document_root == nil) then
document_root = '';
end
--set default values
if (not fax_success) then
fax_success = "0";
end
if (hangup_cause_q850 == "17") then
fax_success = "0";
fax_result_text = "USER_BUSY";
end
if (not fax_result_text) then
fax_result_text = "FS_NOT_SET";
end
--fax sent successfully
if (fax_success == '1') then
fax_status = 'sent';
else
fax_status = 'trying';
end
--fax busy
if (fax_result_code == "2" or fax_result_code == "3" or hangup_cause_q850 == 17) then
--do nothing. don't want to increment
freeswitch.consoleLog("INFO","[FAX] Last Fax was probably Busy, don't increment retry_attempts. \n");
fax_status = 'busy';
end
--get the fax settings from the database
local sql = [[SELECT * FROM v_fax
WHERE fax_uuid = :fax_uuid
AND domain_uuid = :domain_uuid]];
local params = {fax_uuid = fax_uuid, domain_uuid = domain_uuid};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[fax] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
dialplan_uuid = row["dialplan_uuid"];
fax_extension = row["fax_extension"];
fax_accountcode = row["accountcode"];
fax_destination_number = row["fax_destination_number"];
fax_name = row["fax_name"];
fax_prefix = row["fax_prefix"];
fax_email = row["fax_email"];
fax_email_connection_type = row["fax_email_connection_type"];
fax_email_connection_host = row["fax_email_connection_host"];
fax_email_connection_port = row["fax_email_connection_port"];
fax_email_connection_security = row["fax_email_connection_security"];
fax_email_connection_validate = row["fax_email_connection_validate"];
fax_email_connection_username = row["fax_email_connection_username"];
fax_email_connection_password = row["fax_email_connection_password"];
fax_email_connection_mailbox = row["fax_email_connection_mailbox"];
fax_email_inbound_subject_tag = row["fax_email_inbound_subject_tag"];
fax_email_outbound_subject_tag = row["fax_email_outbound_subject_tag"];
fax_email_outbound_authorized_senders = row["fax_email_outbound_authorized_senders"];
fax_caller_id_name = row["fax_caller_id_name"];
fax_caller_id_number = row["fax_caller_id_number"];
fax_forward_number = row["fax_forward_number"];
fax_description = row["fax_description"];
end);
--get the values from the fax file
if (fax_file ~= nil) then
array = explode("/", fax_file);
fax_file_name = array[count(array)];
end
--add to fax logs
sql = "insert into v_fax_logs ";
sql = sql .. "(";
sql = sql .. "fax_log_uuid, ";
sql = sql .. "domain_uuid, ";
if (fax_uuid ~= nil) then
sql = sql .. "fax_uuid, ";
end
sql = sql .. "fax_success, ";
sql = sql .. "fax_result_code, ";
sql = sql .. "fax_result_text, ";
sql = sql .. "fax_file, ";
if (fax_ecm_used ~= nil) then
sql = sql .. "fax_ecm_used, ";
end
if (fax_local_station_id ~= nil) then
sql = sql .. "fax_local_station_id, ";
end
sql = sql .. "fax_document_transferred_pages, ";
sql = sql .. "fax_document_total_pages, ";
if (fax_image_resolution ~= nil) then
sql = sql .. "fax_image_resolution, ";
end
if (fax_image_size ~= nil) then
sql = sql .. "fax_image_size, ";
end
if (fax_bad_rows ~= nil) then
sql = sql .. "fax_bad_rows, ";
end
if (fax_transfer_rate ~= nil) then
sql = sql .. "fax_transfer_rate, ";
end
if (fax_uri ~= nil) then
sql = sql .. "fax_uri, ";
end
if (fax_duration ~= nil) then
sql = sql .. "fax_duration, ";
end
sql = sql .. "fax_date, ";
sql = sql .. "fax_epoch ";
sql = sql .. ") ";
sql = sql .. "values ";
sql = sql .. "(";
sql = sql .. ":uuid, ";
sql = sql .. ":domain_uuid, ";
if (fax_uuid ~= nil) then
sql = sql .. ":fax_uuid, ";
end
sql = sql .. ":fax_success, ";
sql = sql .. ":fax_result_code, ";
sql = sql .. ":fax_result_text, ";
sql = sql .. ":fax_file, ";
if (fax_ecm_used ~= nil) then
sql = sql .. ":fax_ecm_used, ";
end
if (fax_local_station_id ~= nil) then
sql = sql .. ":fax_local_station_id, ";
end
sql = sql .. ":fax_document_transferred_pages, ";
sql = sql .. ":fax_document_total_pages, ";
if (fax_image_resolution ~= nil) then
sql = sql .. ":fax_image_resolution, ";
end
if (fax_image_size ~= nil) then
sql = sql .. ":fax_image_size, ";
end
if (fax_bad_rows ~= nil) then
sql = sql .. ":fax_bad_rows, ";
end
if (fax_transfer_rate ~= nil) then
sql = sql .. ":fax_transfer_rate, ";
end
if (fax_uri ~= nil) then
sql = sql .. ":fax_uri, ";
end
if (fax_duration ~= nil) then
sql = sql .. ":fax_duration, ";
end
if (database["type"] == "sqlite") then
sql = sql .. ":fax_date, ";
else
sql = sql .. "now(), ";
end
sql = sql .. ":fax_time ";
sql = sql .. ")";
local params = {
uuid = uuid;
domain_uuid = domain_uuid;
fax_uuid = fax_uuid;
fax_success = fax_success;
fax_result_code = fax_result_code;
fax_result_text = fax_result_text;
fax_file = fax_file;
fax_ecm_used = fax_ecm_used;
fax_local_station_id = fax_local_station_id;
fax_document_transferred_pages = fax_document_transferred_pages or '0';
fax_document_total_pages = fax_document_total_pages or '0';
fax_image_resolution = fax_image_resolution;
fax_image_size = fax_image_size;
fax_bad_rows = fax_bad_rows;
fax_transfer_rate = fax_transfer_rate;
fax_uri = fax_uri;
fax_duration = fax_duration;
fax_date = os.date("%Y-%m-%d %X");
fax_time = os.time();
};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[fax] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params);
--update the email queue status
sql = "update v_fax_queue ";
sql = sql .. "set fax_status = :fax_status, fax_log_uuid = :fax_log_uuid ";
sql = sql .. "where fax_queue_uuid = :fax_queue_uuid ";
local params = {fax_queue_uuid = fax_queue_uuid, fax_status = fax_status, fax_log_uuid = uuid}
dbh:query(sql, params);
--prepare base64
if (storage_type == "base64") then
--include the file io
local file = require "resources.functions.file"
--read file content as base64 string
fax_base64 = assert(file.read_base64(fax_file));
end
--add the fax files
local sql = {}
table.insert(sql, "insert into v_fax_files ");
table.insert(sql, "(");
table.insert(sql, "fax_file_uuid, ");
table.insert(sql, "fax_uuid, ");
table.insert(sql, "fax_mode, ");
table.insert(sql, "fax_file_type, ");
table.insert(sql, "fax_file_path, ");
if (caller_id_name ~= nil) then
table.insert(sql, "fax_caller_id_name, ");
end
if (caller_id_number ~= nil) then
table.insert(sql, "fax_caller_id_number, ");
end
table.insert(sql, "fax_destination, ");
table.insert(sql, "fax_date, ");
table.insert(sql, "fax_epoch, ");
if (storage_type == "base64") then
table.insert(sql, "fax_base64, ");
end
table.insert(sql, "domain_uuid");
table.insert(sql, ") ");
table.insert(sql, "values ");
table.insert(sql, "(");
table.insert(sql, ":uuid, ");
table.insert(sql, ":fax_uuid, ");
table.insert(sql, "'tx', ");
table.insert(sql, "'tif', ");
table.insert(sql, ":fax_file, ");
if (caller_id_name ~= nil) then
table.insert(sql, ":caller_id_name, ");
end
if (caller_id_number ~= nil) then
table.insert(sql, ":caller_id_number, ");
end
table.insert(sql, ":fax_destination, ");
if (database["type"] == "sqlite") then
table.insert(sql, ":fax_date, ");
else
table.insert(sql, "now(), ");
end
table.insert(sql, ":fax_time, ");
if (storage_type == "base64") then
table.insert(sql, ":fax_base64, ");
end
table.insert(sql, ":domain_uuid");
table.insert(sql, ")");
sql = table.concat(sql, "\n");
local params = {
uuid = uuid;
domain_uuid = domain_uuid;
fax_uuid = fax_uuid;
fax_file = fax_file;
caller_id_name = fax_caller_id_name;
caller_id_number = fax_caller_id_number;
fax_destination = sip_to_user;
fax_base64 = fax_base64;
fax_date = os.date("%Y-%m-%d %X");
fax_time = os.time();
};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[fax] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
if (storage_type == "base64") then
local dbh = Database.new('system', 'base64');
dbh:query(sql, params);
dbh:release();
else
dbh:query(sql, params);
end
--send the selected variables to the console
if (fax_success ~= nil) then
freeswitch.consoleLog("INFO","fax_success: '" .. fax_success .. "'\n");
end
freeswitch.consoleLog("INFO","domain_uuid: '" .. domain_uuid .. "'\n");
freeswitch.consoleLog("INFO","domain_name: '" .. domain_name .. "'\n");
freeswitch.consoleLog("INFO","fax_uuid: '" .. fax_uuid .. "'\n");
freeswitch.consoleLog("INFO","fax_extension: '" .. fax_extension .. "'\n");
freeswitch.consoleLog("INFO","fax_result_text: '" .. fax_result_text .. "'\n");
freeswitch.consoleLog("INFO","fax_file: '" .. fax_file .. "'\n");
freeswitch.consoleLog("INFO","uuid: '" .. uuid .. "'\n");
--freeswitch.consoleLog("INFO","fax_ecm_used: '" .. fax_ecm_used .. "'\n");
freeswitch.consoleLog("INFO","fax_uri: '" .. fax_uri.. "'\n");
if (caller_id_name ~= nil) then
freeswitch.consoleLog("INFO","caller_id_name: " .. fax_caller_id_name .. "\n");
end
if (caller_id_number ~= nil) then
freeswitch.consoleLog("INFO","caller_id_number: " .. fax_caller_id_number .. "\n");
end
freeswitch.consoleLog("INFO","fax_destination: " .. sip_to_user .. "\n");
freeswitch.consoleLog("INFO","fax_result_code: ".. fax_result_code .."\n");
--freeswitch.consoleLog("INFO","mailfrom_address: ".. from_address .."\n");
--freeswitch.consoleLog("INFO","mailto_address: ".. email_address .."\n");
freeswitch.consoleLog("INFO","hangup_cause_q850: '" .. hangup_cause_q850 .. "'\n");
@@ -0,0 +1,51 @@
<html>
<table width="400" border="0" cellspacing="0" cellpadding="0" align="center"
style="border: 1px solid #cbcfd5;-moz-border-radius: 4px;
-webkit-border-radius: 4px; border-radius: 4px;">
<tr>
<td valign="middle" align="center" bgcolor="#ff7174" style="background-color: #ff7174;
color: #000; font-family: Arial; font-size: 14px; padding: 7px;-moz-border-radius: 4px;
-webkit-border-radius: 4px; border-radius: 4px;">
<strong>Send fax fail</strong>
</td>
</tr>
<tr>
<td valign="top" style="padding: 15px;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td style="color: #333; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
<strong>To</strong>
</td>
<td style="color: #666; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
${destination_number}
</td>
</tr>
<tr>
<td style="color: #333; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
<strong>Pages</strong>
</td>
<td style="color: #666; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
${document_transferred_pages}/${document_total_pages}
</td>
</tr>
<tr>
<td style="color: #333; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
<strong>Message</strong>
</td>
<td style="color: #666; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
${message}
</td>
</tr>
<tr>
<td style="color: #333; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
<strong>Error</strong>
</td>
<td style="color: #666; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
${hangup_cause}/${fax_result_code}/${fax_result_text}
</td>
</tr>
</table>
</td>
</tr>
</table>
</html>
@@ -0,0 +1 @@
Fax to: ${destination_number} FAILED
@@ -0,0 +1,51 @@
<html>
<table width="400" border="0" cellspacing="0" cellpadding="0" align="center"
style="border: 1px solid #cbcfd5;-moz-border-radius: 4px;
-webkit-border-radius: 4px; border-radius: 4px;">
<tr>
<td valign="middle" align="center" bgcolor="#e5e9f0" style="background-color: #e5e9f0;
color: #000; font-family: Arial; font-size: 14px; padding: 7px;-moz-border-radius: 4px;
-webkit-border-radius: 4px; border-radius: 4px;">
<strong>Send fax successfully</strong>
</td>
</tr>
<tr>
<td valign="top" style="padding: 15px;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td style="color: #333; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
<strong>To</strong>
</td>
<td style="color: #666; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
${destination_number}
</td>
</tr>
<tr>
<td style="color: #333; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
<strong>Pages</strong>
</td>
<td style="color: #666; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
${document_transferred_pages}
</td>
</tr>
<tr>
<td style="color: #333; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
<strong>Message</strong>
</td>
<td style="color: #666; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
${message}
</td>
</tr>
<tr>
<td style="color: #333; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
<strong>Options</strong>
</td>
<td style="color: #666; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
${fax_options}
</td>
</tr>
</table>
</td>
</tr>
</table>
</html>
@@ -0,0 +1 @@
Fax to: ${destination_number} sent
@@ -0,0 +1,359 @@
--
-- event_notify
-- Version: MPL 1.1
--
-- The contents of this file are subject to the Mozilla Public License Version
-- 1.1 (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
-- http://www.mozilla.org/MPL/
--
-- Software distributed under the License is distributed on an "AS IS" basis,
-- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-- for the specific language governing rights and limitations under the
-- License.
--
-- The Original Code is FusionPBX - event_notify
--
-- The Initial Developer of the Original Code is
-- Mark J Crane <markjcrane@fusionpbx.com>
-- Copyright (C) 2013 - 2021
-- the Initial Developer. All Rights Reserved.
--
-- Contributor(s):
-- Mark J Crane <markjcrane@fusionpbx.com>
-- KonradSC <konrd@yahoo.com>
-- Start the script
-- /etc/freeswitch/autoload_configs/lua.conf.xml
-- <!-- Subscribe to events -->
-- <hook event="PHONE_FEATURE_SUBSCRIBE" subclass="" script="app.lua feature_event"/>
--Enable Feature Sync
-- Default Settings:
-- Device -> feature_sync = true
--Yealink
-- Web Interface -> Features -> General Information -> Feature Key Synchronization set to Enabled
-- Config Files -> bw.feature_key_sync = 1
--Polycom
-- reg.{$row.line_number}.serverFeatureControl.cf="1"
-- reg.{$row.line_number}.serverFeatureControl.dnd="1"
-- Cisco SPA
-- <Feature_Key_Sync_1_ group="Ext_1/Call_Feature_Settings">Yes</Feature_Key_Sync_1_>
--prepare the api object
api = freeswitch.API();
--define the functions
require "resources.functions.trim";
require "resources.functions.explode";
--connect to the database
local Database = require "resources.functions.database";
local Settings = require "resources.functions.lazy_settings"
local cache = require "resources.functions.cache"
local notify = require "app.feature_event.resources.functions.feature_event_notify"
dbh = Database.new('system');
local settings = Settings.new(dbh, domain_name, domain_uuid);
--set debug
debug["sql"] = false;
--include json library
local json
if (debug["sql"]) then
json = require "resources.functions.lunajson"
end
--get the events
--serialize the data for the console
--freeswitch.consoleLog("notice","[events] " .. event:serialize("xml") .. "\n");
--freeswitch.consoleLog("notice","[evnts] " .. event:serialize("json") .. "\n");
--get the event variables
user = event:getHeader("user");
host = event:getHeader("host");
domain_name = event:getHeader("host");
contact = event:getHeader("contact");
feature_action = event:getHeader("Feature-Action");
feature_enabled = event:getHeader("Feature-Enabled");
action_name = event:getHeader("Action-Name");
action_value = event:getHeader("Action-Value")
ring_count = event:getHeader("ringCount")
--send to the log
-- freeswitch.consoleLog("notice","[events] user: " .. user .. "\n");
-- freeswitch.consoleLog("notice","[events] host: " .. host .. "\n");
-- if (feature_action ~= nil) then freeswitch.consoleLog("notice","[events] feature_action: " .. feature_action .. "\n"); end
-- if (feature_enabled ~= nil) then freeswitch.consoleLog("notice","[events] feature_enabled: " .. feature_enabled .. "\n"); end
-- if (action_name ~= nil) then freeswitch.consoleLog("notice","[events] action_name: " .. action_name .. "\n"); end
-- if (action_value ~= nil) then freeswitch.consoleLog("notice","[events] action_value: " .. action_value .. "\n"); end
--get the domain uuid from the host
local sql = "select * from v_domains ";
sql = sql .. "where domain_name = :domain_name ";
local params = {domain_name = domain_name};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[feature_event] " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
domain_uuid = row.domain_uuid;
end);
--get extension information
if (user ~= nil and domain_name ~= nil) then
do_not_disturb, forward_all_enabled, forward_all_destination, forward_busy_enabled, forward_busy_destination, forward_no_answer_enabled, forward_no_answer_destination, call_timeout = notify.get_db_values(user, domain_name)
end
--get sip profile
if (user ~= nil and host ~= nil) then
sip_profiles = notify.get_profiles(user, host);
end
--DND
if (sip_profiles ~= nil) then
--DND enabled
if (feature_action == "SetDoNotDisturb") then
--set dnd
if(feature_enabled == "true") then
do_not_disturb = "true";
else
do_not_disturb = "false";
end
--update the extension
sql = "update v_extensions set ";
sql = sql .. "do_not_disturb = :do_not_disturb ";
sql = sql .. "where domain_uuid = :domain_uuid ";
sql = sql .. "and extension_uuid = :extension_uuid ";
local params = {domain_uuid = domain_uuid, extension_uuid = extension_uuid, do_not_disturb = do_not_disturb};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[feature_event] "..sql.."; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params);
--send notify to the phone
notify.dnd(user, host, sip_profiles, do_not_disturb);
end
--Call Forward
--Call Forward All enabled
if (feature_action == "SetCallForward" and feature_enabled == "true" and action_name == "forward_immediate") then
--set a variable
forward_all_destination = action_value;
forward_all_enabled = "true";
forward_immediate_destination = action_value;
forward_immediate_enabled = "true";
if feature_enabled == "true" then
local destination_extension, destination_number_alias
--used for number_alias to get the correct user
local sql = "select extension, number_alias from v_extensions ";
sql = sql .. "where domain_uuid = :domain_uuid ";
sql = sql .. "and number_alias = :number_alias ";
local params = {domain_uuid = domain_uuid; number_alias = forward_all_destination}
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[feature_event] "..sql.."; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
destination_user = row.extension;
destination_extension = row.extension;
destination_number_alias = row.number_alias or '';
end);
if (destination_user ~= nil) then
cmd = "user_exists id ".. destination_user .." "..domain_name;
else
cmd = "user_exists id ".. forward_all_destination .." "..domain_name;
end
local user_exists = trim(api:executeString(cmd));
end
--update the extension
sql = "update v_extensions set ";
sql = sql .. "do_not_disturb = 'false', ";
sql = sql .. "forward_all_enabled = 'true', ";
sql = sql .. "forward_all_destination = :forward_all_destination ";
sql = sql .. "where domain_uuid = :domain_uuid ";
sql = sql .. "and extension_uuid = :extension_uuid ";
local params = {domain_uuid = domain_uuid, extension_uuid = extension_uuid, forward_all_destination = forward_all_destination};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[feature_event] "..sql.."; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params);
--send notify to the phone
notify.forward_immediate(user, host, sip_profiles, forward_immediate_enabled, forward_immediate_destination);
end
--Call Forward All disable
if (feature_action == "SetCallForward" and feature_enabled == "false" and action_name == "forward_immediate") then
--set a variable
forward_all_destination = action_value;
forward_all_enabled = "false";
forward_immediate_enabled = "false";
forward_immediate_destination = action_value;
--update the extension
sql = "update v_extensions set ";
sql = sql .. "do_not_disturb = 'false', ";
sql = sql .. "forward_all_enabled = 'false', ";
if (forward_all_destination ~= nil) then
sql = sql .. "forward_all_destination = :forward_all_destination ";
else
sql = sql .. "forward_all_destination = null ";
end
sql = sql .. "where domain_uuid = :domain_uuid ";
sql = sql .. "and extension_uuid = :extension_uuid ";
local params = {domain_uuid = domain_uuid, extension_uuid = extension_uuid, forward_all_destination = forward_all_destination};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[feature_event] "..sql.."; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params);
--send notify to the phone
if (forward_immediate_destination == nil) then
forward_immediate_destination = " ";
end
notify.forward_immediate(user, host, sip_profiles, forward_immediate_enabled, forward_immediate_destination);
end
--Call Forward BUSY enable
if (feature_action == "SetCallForward" and feature_enabled == "true" and action_name == "forward_busy") then
--set a variable
forward_busy_destination = action_value;
forward_busy_enabled = "true";
--update the extension
sql = "update v_extensions set ";
sql = sql .. "do_not_disturb = 'false', ";
sql = sql .. "forward_busy_enabled = 'true', ";
sql = sql .. "forward_busy_destination = :forward_busy_destination ";
sql = sql .. "where domain_uuid = :domain_uuid ";
sql = sql .. "and extension_uuid = :extension_uuid ";
local params = {domain_uuid = domain_uuid, extension_uuid = extension_uuid, forward_busy_destination = forward_busy_destination};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[feature_event] "..sql.."; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params);
--send notify to the phone
notify.forward_busy(user, host, sip_profiles, forward_busy_enabled, forward_busy_destination);
end
--Call Forward BUSY disable
if (feature_action == "SetCallForward" and feature_enabled == "false" and action_name == "forward_busy") then
--set a variable
forward_busy_destination = action_value;
forward_busy_enabled = "false";
--update the extension
sql = "update v_extensions set ";
sql = sql .. "do_not_disturb = 'false', ";
sql = sql .. "forward_busy_enabled = 'false', ";
if (forward_busy_destination ~= nil) then
sql = sql .. "forward_busy_destination = :forward_busy_destination ";
else
sql = sql .. "forward_busy_destination = null ";
end
sql = sql .. "where domain_uuid = :domain_uuid ";
sql = sql .. "and extension_uuid = :extension_uuid ";
local params = {domain_uuid = domain_uuid, extension_uuid = extension_uuid, forward_busy_destination = forward_busy_destination};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[feature_event] "..sql.."; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params);
--send notify to the phone
notify.forward_busy(user, host, sip_profiles, forward_busy_enabled, forward_busy_destination);
end
--Call Forward NO ANSWER enable
if (feature_action == "SetCallForward" and feature_enabled == "true" and action_name == "forward_no_answer") then
--set a variable
forward_no_answer_destination = action_value;
forward_no_answer_enabled = "true";
call_timeout = ring_count * 6;
--update the extension
sql = "update v_extensions set ";
sql = sql .. "do_not_disturb = 'false', ";
sql = sql .. "call_timeout = :call_timeout, ";
sql = sql .. "forward_no_answer_enabled = 'true', ";
sql = sql .. "forward_no_answer_destination = :forward_no_answer_destination ";
sql = sql .. "where domain_uuid = :domain_uuid ";
sql = sql .. "and extension_uuid = :extension_uuid ";
local params = {domain_uuid = domain_uuid, extension_uuid = extension_uuid, forward_no_answer_destination = forward_no_answer_destination, call_timeout = call_timeout};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[feature_event] "..sql.."; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params);
--send notify to the phone
notify.forward_no_answer(user, host, sip_profiles, forward_no_answer_enabled, forward_no_answer_destination, ring_count);
end
--Call Forward NO ANSWER disable
if (feature_action == "SetCallForward" and feature_enabled == "false" and action_name == "forward_no_answer") then
--set a variable
forward_no_answer_destination = action_value;
forward_no_answer_enabled = "false";
--update the extension
sql = "update v_extensions set ";
sql = sql .. "do_not_disturb = 'false', ";
sql = sql .. "forward_no_answer_enabled = 'false', ";
if (forward_no_answer_destination ~= nil) then
sql = sql .. "forward_no_answer_destination = :forward_no_answer_destination ";
else
sql = sql .. "forward_no_answer_destination = null ";
end
sql = sql .. "where domain_uuid = :domain_uuid ";
sql = sql .. "and extension_uuid = :extension_uuid ";
local params = {domain_uuid = domain_uuid, extension_uuid = extension_uuid, forward_no_answer_destination = forward_no_answer_destination, call_timeout = call_timeout};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[feature_event] "..sql.."; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params);
--send notify to the phone
notify.forward_no_answer(user, host, sip_profiles, forward_no_answer_enabled, forward_no_answer_destination, ring_count);
end
--No feature event (phone boots): Send all values
if (feature_enabled == nil) then
--Do Not Disturb
--notify.dnd(user, host, sip_profiles, do_not_disturb);
--Forward all
forward_immediate_enabled = forward_all_enabled;
forward_immediate_destination = forward_all_destination;
--notify.forward_immediate(user, host, sip_profiles, forward_immediate_enabled, forward_immediate_destination);
--Forward busy
--notify.forward_busy(user, host, sip_profiles, forward_busy_enabled, forward_busy_destination);
--Forward No Answer
ring_count = math.ceil (call_timeout / 6);
--notify.forward_no_answer(user, host, sip_profiles, forward_no_answer_enabled, forward_no_answer_destination, ring_count);
notify.init(user,
host,
sip_profiles,
forward_immediate_enabled,
forward_immediate_destination,
forward_busy_enabled,
forward_busy_destination,
forward_no_answer_enabled,
forward_no_answer_destination,
ring_count,
do_not_disturb);
end
-- feature_event_notify.init(user, host, sip_profiles, forward_immediate_enabled, forward_immediate_destination, forward_busy_enabled, forward_busy_destination, forward_no_answer_enabled, forward_no_answer_destination, ring_count, do_not_disturb)
end
--clear the cache
if (feature_enabled ~= nil) then
cache.del("directory:"..user.."@"..host)
end
@@ -0,0 +1,176 @@
local feature_event_notify = {}
function feature_event_notify.get_db_values(user, domain_name)
--get the domain uuid from the host
local Database = require "resources.functions.database";
local dbh = Database.new('system');
local sql = "select * from v_domains ";
sql = sql .. "where domain_name = :domain_name ";
local params = {domain_name = domain_name};
-- if (debug["sql"]) then
-- freeswitch.consoleLog("notice", "[feature_event] " .. sql .. "; params:" .. json.encode(params) .. "\n");
-- end
dbh:query(sql, params, function(row)
domain_uuid = row.domain_uuid;
end);
--get extension information
local sql = "select * from v_extensions ";
sql = sql .. "where domain_uuid = :domain_uuid ";
sql = sql .. "and (extension = :extension or number_alias = :extension) ";
local params = {domain_uuid = domain_uuid, extension = user};
-- if (debug["sql"]) then
-- freeswitch.consoleLog("notice", "[feature_event] " .. sql .. "; params:" .. json.encode(params) .. "\n");
-- end
dbh:query(sql, params, function(row)
extension_uuid = row.extension_uuid;
extension = row.extension;
number_alias = row.number_alias or '';
accountcode = row.accountcode;
follow_me_uuid = row.follow_me_uuid;
do_not_disturb = row.do_not_disturb;
forward_all_enabled = row.forward_all_enabled;
forward_all_destination = row.forward_all_destination;
forward_busy_enabled = row.forward_busy_enabled;
forward_busy_destination = row.forward_busy_destination;
forward_no_answer_enabled = row.forward_no_answer_enabled;
forward_no_answer_destination = row.forward_no_answer_destination;
forward_user_not_registered_enabled = row.forward_user_not_registered_enabled;
forward_user_not_registered_destination = row.forward_user_not_registered_destination;
toll_allow = row.toll_allow
call_timeout = row.call_timeout
--freeswitch.consoleLog("NOTICE", "[feature_event] extension "..row.extension.."\n");
--freeswitch.consoleLog("NOTICE", "[feature_event] accountcode "..row.accountcode.."\n");
end);
--set some defaults if values in database are NULL
if (forward_all_enabled == "") then forward_all_enabled = "false"; end
--if (forward_all_destination == "") then forward_all_destination = nil; end
if (forward_busy_enabled == "") then forward_busy_enabled = "false"; end
if (forward_no_answer_enabled == "") then forward_no_answer_enabled = "false"; end
if (do_not_disturb == "") then do_not_disturb = "false"; end
if (call_timeout == "") then call_timeout = "30"; end
return do_not_disturb, forward_all_enabled, forward_all_destination, forward_busy_enabled, forward_busy_destination, forward_no_answer_enabled, forward_no_answer_destination, call_timeout
end
---@return table, nil
function feature_event_notify.get_profiles(user, domain)
--includes
require "resources.functions.trim"
local account = user.."@"..domain
--create the api object
api = freeswitch.API();
local sofia_contact = trim(api:executeString("sofia_contact */"..account));
--get all profiles for the user account
local profile_iterator = string.gmatch(sofia_contact, "sofia/([^,]+)/[^,]+");
--remove any duplicates and check if we have any profiles
local unique_profiles = {}
local has_profile = false
for profile in profile_iterator do
has_profile = true
unique_profiles[profile] = 1
end
-- return nil if we have no profiles
if not has_profile then return nil end
return unique_profiles
end
function feature_event_notify.dnd(user, host, sip_profiles, do_not_disturb)
--set the event and send it to each profile
for sip_profile, _ in pairs(sip_profiles) do
local event = freeswitch.Event("SWITCH_EVENT_PHONE_FEATURE")
event:addHeader("profile", sip_profile)
event:addHeader("user", user)
event:addHeader("host", host)
event:addHeader("device", "")
event:addHeader("Feature-Event", "DoNotDisturbEvent")
event:addHeader("doNotDisturbOn", do_not_disturb)
--freeswitch.consoleLog("notice","[events] " .. event:serialize("xml") .. "\n");
freeswitch.msleep(300);
event:fire()
end
end
function feature_event_notify.forward_immediate(user, host, sip_profiles, forward_immediate_enabled, forward_immediate_destination)
--set the event and send it to each profile
for sip_profile, _ in pairs(sip_profiles) do
local event = freeswitch.Event("SWITCH_EVENT_PHONE_FEATURE")
event:addHeader("profile", sip_profile)
event:addHeader("user", user)
event:addHeader("host", host)
event:addHeader("device", "")
event:addHeader("Feature-Event", "ForwardingEvent")
event:addHeader("forward_immediate_enabled", forward_immediate_enabled)
event:addHeader("forward_immediate", forward_immediate_destination);
freeswitch.consoleLog("notice","[events] " .. event:serialize("xml") .. "\n");
freeswitch.msleep(300);
event:fire()
end
end
function feature_event_notify.forward_busy(user, host, sip_profiles, forward_busy_enabled, forward_busy_destination)
--set the event and send it to each profile
for sip_profile, _ in pairs(sip_profiles) do
local event = freeswitch.Event("SWITCH_EVENT_PHONE_FEATURE")
event:addHeader("profile", sip_profile)
event:addHeader("user", user)
event:addHeader("host", host)
event:addHeader("device", "")
event:addHeader("Feature-Event", "ForwardingEvent")
event:addHeader("forward_busy", forward_busy_destination)
event:addHeader("forward_busy_enabled", forward_busy_enabled)
freeswitch.msleep(300);
event:fire()
end
end
function feature_event_notify.forward_no_answer(user, host, sip_profiles, forward_no_answer_enabled, forward_no_answer_destination, ring_count)
--set the event and send it to each profile
for sip_profile, _ in pairs(sip_profiles) do
local event = freeswitch.Event("SWITCH_EVENT_PHONE_FEATURE")
event:addHeader("profile", sip_profile)
event:addHeader("user", user)
event:addHeader("host", host)
event:addHeader("device", "")
event:addHeader("Feature-Event", "ForwardingEvent")
event:addHeader("forward_no_answer", forward_no_answer_destination)
event:addHeader("forward_no_answer_enabled", forward_no_answer_enabled)
event:addHeader("ringCount", ring_count)
freeswitch.msleep(300);
event:fire()
end
end
function feature_event_notify.init(user, host, sip_profiles, forward_immediate_enabled, forward_immediate_destination, forward_busy_enabled, forward_busy_destination, forward_no_answer_enabled, forward_no_answer_destination, ring_count, do_not_disturb)
--set the event and send it to each profile
for sip_profile, _ in pairs(sip_profiles) do
local event = freeswitch.Event("SWITCH_EVENT_PHONE_FEATURE")
event:addHeader("profile", sip_profile)
event:addHeader("user", user)
event:addHeader("host", host)
event:addHeader("device", "")
event:addHeader("Feature-Event", "init")
event:addHeader("forward_immediate_enabled", forward_immediate_enabled)
event:addHeader("forward_immediate", forward_immediate_destination);
event:addHeader("forward_busy", forward_busy_destination)
event:addHeader("forward_busy_enabled", forward_busy_enabled)
event:addHeader("Feature-Event", "ForwardingEvent")
event:addHeader("forward_no_answer", forward_no_answer_destination)
event:addHeader("forward_no_answer_enabled", forward_no_answer_enabled)
event:addHeader("ringCount", ring_count)
event:addHeader("Feature-Event", "DoNotDisturbEvent")
event:addHeader("doNotDisturbOn", do_not_disturb)
freeswitch.msleep(300);
event:fire()
end
end
return feature_event_notify
@@ -0,0 +1,567 @@
-- FusionPBX
-- Version: MPL 1.1
-- The contents of this file are subject to the Mozilla Public License Version
-- 1.1 (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
-- http://www.mozilla.org/MPL/
-- Software distributed under the License is distributed on an "AS IS" basis,
-- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-- for the specific language governing rights and limitations under the
-- License.
-- The Original Code is FusionPBX
-- The Initial Developer of the Original Code is
-- Mark J Crane <markjcrane@fusionpbx.com>
-- Portions created by the Initial Developer are Copyright (C) 2019 - 2020
-- the Initial Developer. All Rights Reserved.
--includes
local Database = require "resources.functions.database";
local route_to_bridge = require "resources.functions.route_to_bridge"
local Settings = require "resources.functions.lazy_settings"
require "resources.functions.trim";
require 'resources.functions.send_mail';
--get the variables
if (session:ready()) then
domain_name = session:getVariable("domain_name");
domain_uuid = session:getVariable("domain_uuid");
uuid = session:getVariable("uuid");
destination_number = session:getVariable("destination_number");
caller_id_name = session:getVariable("caller_id_name");
caller_id_number = session:getVariable("caller_id_number");
outbound_caller_id_name = session:getVariable("outbound_caller_id_name");
outbound_caller_id_number = session:getVariable("outbound_caller_id_number");
call_direction = session:getVariable("call_direction");
original_destination_number = session:getVariable("destination_number");
missed_call_app = session:getVariable("missed_call_app");
missed_call_data = session:getVariable("missed_call_data");
sip_to_user = session:getVariable("sip_to_user");
dialed_user = session:getVariable("dialed_user") or '';
end
--set caller id
if (effective_caller_id_name ~= nil) then
caller_id_name = effective_caller_id_name;
end
if (effective_caller_id_number ~= nil) then
caller_id_number = effective_caller_id_number;
end
--default to local if nil
if (call_direction == nil) then
call_direction = "local";
end
--connect to the database
local dbh = Database.new('system');
--set the strategy
local settings = Settings.new(dbh, domain_name, domain_uuid);
local follow_me_strategy = settings:get('follow_me', 'strategy', 'text') or 'enterprise'; --simultaneous, enterprise
--include json library
debug["sql"] = false;
local json
if (debug["sql"]) then
json = require "resources.functions.lunajson";
end
--prepare the api object
api = freeswitch.API();
--check the missed calls
function missed()
if (missed_call_app ~= nil and missed_call_data ~= nil) then
if (missed_call_app == "email") then
--set the sounds path for the language, dialect and voice
default_language = session:getVariable("default_language");
default_dialect = session:getVariable("default_dialect");
default_voice = session:getVariable("default_voice");
if (not default_language) then default_language = 'en'; end
if (not default_dialect) then default_dialect = 'us'; end
if (not default_voice) then default_voice = 'callie'; end
--connect to the database
local Database = require "resources.functions.database";
local dbh = Database.new('system');
--get the templates
local sql = "SELECT * FROM v_email_templates ";
sql = sql .. "WHERE (domain_uuid = :domain_uuid or domain_uuid is null) ";
sql = sql .. "AND template_language = :template_language ";
sql = sql .. "AND template_category = 'missed' "
sql = sql .. "AND template_enabled = 'true' "
sql = sql .. "ORDER BY domain_uuid DESC "
local params = {domain_uuid = domain_uuid, template_language = default_language.."-"..default_dialect};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
subject = row["template_subject"];
body = row["template_body"];
end);
--prepare the headers
local headers = {}
headers["X-FusionPBX-Domain-UUID"] = domain_uuid;
headers["X-FusionPBX-Domain-Name"] = domain_name;
headers["X-FusionPBX-Call-UUID"] = uuid;
headers["X-FusionPBX-Email-Type"] = 'missed';
--prepare the subject
subject = subject:gsub("${caller_id_name}", caller_id_name);
subject = subject:gsub("${caller_id_number}", caller_id_number);
subject = subject:gsub("${sip_to_user}", sip_to_user);
subject = subject:gsub("${dialed_user}", dialed_user);
subject = trim(subject);
subject = '=?utf-8?B?'..base64.encode(subject)..'?=';
--prepare the body
body = body:gsub("${caller_id_name}", caller_id_name);
body = body:gsub("${caller_id_number}", caller_id_number);
body = body:gsub("${sip_to_user}", sip_to_user);
body = body:gsub("${dialed_user}", dialed_user);
body = body:gsub(" ", "&nbsp;");
body = body:gsub("%s+", "");
body = body:gsub("&nbsp;", " ");
body = body:gsub("\n", "");
body = body:gsub("\n", "");
body = body:gsub("'", "&#39;");
body = body:gsub([["]], "&#34;");
body = trim(body);
--send the emails
send_mail(headers,
missed_call_data,
{subject, body}
);
if (debug["info"]) then
freeswitch.consoleLog("notice", "[missed call] " .. caller_id_number .. "->" .. sip_to_user .. "emailed to " .. missed_call_data .. "\n");
end
end
end
end
--get the destination and follow the forward
function get_forward_all(count, destination_number, domain_name)
cmd = "user_exists id ".. destination_number .." "..domain_name;
--freeswitch.consoleLog("notice", "[follow me][call forward all] " .. cmd .. "\n");
user_exists = api:executeString(cmd);
if (user_exists == "true") then
---check to see if the new destination is forwarded
cmd = "user_data ".. destination_number .."@" ..domain_name.." var forward_all_enabled";
if (api:executeString(cmd) == "true") then
--get the toll_allow var
cmd = "user_data ".. destination_number .."@" ..domain_name.." var toll_allow";
toll_allow = api:executeString(cmd);
--freeswitch.consoleLog("notice", "[follow me][call forward all] " .. destination_number .. " toll_allow is ".. toll_allow .."\n");
--get the extensions accountcode
cmd = "user_data ".. destination_number .."@" ..domain_name.." var accountcode";
accountcode = api:executeString(cmd);
--get the new destination
cmd = "user_data ".. destination_number .."@" ..domain_name.." var forward_all_destination";
destination_number = api:executeString(cmd);
--freeswitch.consoleLog("notice", "[follow me][call forward all] " .. count .. " " .. cmd .. " ".. destination_number .."\n");
count = count + 1;
if (count < 5) then
count, destination_number, toll_allow, accountcode = get_forward_all(count, destination_number, domain_name);
end
end
end
return count, destination_number, toll_allow, accountcode;
end
--get the forward busy
--cmd = "user_data ".. destination_number .."@"..domain_name.." var forward_busy_enabled=";
--forward_busy_enabled = trim(api:executeString(cmd));
--cmd = "user_data ".. destination_number .."@"..domain_name.." var forward_busy_destination=";
--forward_busy_destination = trim(api:executeString(cmd));
--get the domain_uuid
if (domain_uuid == nil) then
if (domain_name ~= nil) then
local sql = "SELECT domain_uuid FROM v_domains "
.. "WHERE domain_name = :domain_name ";
local params = {domain_name = domain_name};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[xml_handler] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(rows)
domain_uuid = rows["domain_uuid"];
end);
end
end
--select data from the database
local sql = "select follow_me_uuid, toll_allow, accountcode ";
sql = sql .. "from v_extensions ";
sql = sql .. "where domain_uuid = :domain_uuid ";
sql = sql .. "and ( ";
sql = sql .. " extension = :destination_number ";
sql = sql .. " OR number_alias = :destination_number ";
sql = sql .. ") ";
local params = {domain_uuid = domain_uuid,destination_number = destination_number};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "SQL:" .. sql .. "; params: " .. json.encode(params) .. "\n");
end
status = dbh:query(sql, params, function(row)
follow_me_uuid = row["follow_me_uuid"];
extension_toll_allow = row["toll_allow"];
accountcode = row["accountcode"];
end);
--dbh:query(sql, params, function(row);
--get the follow me data
if (follow_me_uuid ~= nil) then
local sql = "select cid_name_prefix, cid_number_prefix, ";
sql = sql .. "follow_me_enabled, follow_me_ignore_busy ";
sql = sql .. "from v_follow_me ";
sql = sql .. "where domain_uuid = :domain_uuid ";
sql = sql .. "and follow_me_uuid = :follow_me_uuid; ";
local params = {domain_uuid = domain_uuid,follow_me_uuid = follow_me_uuid};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "SQL:" .. sql .. "; params: " .. json.encode(params) .. "\n");
end
status = dbh:query(sql, params, function(row)
caller_id_name_prefix = row["cid_name_prefix"];
caller_id_number_prefix = row["cid_number_prefix"];
follow_me_enabled = row["follow_me_enabled"];
follow_me_ignore_busy = row["follow_me_ignore_busy"];
end);
--dbh:query(sql, params, function(row);
end
--get the follow me destinations
if (follow_me_uuid ~= nil) then
sql = "select d.domain_uuid, d.domain_name, f.follow_me_destination as destination_number, ";
sql = sql .. "f.follow_me_delay * 1000 as destination_delay, f.follow_me_timeout as destination_timeout, ";
sql = sql .. "f.follow_me_prompt as destination_prompt ";
sql = sql .. "from v_follow_me_destinations as f, v_domains as d ";
sql = sql .. "where f.follow_me_uuid = :follow_me_uuid ";
sql = sql .. "and f.domain_uuid = d.domain_uuid ";
sql = sql .. "order by f.follow_me_order; ";
local params = {follow_me_uuid = follow_me_uuid};
destinations = {};
destination_count = 0;
x = 1;
dbh:query(sql, params, function(row)
domain_uuid = row["domain_uuid"];
domain_name = row["domain_name"];
if (row.destination_prompt == "1" or row.destination_prompt == "2") then
prompt = "true";
end
--follow the forwards
count, destination_number, toll_allow, accountcode = get_forward_all(0, row.destination_number, domain_name);
--update values
row['destination_number'] = destination_number
--row['toll_allow'] = toll_allow;
--check if the user exists
cmd = "user_exists id ".. destination_number .." "..domain_name;
user_exists = api:executeString(cmd);
--cmd = "user_exists id ".. destination_number .." "..domain_name;
if (user_exists == "true") then
--add user_exists true or false to the row array
row['user_exists'] = "true";
--handle do_not_disturb
cmd = "user_data ".. destination_number .."@" ..domain_name.." var do_not_disturb";
if (api:executeString(cmd) ~= "true") then
--add the row to the destinations array
destinations[x] = row;
end
else
--set the values
external = "true";
row['user_exists'] = "false";
if (accountcode ~= nil) then
row['accountcode'] = accountcode;
else
row['accountcode'] = domain_name;
end
--add the row to the destinations array
destinations[x] = row;
end
row['domain_name'] = domain_name;
destination_count = destination_count + 1;
x = x + 1;
end);
end
--get the dialplan data and save it to a table
if (external == "true") then
dialplans = route_to_bridge.preload_dialplan(
dbh, domain_uuid, {hostname = hostname, context = context}
)
end
--prepare the array of destinations
x = 1;
for key, row in pairs(destinations) do
--set the values from the database as variables
destination_number = row.destination_number;
--determine if the user is registered if not registered then lookup
if (user_exists == "true") then
cmd = "sofia_contact */".. destination_number .."@" ..domain_name;
if (api:executeString(cmd) == "error/user_not_registered") then
freeswitch.consoleLog("NOTICE", "[follow_me] "..cmd.."\n");
cmd = "user_data ".. destination_number .."@" ..domain_name.." var forward_user_not_registered_enabled";
freeswitch.consoleLog("NOTICE", "[follow_me] "..cmd.."\n");
if (api:executeString(cmd) == "true") then
--get the new destination number
cmd = "user_data ".. destination_number .."@" ..domain_name.." var forward_user_not_registered_destination";
freeswitch.consoleLog("NOTICE", "[follow_me] "..cmd.."\n");
not_registered_destination_number = api:executeString(cmd);
freeswitch.consoleLog("NOTICE", "[follow_me] "..not_registered_destination_number.."\n");
if (not_registered_destination_number ~= nil) then
destination_number = not_registered_destination_number;
destinations[key]['destination_number'] = destination_number;
end
end
end
end
end
--process the destinations
x = 1;
for key, row in pairs(destinations) do
freeswitch.consoleLog("NOTICE", "[follow me] destination_number: "..row.destination_number.."\n");
end
--process the destinations
x = 1;
for key, row in pairs(destinations) do
--set the values from the database as variables
domain_uuid = row.domain_uuid;
destination_number = row.destination_number;
destination_delay = row.destination_delay;
destination_timeout = row.destination_timeout;
destination_prompt = row.destination_prompt;
group_confirm_key = row.group_confirm_key;
group_confirm_file = row.group_confirm_file;
toll_allow = row.toll_allow;
accountcode = row.accountcode;
user_exists = row.user_exists;
--follow the forwards
count, destination_number = get_forward_all(0, destination_number, domain_name);
--check if the user exists
cmd = "user_exists id ".. destination_number .." "..domain_name;
user_exists = api:executeString(cmd);
--set ringback
--follow_me_ringback = format_ringback(follow_me_ringback);
--session:setVariable("ringback", follow_me_ringback);
--session:setVariable("transfer_ringback", follow_me_ringback);
--set the timeout if there is only one destination
if (session:ready() and destination_count == 1) then
session:execute("set", "call_timeout="..row.destination_timeout);
end
--setup the delimiter
delimiter = ",";
if (follow_me_strategy == "simultaneous") then
delimiter = ",";
end
if (follow_me_strategy == "enterprise") then
delimiter = ":_:";
end
--leg delay settings
if (follow_me_strategy == "enterprise") then
timeout_name = "originate_timeout";
delay_name = "originate_delay_start";
--destination_delay = destination_delay * 500;
else
timeout_name = "leg_timeout";
delay_name = "leg_delay_start";
end
--set confirm
if (session:ready() and follow_me_strategy == "simultaneous") then
session:execute("set", "group_confirm_key=exec");
session:execute("set", "group_confirm_file=lua ".. scripts_dir:gsub('\\','/') .."/confirm.lua");
end
--determine confirm prompt
if (destination_prompt == nil) then
group_confirm = "confirm=false,";
elseif (destination_prompt == "1") then
group_confirm = "group_confirm_key=exec,group_confirm_file=lua ".. scripts_dir:gsub('\\','/') .."/confirm.lua,confirm=true";
elseif (destination_prompt == "2") then
group_confirm = "group_confirm_key=exec,group_confirm_file=lua ".. scripts_dir:gsub('\\','/') .."/confirm.lua,confirm=true";
else
group_confirm = "confirm=false";
end
--process according to user_exists, sip_uri, external number
if (user_exists == "true") then
--get the extension_uuid
cmd = "user_data ".. destination_number .."@"..domain_name.." var extension_uuid";
extension_uuid = trim(api:executeString(cmd));
local record_session = "";
--if session is already recording then skip
if (session:getVariable("record_session") ~= "true") then
local cmd = "user_data "..destination_number.."@"..domain_name.." var user_record";
local user_record = api:executeString(cmd);
if (user_record == "all" or user_record == call_direction) then
local recordings_dir = session:getVariable("recordings_dir");
local record_ext = session:getVariable("record_ext") or "wav";
local record_name = uuid.."."..record_ext;
local record_path = recordings_dir .. "/" .. domain_name .. "/archive/" .. os.date("%Y/%b/%d");
record_session = ",api_on_answer='uuid_record "..uuid.." start ".. record_path .. "/" .. record_name .. "',record_path='".. record_path .."',record_name="..record_name;
end
end
--send to user
local dial_string_to_user = "[sip_invite_domain="..domain_name..",call_direction="..call_direction..","..group_confirm..","..timeout_name.."="..destination_timeout..","..delay_name.."="..destination_delay..",dialed_extension=" .. row.destination_number .. ",extension_uuid="..extension_uuid..record_session.."]user/" .. row.destination_number .. "@" .. domain_name;
dial_string = dial_string_to_user;
elseif (tonumber(destination_number) == nil) then
--sip uri
dial_string = "[sip_invite_domain="..domain_name..",call_direction="..call_direction..","..group_confirm..","..timeout_name.."="..destination_timeout..","..delay_name.."="..destination_delay.."]" .. row.destination_number;
else
--external number
route_bridge = 'loopback/'..destination_number;
if (extension_toll_allow ~= nil) then
toll_allow = extension_toll_allow:gsub(",", ":");
end
--set the toll allow to an empty string
if (toll_allow == nil) then
toll_allow = '';
end
--check if the user exists
if tonumber(caller_id_number) ~= nil then
cmd = "user_exists id ".. caller_id_number .." "..domain_name;
caller_is_local = api:executeString(cmd);
end
--set the outbound caller id
ignore_outbound_caller_id = session:getVariable("follow_me_ignore_outbound_caller_id");
if (session:ready() and caller_is_local and ignore_outbound_caller_id ~= "true") then
if (outbound_caller_id_name ~= nil) then
caller_id_name = outbound_caller_id_name;
end
if (outbound_caller_id_number ~= nil) then
caller_id_number = outbound_caller_id_number;
end
end
--set the caller id
caller_id = '';
if (caller_id_name ~= nil) then
caller_id = "origination_caller_id_name='"..caller_id_name.."'"
end
if (caller_id_number ~= nil) then
caller_id = caller_id .. ",origination_caller_id_number="..caller_id_number;
end
--set the destination dial string
-- have to double destination_delay here due a FS bug requiring a 50% delay value for internal extensions, but not external calls.
--destination_delay = destination_delay * 2;
dial_string = "[toll_allow=".. toll_allow ..",accountcode="..accountcode..",".. caller_id ..",sip_invite_domain="..domain_name..",domain_uuid="..domain_uuid..",call_direction="..call_direction..","..group_confirm..","..timeout_name.."="..destination_timeout..","..delay_name.."="..destination_delay.."]"..route_bridge
end
--add a delimiter between destinations
if (dial_string ~= nil) then
--freeswitch.consoleLog("notice", "[follow me] dial_string: " .. dial_string .. "\n");
if (x == 1) then
if (follow_me_strategy == "enterprise") then
app_data = dial_string;
else
app_data = "{ignore_early_media=true}"..dial_string;
end
else
if (app_data == nil) then
if (follow_me_strategy == "enterprise") then
app_data = dial_string;
else
app_data = "{ignore_early_media=true}"..dial_string;
end
else
app_data = app_data .. delimiter .. dial_string;
end
end
end
--increment the value of x
x = x + 1;
end
--set ring ready
if (session:ready()) then
session:execute("ring_ready", "");
end
--send to the console
freeswitch.consoleLog("notice", "[app:follow_me] " .. destination_number .. "\n");
--session execute
if (session:ready()) then
--set the variables
session:execute("set", "hangup_after_bridge=true");
session:execute("set", "continue_on_fail=true");
--execute the bridge
if (app_data ~= nil) then
if (follow_me_strategy == "enterprise") then
app_data = app_data:gsub("%[", "{");
app_data = app_data:gsub("%]", "}");
end
freeswitch.consoleLog("NOTICE", "[follow me] app_data: "..app_data.."\n");
session:execute("bridge", app_data);
end
--timeout destination
if (app_data ~= nil) then
if (
session:getVariable("originate_disposition") == "ALLOTTED_TIMEOUT"
or session:getVariable("originate_disposition") == "NO_ANSWER"
or session:getVariable("originate_disposition") == "NO_USER_RESPONSE"
or session:getVariable("originate_disposition") == "USER_NOT_REGISTERED"
or session:getVariable("originate_disposition") == "NORMAL_TEMPORARY_FAILURE"
or session:getVariable("originate_disposition") == "NO_ROUTE_DESTINATION"
or session:getVariable("originate_disposition") == "USER_BUSY"
or session:getVariable("originate_disposition") == "RECOVERY_ON_TIMER_EXPIRE"
or session:getVariable("originate_disposition") == "failure"
) then
if session:ready() then
--get the forward no answer
cmd = "user_data ".. original_destination_number .."@"..domain_name.." var forward_no_answer_enabled";
forward_no_answer_enabled = trim(api:executeString(cmd));
cmd = "user_data ".. original_destination_number .."@"..domain_name.." var forward_no_answer_destination";
forward_no_answer_destination = trim(api:executeString(cmd));
cmd = "user_data ".. original_destination_number .."@"..domain_name.." var user_context";
user_context = trim(api:executeString(cmd));
--execute the time out action
if (forward_no_answer_enabled == 'true') then
session:transfer(forward_no_answer_destination, 'XML', user_context);
else
session:transfer('*99' .. original_destination_number, 'XML', user_context);
end
end
--check and report missed call
missed();
end
end
end
@@ -0,0 +1,164 @@
--
-- FusionPBX
-- Version: MPL 1.1
--
-- The contents of this file are subject to the Mozilla Public License Version
-- 1.1 (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
-- http://www.mozilla.org/MPL/
--
-- Software distributed under the License is distributed on an "AS IS" basis,
-- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-- for the specific language governing rights and limitations under the
-- License.
--
-- The Original Code is FusionPBX
--
-- The Initial Developer of the Original Code is
-- Mark J Crane <markjcrane@fusionpbx.com>
-- Copyright (C) 2015 - 2018
-- the Initial Developer. All Rights Reserved.
--set the debug options
debug["params"] = false;
debug["info"] = false;
debug["sql"] = false;
--general functions
require "resources.functions.config";
require "resources.functions.explode";
require "resources.functions.trim";
require "resources.functions.base64";
require "resources.functions.file_exists";
--load libraries
require 'resources.functions.send_mail'
--create the api object
api = freeswitch.API();
--check the missed calls
function missed()
if (missed_call_app ~= nil and missed_call_data ~= nil) then
if (missed_call_app == "email") then
--set the sounds path for the language, dialect and voice
default_language = env:getHeader("default_language");
default_dialect = env:getHeader("default_dialect");
default_voice = env:getHeader("default_voice");
if (not default_language) then default_language = 'en'; end
if (not default_dialect) then default_dialect = 'us'; end
if (not default_voice) then default_voice = 'callie'; end
--connect to the database
local Database = require "resources.functions.database";
local dbh = Database.new('system');
--prepare to get the settings
local Settings = require "resources.functions.lazy_settings"
local settings = Settings.new(dbh, domain_name, domain_uuid)
local from_address = settings:get('email', 'smtp_from', 'text');
--get the templates
local sql = "SELECT * FROM v_email_templates ";
sql = sql .. "WHERE (domain_uuid = :domain_uuid or domain_uuid is null) ";
sql = sql .. "AND template_language = :template_language ";
sql = sql .. "AND template_category = 'missed' "
sql = sql .. "AND template_enabled = 'true' "
sql = sql .. "ORDER BY domain_uuid DESC "
local params = {domain_uuid = domain_uuid, template_language = default_language.."-"..default_dialect};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
subject = row["template_subject"];
body = row["template_body"];
end);
--prepare the headers
local headers = {}
headers["X-FusionPBX-Domain-UUID"] = domain_uuid;
headers["X-FusionPBX-Domain-Name"] = domain_name;
headers["X-FusionPBX-Call-UUID"] = uuid;
headers["X-FusionPBX-Email-Type"] = 'missed';
--remove quotes from caller id name and number
caller_id_name = caller_id_name:gsub("'", "&#39;");
caller_id_name = caller_id_name:gsub([["]], "&#34;");
caller_id_number = caller_id_number:gsub("'", "&#39;");
caller_id_number = caller_id_number:gsub([["]], "&#34;");
--prepare the subject
subject = subject:gsub("${caller_id_name}", caller_id_name);
subject = subject:gsub("${caller_id_number}", caller_id_number);
subject = subject:gsub("${sip_to_user}", sip_to_user);
subject = subject:gsub("${dialed_user}", dialed_user);
subject = trim(subject);
subject = '=?utf-8?B?'..base64.encode(subject)..'?=';
--prepare the body
body = body:gsub("${caller_id_name}", caller_id_name);
body = body:gsub("${caller_id_number}", caller_id_number);
body = body:gsub("${sip_to_user}", sip_to_user);
body = body:gsub("${dialed_user}", dialed_user);
body = body:gsub(" ", "&nbsp;");
body = body:gsub("%s+", "");
body = body:gsub("&nbsp;", " ");
body = body:gsub("\n", "");
body = body:gsub("\n", "");
body = trim(body);
--send the emails
send_mail(headers,
from_address,
missed_call_data,
{subject, body}
);
if (debug["info"]) then
freeswitch.consoleLog("notice", "[missed call] " .. caller_id_name .. " " .. caller_id_number .. " " .. sip_to_user .. "emailed to " .. missed_call_data .. "\n");
end
end
end
end
-- show all channel variables
--serialized = env:serialize()
--freeswitch.consoleLog("INFO","[hangup]\n" .. serialized .. "\n")
-- set channel variables to lua variables
originate_disposition = env:getHeader("originate_disposition");
originate_causes = env:getHeader("originate_causes");
uuid = env:getHeader("uuid");
domain_uuid = env:getHeader("domain_uuid");
domain_name = env:getHeader("domain_name");
sip_to_user = env:getHeader("sip_to_user");
dialed_user = env:getHeader("dialed_user");
missed_call_app = env:getHeader("missed_call_app");
missed_call_data = env:getHeader("missed_call_data");
call_direction = env:getHeader("call_direction");
-- get the Caller ID
caller_id_name = env:getHeader("caller_id_name");
caller_id_number = env:getHeader("caller_id_number");
if (caller_id_name == nil) then
caller_id_name = env:getHeader("Caller-Caller-ID-Name");
end
if (caller_id_number == nil) then
caller_id_number = env:getHeader("Caller-Caller-ID-Number");
end
if (call_direction == "local") then
caller_id_name = env:getHeader("effective_caller_id_name");
end
--show the logs
if (debug["info"] == true) then
freeswitch.consoleLog("INFO", "[hangup] originate_causes: " .. tostring(originate_causes) .. "\n");
freeswitch.consoleLog("INFO", "[hangup] originate_disposition: " .. tostring(originate_disposition) .. "\n");
end
--handle originate disposition
if (originate_disposition ~= nil) then
if (originate_disposition == "ORIGINATOR_CANCEL") then
missed();
end
end
@@ -0,0 +1,140 @@
-- FusionPBX
-- Version: MPL 1.1
-- The contents of this file are subject to the Mozilla Public License Version
-- 1.1 (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
-- http://www.mozilla.org/MPL/
-- Software distributed under the License is distributed on an "AS IS" basis,
-- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-- for the specific language governing rights and limitations under the
-- License.
-- The Original Code is FusionPBX
-- The Initial Developer of the Original Code is
-- Mark J Crane <markjcrane@fusionpbx.com>
-- Portions created by the Initial Developer are Copyright (C) 2014-2020
-- the Initial Developer. All Rights Reserved.
--set defaults
expire = {}
expire["is_local"] = "3600";
--get the variables
domain_name = session:getVariable("domain_name");
destination_number = session:getVariable("destination_number");
outbound_caller_id_name = session:getVariable("outbound_caller_id_name");
outbound_caller_id_number = session:getVariable("outbound_caller_id_number");
--includes
local cache = require "resources.functions.cache"
--include json library
local json
if (debug["sql"]) then
json = require "resources.functions.lunajson"
end
--prepare the api object
api = freeswitch.API();
--define the trim function
require "resources.functions.trim";
--set the cache key
key = "app:dialplan:outbound:is_local:" .. destination_number .. "@" .. domain_name;
--get the destination number
value, err = cache.get(key);
if (err == 'NOT FOUND') then
--connect to the database
local Database = require "resources.functions.database";
local dbh = Database.new('system');
--select data from the database
local sql = "SELECT destination_number, destination_context ";
sql = sql .. "FROM v_destinations ";
sql = sql .. "WHERE ( ";
sql = sql .. " destination_prefix || destination_area_code || destination_number = :destination_number ";
sql = sql .. " OR destination_trunk_prefix || destination_area_code || destination_number = :destination_number ";
sql = sql .. " OR destination_prefix || destination_number = :destination_number ";
sql = sql .. " OR '+' || destination_prefix || destination_number = :destination_number ";
sql = sql .. " OR '+' || destination_prefix || destination_area_code || destination_number = :destination_number ";
sql = sql .. " OR destination_area_code || destination_number = :destination_number ";
sql = sql .. " OR destination_number = :destination_number ";
sql = sql .. ") ";
sql = sql .. "AND destination_type = 'inbound' ";
sql = sql .. "AND destination_enabled = 'true' ";
local params = {destination_number = destination_number};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "SQL:" .. sql .. "; params: " .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
--set the local variables
destination_context = row.destination_context;
--set the cache
if (destination_number == row.destination_number) then
key = "app:dialplan:outbound:is_local:" .. destination_number .. "@" .. domain_name;
value = "destination_number=" .. row.destination_number .. "&destination_context=" .. destination_context;
ok, err = cache.set(key, value, expire["is_local"]);
else
key = "app:dialplan:outbound:is_local:" .. destination_number .. "@" .. domain_name;
value = "destination_number=" .. row.destination_number .. "&destination_context=" .. destination_context;
ok, err = cache.set(key, value, expire["is_local"]);
end
--log the result
freeswitch.consoleLog("notice", "[app:dialplan:outbound:is_local] " .. row.destination_number .. " XML " .. destination_context .. " source: database\n");
--set the outbound caller id
if (outbound_caller_id_name ~= nil) then
session:execute("set", "caller_id_name="..outbound_caller_id_name);
session:execute("set", "effective_caller_id_name="..outbound_caller_id_name);
end
if (outbound_caller_id_number ~= nil) then
session:execute("set", "caller_id_number="..outbound_caller_id_number);
session:execute("set", "effective_caller_id_number="..outbound_caller_id_number);
end
--transfer the call
session:transfer(row.destination_number, "XML", row.destination_context);
end);
else
--add the function
require "resources.functions.explode";
--define the array/table
local var = {}
--parse the cache
key_pairs = explode("&", value);
for k,v in pairs(key_pairs) do
f = explode("=", v);
key = f[1];
value = f[2];
var[key] = value;
end
--set the outbound caller id
if (outbound_caller_id_name ~= nil) then
session:execute("set", "caller_id_name="..outbound_caller_id_name);
session:execute("set", "effective_caller_id_name="..outbound_caller_id_name);
end
if (outbound_caller_id_number ~= nil) then
session:execute("set", "caller_id_number="..outbound_caller_id_number);
session:execute("set", "effective_caller_id_number="..outbound_caller_id_number);
end
--send to the console
freeswitch.consoleLog("notice", "[app:dialplan:outbound:is_local] " .. value .. " source: cache\n");
--transfer the call
session:transfer(var["destination_number"], "XML", var["destination_context"]);
end
@@ -0,0 +1 @@
Missed Call from ${caller_id_name} &lt;${caller_id_number}&gt; to ${sip_to_user} ext ${dialed_user}
@@ -0,0 +1 @@
Missed Call from ${caller_id_name} <${caller_id_number}>
@@ -0,0 +1,256 @@
-- Part of FusionPBX
-- Copyright (C) 2015-2018 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--set the debug options
debug["sql"] = false;
--define the explode function
require "resources.functions.explode";
require "resources.functions.trim";
--set the defaults
max_tries = 3;
digit_timeout = 5000;
max_retries = 3;
tries = 0;
--connect to the database
local Database = require "resources.functions.database";
dbh = Database.new('system');
--include json library
local json
if (debug["sql"]) then
json = require "resources.functions.lunajson"
end
--answer
session:answer();
--sleep
session:sleep(500);
--get the domain_uuid
domain_uuid = session:getVariable("domain_uuid");
--get the variables
action = session:getVariable("action");
reboot = session:getVariable("reboot");
--set defaults
if (not reboot) then reboot = 'true'; end
--set the sounds path for the language, dialect and voice
default_language = session:getVariable("default_language");
default_dialect = session:getVariable("default_dialect");
default_voice = session:getVariable("default_voice");
if (not default_language) then default_language = 'en'; end
if (not default_dialect) then default_dialect = 'us'; end
if (not default_voice) then default_voice = 'callie'; end
--get the user id
min_digits = 2;
max_digits = 20;
user_id = session:playAndGetDigits(min_digits, max_digits, max_tries, digit_timeout, "#", "phrase:voicemail_enter_id:#", "", "\\d+");
--user_id = session:playAndGetDigits(min_digits, max_digits, max_tries, digit_timeout, "#", sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/ivr/ivr-please_enter_extension_followed_by_pound.wav", "", "\\d+");
--get the user password
min_digits = 2;
max_digits = 20;
password = session:playAndGetDigits(min_digits, max_digits, max_tries, digit_timeout, "#", "phrase:voicemail_enter_pass:#", "", "\\d+");
--password = session:playAndGetDigits(min_digits, max_digits, max_tries, digit_timeout, "#", sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/ivr/ivr-please_enter_pin_followed_by_pound.wav", "", "\\d+");
--get the user and domain name from the user argv user@domain
sip_from_uri = session:getVariable("sip_from_uri");
user_table = explode("@",sip_from_uri);
domain_table = explode(":",user_table[2]);
user = user_table[1];
domain = domain_table[1];
--show the phone that will be overridden
if (sip_from_uri ~= nil) then
freeswitch.consoleLog("NOTICE", "[provision] sip_from_uri: ".. sip_from_uri .. "\n");
end
if (user ~= nil) then
freeswitch.consoleLog("NOTICE", "[provision] user: ".. user .. "\n");
end
if (domain ~= nil) then
freeswitch.consoleLog("NOTICE", "[provision] domain: ".. domain .. "\n");
end
--get the device uuid for the phone that will have its configuration overridden
if (user ~= nil and domain ~= nil and domain_uuid ~= nil) then
local sql = [[SELECT device_uuid FROM v_device_lines ]];
sql = sql .. [[WHERE user_id = :user ]];
sql = sql .. [[AND server_address = :domain ]];
sql = sql .. [[AND domain_uuid = :domain_uuid ]];
local params = {user = user, domain = domain, domain_uuid = domain_uuid};
if (debug["sql"]) then
freeswitch.consoleLog("NOTICE", "[provision] SQL: ".. sql .. "; params: " .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
--get device uuid
device_uuid = row.device_uuid;
freeswitch.consoleLog("NOTICE", "[provision] device_uuid: ".. device_uuid .. "\n");
end);
end
--get the alternate device uuid using the device username and password
authorized = 'false';
if (user_id ~= nil and password ~= nil and domain_uuid ~= nil) then
local sql = [[SELECT device_uuid FROM v_devices ]];
sql = sql .. [[WHERE device_username = :user_id ]];
sql = sql .. [[AND device_password = :password ]]
sql = sql .. [[AND domain_uuid = :domain_uuid ]];
local params = {user_id = user_id, password = password, domain_uuid = domain_uuid};
if (debug["sql"]) then
freeswitch.consoleLog("NOTICE", "[provision] SQL: ".. sql .. "; params: " .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
--get the alternate device_uuid
device_uuid_alternate = row.device_uuid;
freeswitch.consoleLog("NOTICE", "[provision] alternate device_uuid: ".. device_uuid_alternate .. "\n");
--authorize the user
authorized = 'true';
end);
end
--authentication failed
if (authorized == 'false') then
result = session:streamFile(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/voicemail/vm-fail_auth.wav");
end
--this device already has an alternate find the correct device_uuid and then override current one
if (authorized == 'true' and action == "login" and device_uuid_alternate ~= nil and device_uuid ~= nil and domain_uuid ~= nil) then
local sql = [[SELECT * FROM v_devices ]];
sql = sql .. [[WHERE device_uuid_alternate = :device_uuid ]];
sql = sql .. [[AND domain_uuid = :domain_uuid ]];
local params = {device_uuid = device_uuid, domain_uuid = domain_uuid};
if (debug["sql"]) then
freeswitch.consoleLog("NOTICE", "[provision] SQL: ".. sql .. "; params: " .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
if (row.device_uuid_alternate ~= nil) then
device_uuid = row.device_uuid;
end
end);
end
--remove the alternate device from another device so that it can be added to this device
if (authorized == 'true' and action == "login" and device_uuid_alternate ~= nil and domain_uuid ~= nil) then
local sql = [[SELECT * FROM v_device_lines ]];
sql = sql .. [[WHERE device_uuid = :device_uuid ]];
sql = sql .. [[AND domain_uuid = :domain_uuid ]];
local params = {device_uuid = device_uuid_alternate, domain_uuid = domain_uuid};
if (debug["sql"]) then
freeswitch.consoleLog("NOTICE", "[provision] SQL: ".. sql .. "; params: " .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
--remove the previous alternate device uuid
local sql = [[UPDATE v_devices SET device_uuid_alternate = null ]];
sql = sql .. [[WHERE device_uuid_alternate = :device_uuid_alternate ]];
sql = sql .. [[AND domain_uuid = :domain_uuid ]];
local params = {device_uuid_alternate = device_uuid_alternate, domain_uuid = domain_uuid};
if (debug["sql"]) then
freeswitch.consoleLog("NOTICE", "[provision] SQL: ".. sql .. "; params: " .. json.encode(params) .. "\n");
end
dbh:query(sql, params);
--get the sip profile
api = freeswitch.API();
local sofia_contact = trim(api:executeString("sofia_contact */"..row.user_id.."@"..row.server_address));
array = explode("/", sofia_contact);
profile = array[2];
freeswitch.consoleLog("NOTICE", "[provision] profile: ".. profile .. "\n");
--send a sync command to the previous device
--create the event notify object
local event = freeswitch.Event('NOTIFY');
--add the headers
event:addHeader('profile', profile);
event:addHeader('user', row.user_id);
event:addHeader('host', row.server_address);
event:addHeader('content-type', 'application/simple-message-summary');
--check sync
event:addHeader('event-string', 'check-sync;reboot='..reboot);
--event:addHeader('event-string', 'resync');
--send the event
event:fire();
end);
end
--add the override to the device uuid (login)
if (authorized == 'true' and action == "login") then
if (device_uuid_alternate ~= nil and device_uuid ~= nil and domain_uuid ~= nil) then
--send a hangup
session:hangup();
--add the new alternate
local sql = [[UPDATE v_devices SET device_uuid_alternate = :device_uuid_alternate ]];
sql = sql .. [[WHERE device_uuid = :device_uuid ]];
sql = sql .. [[AND domain_uuid = :domain_uuid ]];
local params = {device_uuid_alternate = device_uuid_alternate,
device_uuid = device_uuid, domain_uuid = domain_uuid};
if (debug["sql"]) then
freeswitch.consoleLog("NOTICE", "[provision] SQL: ".. sql .. "; params: " .. json.encode(params) .. "\n");
end
dbh:query(sql, params);
end
end
--remove the override to the device uuid (logout)
if (authorized == 'true' and action == "logout") then
if (device_uuid_alternate ~= nil and device_uuid ~= nil and domain_uuid ~= nil) then
local sql = [[UPDATE v_devices SET device_uuid_alternate = null ]];
sql = sql .. [[WHERE device_uuid_alternate = :device_uuid ]];
sql = sql .. [[AND domain_uuid = :domain_uuid ]];
local params = {device_uuid = device_uuid, domain_uuid = domain_uuid};
if (debug["sql"]) then
freeswitch.consoleLog("NOTICE", "[provision] sql: ".. sql .. "; params: " .. json.encode(params) .. "\n");
end
dbh:query(sql, params);
end
end
--found the device send a sync command
if (authorized == 'true') then
--get the sip profile
api = freeswitch.API();
local sofia_contact = trim(api:executeString("sofia_contact */"..user.."@"..domain));
array = explode("/", sofia_contact);
profile = array[2];
freeswitch.consoleLog("NOTICE", "[provision] profile: ".. profile .. "\n");
--send a hangup
session:hangup();
--create the event notify object
local event = freeswitch.Event('NOTIFY');
--add the headers
event:addHeader('profile', profile);
event:addHeader('user', user);
event:addHeader('host', domain);
event:addHeader('content-type', 'application/simple-message-summary');
--check sync
event:addHeader('event-string', 'check-sync;reboot='..reboot);
--event:addHeader('event-string', 'resync');
--send the event
event:fire();
end
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,199 @@
--
-- FusionPBX
-- Version: MPL 1.1
--
-- The contents of this file are subject to the Mozilla Public License Version
-- 1.1 (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
-- http://www.mozilla.org/MPL/
--
-- Software distributed under the License is distributed on an "AS IS" basis,
-- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-- for the specific language governing rights and limitations under the
-- License.
--
-- The Original Code is FusionPBX
--
-- The Initial Developer of the Original Code is
-- Mark J Crane <markjcrane@fusionpbx.com>
-- Copyright (C) 2010-2021
-- the Initial Developer. All Rights Reserved.
--
-- Contributor(s):
-- Mark J Crane <markjcrane@fusionpbx.com>
--set default variables
local min_digits = 3;
local max_digits = 11;
local max_tries = 3;
local digit_timeout = 3000;
--include config.lua
require "resources.functions.config";
--include libraries
require "resources.functions.channel_utils";
local log = require "resources.functions.log".ring_group_call_forward
local Database = require "resources.functions.database"
local blf = require "resources.functions.blf"
--include json library
local json
if (debug["sql"]) then
json = require "resources.functions.lunajson"
end
local function empty(t)
return (not t) or (#t == 0)
end
--check if the session is ready
if not session:ready() then return end
--answer the call
session:answer();
--get the variables
local enabled = session:getVariable("enabled");
local pin_number = session:getVariable("pin_number");
local sounds_dir = session:getVariable("sounds_dir");
local domain_uuid = session:getVariable("domain_uuid");
local domain_name = session:getVariable("domain_name");
local ring_group_number = session:getVariable("ring_group_number");
local ring_group_uuid = session:getVariable("ring_group_uuid");
local forward_destination = session:getVariable("forward_destination");
local forward_reset = session:getVariable("forward_reset");
--set the sounds path for the language, dialect and voice
local default_language = session:getVariable("default_language") or 'en';
local default_dialect = session:getVariable("default_dialect") or 'us';
local default_voice = session:getVariable("default_voice") or 'callie';
--a moment to sleep
session:sleep(1000);
--connect to the database
local dbh = Database.new('system');
--check pin code
if not empty(pin_number) then
--get the pin number
local min_digits = 3;
local max_digits = 20;
local caller_pin_number = session:playAndGetDigits(min_digits, max_digits, max_tries, digit_timeout, "#", "phrase:voicemail_enter_pass:#", "", "\\d+");
if empty(caller_pin_number) then return end
--check pin number
if pin_number ~= caller_pin_number then return end
--user hangup
if not session:ready() then return end
end
--get ring group number
if empty(ring_group_number) then
--get the ring group extension number
local min_digits = 2;
local max_digits = 20;
ring_group_number = session:playAndGetDigits(min_digits, max_digits, max_tries, digit_timeout, "#", "phrase:voicemail_enter_id:#", "", "\\d+");
if empty(ring_group_number) then return end
-- user hangup
if not session:ready() then return end
end
--user hangup
if not session:ready() then return end
--search ring_group in database
local sql = [[SELECT ring_group_uuid as uuid, ring_group_forward_enabled as forward_enabled,
ring_group_forward_destination as forward_destination, ring_group_extension as extension
FROM v_ring_groups
WHERE domain_uuid = :domain_uuid
]]
local params = {domain_uuid = domain_uuid}
if (not empty(ring_group_number) or empty(ring_group_uuid)) then
sql = sql .. " AND ring_group_extension=:extension"
params.extension = ring_group_number
else
sql = sql .. " AND ring_group_uuid=:ring_group_uuid"
params.ring_group_uuid = ring_group_uuid
end
if (debug["sql"]) then
log.noticef("SQL: %s; params: %s", sql, json.encode(params));
end
local ring_group = dbh:first_row(sql, params)
--if can not find ring group
if (not ring_group) or (not ring_group.uuid) then return end
-- user hangup
if not session:ready() then return end
-- get destination number from the database if it not provided
if enabled == 'toggle' and empty(forward_destination) then
forward_destination = ring_group.forward_destination
end
--toggle enabled
if enabled == 'toggle' then
-- if we toggle CF and specify new destination number then just enable it
if forward_destination == ring_group.forward_destination then
enabled = (ring_group.forward_enabled == 'true') and 'false' or 'true'
else
enabled = 'true'
end
end
--get the forward destination
if enabled == 'true' and empty(forward_destination) then
-- get number
forward_destination = session:playAndGetDigits(min_digits, max_digits, max_tries, digit_timeout, "#", sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/ivr/ivr-enter_destination_telephone_number.wav", "", "\\d+");
if empty(forward_destination) then return end
-- user hangup
if not session:ready() then return end
end
--set the values based on conditions
forward_enabled = (enabled == 'true') and 'true' or 'false';
if (forward_enabled == 'false' and forward_reset == 'true') then
forward_destination = '';
end
--update ring group call farward in database
local sql = [[UPDATE v_ring_groups
SET
ring_group_forward_enabled = :forward_enabled,
ring_group_forward_destination = :forward_destination
WHERE
ring_group_uuid = :ring_group_uuid]]
local params = {
forward_enabled = forward_enabled,
forward_destination = forward_destination,
ring_group_uuid = ring_group.uuid,
}
if (debug["sql"]) then
log.noticef("SQL: %s; params: %s", sql, json.encode(params));
end
dbh:query(sql, params)
--disconnect from database
dbh:release()
--notify caller
if enabled == 'true' then
--set forward_all_enabled
channel_display(session:get_uuid(), "Activated")
--say the destination number
session:say(forward_destination, default_language, "number", "iterated");
--notify the caller
session:streamFile(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/ivr/ivr-call_forwarding_has_been_set.wav");
else
--set forward_all_enabled
channel_display(session:get_uuid(), "Cancelled")
--notify the caller
session:streamFile(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/ivr/ivr-call_forwarding_has_been_cancelled.wav");
end
-- BLF for display CF status
blf.forward(enabled == 'true', ring_group.extension, nil, ring_group.forward_destination, forward_destination, domain_name)
@@ -0,0 +1,136 @@
--
-- FusionPBX
-- Version: MPL 1.1
--
-- The contents of this file are subject to the Mozilla Public License Version
-- 1.1 (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
-- http://www.mozilla.org/MPL/
--
-- Software distributed under the License is distributed on an "AS IS" basis,
-- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-- for the specific language governing rights and limitations under the
-- License.
--
-- The Original Code is FusionPBX
--
-- The Initial Developer of the Original Code is
-- Mark J Crane <markjcrane@fusionpbx.com>
-- Copyright (C) 2010-2013
-- the Initial Developer. All Rights Reserved.
--
-- Contributor(s):
-- Mark J Crane <markjcrane@fusionpbx.com>
--include config.lua
require "resources.functions.config";
--set variables
max_tries = "3";
digit_timeout = "5000";
--define the trim function
require "resources.functions.trim";
--define the explode function
require "resources.functions.explode";
--get the argv values
script_name = argv[0];
argv_uuid = argv[1];
prompt = argv[2];
--prepare the api
api = freeswitch.API();
--answer the call
session:answer();
--get the variables
context = session:getVariable("context");
sounds_dir = session:getVariable("sounds_dir");
destination_number = session:getVariable("destination_number");
uuid = session:getVariable("uuid");
--set the sounds path for the language, dialect and voice
default_language = session:getVariable("default_language");
default_dialect = session:getVariable("default_dialect");
default_voice = session:getVariable("default_voice");
if (not default_language) then default_language = 'en'; end
if (not default_dialect) then default_dialect = 'us'; end
if (not default_voice) then default_voice = 'callie'; end
--if an extension answer the call
-- user_exists id 1005 voip.fusionpbx.com
-- cmd = "user_exists id ".. destination_number .." "..context;
-- result = api:executeString(cmd);
-- freeswitch.consoleLog("NOTICE", "[confirm] "..cmd.." --"..result.."--\n");
-- if (result == "true") then
-- prompt = false;
-- end
--prompt for digits
if (prompt == "true") then
--get the digit
min_digits = 1;
max_digits = 1;
--check if the original call exists
cmd = "uuid_exists "..argv_uuid;
if (trim(api:executeString(cmd)) == "false") then
session:hangup("NO_ANSWER");
end
--digit = session:playAndGetDigits(2, 5, 3, 3000, "#", sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/ivr/ivr-accept_reject.wav", sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/ivr/ivr-that_was_an_invalid_entry.wav", "\\d+")
digit = session:playAndGetDigits(min_digits, max_digits, max_tries, digit_timeout, "#", sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/ivr/ivr-accept_reject.wav", sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/ivr/ivr-that_was_an_invalid_entry.wav", "\\d+");
--process the response
if (digit == "1") then
--confirmed call accepted
confirmed = true;
elseif (digit == "2") then
freeswitch.consoleLog("NOTICE", "[confirm] reject\n");
session:hangup("CALL_REJECTED"); --LOSE_RACE
else
--freeswitch.consoleLog("NOTICE", "[confirm] no answer\n");
session:hangup("NO_ANSWER");
end
else
freeswitch.consoleLog("NOTICE", "[confirm] automatically accepted\n");
confirmed = true;
end
if (confirmed) then
--cmd = "bgapi sched_transfer +1 "..uuid.." *5901";
--freeswitch.consoleLog("NOTICE", "[ring_group] uuid: "..cmd.."\n");
--result = api:executeString(cmd);
freeswitch.consoleLog("NOTICE", "[confirm] accepted\n");
--check if the original call exists
cmd = "uuid_exists "..argv_uuid;
if (trim(api:executeString(cmd)) == "false") then
session:hangup("NO_ANSWER");
end
--unschedule the timeout
cmd = "sched_del ring_group:"..argv_uuid;
freeswitch.consoleLog("NOTICE", "[confirm] cmd: "..cmd.."\n");
results = trim(api:executeString(cmd));
--get the uuids and remove the other calls
cmd = "uuid_getvar "..argv_uuid.." uuids";
freeswitch.consoleLog("NOTICE", "[confirm] cmd: "..cmd.."\n");
uuids = trim(api:executeString(cmd));
u = explode(",", uuids);
for k,v in pairs(u) do
if (uuid ~= v) then
cmd = "uuid_kill "..v;
freeswitch.consoleLog("NOTICE", "[confirm] cmd: "..cmd.."\n");
result = trim(api:executeString(cmd));
end
end
--bridge the call
cmd = "uuid_bridge "..uuid.." "..argv_uuid;
result = trim(api:executeString(cmd));
session:execute("valet_park", "confirm "..argv_uuid);
end
@@ -0,0 +1,204 @@
--
-- FusionPBX
-- Version: MPL 1.1
--
-- The contents of this file are subject to the Mozilla Public License Version
-- 1.1 (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
-- http://www.mozilla.org/MPL/
--
-- Software distributed under the License is distributed on an "AS IS" basis,
-- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-- for the specific language governing rights and limitations under the
-- License.
--
-- The Original Code is FusionPBX
--
-- The Initial Developer of the Original Code is
-- Mark J Crane <markjcrane@fusionpbx.com>
-- Copyright (C) 2010-2016
-- All Rights Reserved.
--
-- Contributor(s):
-- Koldo A. Marcos <koldo.aingeru@sarenet.es>
--include config.lua
require "resources.functions.config";
--set debug
-- debug["sql"] = true;
--connect to the database
local Database = require "resources.functions.database";
dbh = Database.new('system');
--include json library
local json
if (debug["sql"]) then
json = require "resources.functions.lunajson"
end
--set default variables
sounds_dir = "";
recordings_dir = "";
pin_number = "";
max_tries = "3";
digit_timeout = "3000";
--define uuid function
local random = math.random;
local function uuid()
local template ='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
return string.gsub(template, '[xy]', function (c)
local v = (c == 'x') and random(0, 0xf) or random(8, 0xb);
return string.format('%x', v);
end)
end
--get session variables
if (session:ready()) then
session:answer();
--session:execute("info", "");
destination = session:getVariable("destination");
pin_number = session:getVariable("pin_number");
sounds_dir = session:getVariable("sounds_dir");
ring_group_uuid = session:getVariable("ring_group_uuid");
domain_uuid = session:getVariable("domain_uuid");
end
--get the domain uuid and set other required variables
if (session:ready()) then
--get info for the ring group
--sql = "SELECT * FROM v_ring_groups ";
--sql = sql .. "where ring_group_uuid = '"..ring_group_uuid.."' ";
--status = dbh:query(sql, function(row)
-- domain_uuid = row["domain_uuid"];
--end);
--set destination defaults
destination_timeout = 15;
destination_delay = 0;
--create the primary key uuid
ring_group_destination_uuid = uuid();
end
--if the pin number is provided then require it
if (pin_number) then
min_digits = string.len(pin_number);
max_digits = string.len(pin_number)+1;
digits = session:playAndGetDigits(min_digits, max_digits, max_tries, digit_timeout, "#", sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/ivr/ivr-please_enter_pin_followed_by_pound.wav", "", "\\d+");
if (digits == pin_number) then
--pin is correct
else
session:streamFile(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/ivr/ivr-pin_or_extension_is-invalid.wav");
session:streamFile(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/ivr/ivr-im_sorry.wav");
session:streamFile(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/voicemail/vm-goodbye.wav");
session:hangup("NORMAL_CLEARING");
return;
end
end
--get the destination
--if (session:ready()) then
-- if string.len(destination) == 0) then
-- destination = session:playAndGetDigits(1, 1, max_tries, digit_timeout, "#", "ivr/ivr-enter_destination_telephone_number.wav", "", "\\d+");
-- freeswitch.consoleLog("NOTICE", "[ring_group] destination: "..destination.."\n");
-- end
--end
--login or logout
if (session:ready()) then
menu_selection = session:playAndGetDigits(1, 1, max_tries, digit_timeout, "#", "ivr/ivr-enter_destination_telephone_number.wav", "", "\\d+");
freeswitch.consoleLog("NOTICE", "[ring_group] menu_selection: "..menu_selection.."\n");
if (menu_selection == "1") then
--first, check to see if the destination is already in this ring group
local sql = [[
SELECT COUNT(*) AS in_group FROM
v_ring_group_destinations
WHERE
domain_uuid = :domain_uuid
AND ring_group_uuid = :ring_group_uuid
AND destination_number = :destination
]];
local params = {domain_uuid = domain_uuid, ring_group_uuid = ring_group_uuid,
destination = destination};
if debug["sql"] then
freeswitch.consoleLog("NOTICE", "[ring_group] SQL: " .. sql .. "; params: " .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
if (row.in_group == "0") then
sql = [[
INSERT INTO
v_ring_group_destinations
( ring_group_destination_uuid,
domain_uuid,
ring_group_uuid,
destination_number,
destination_delay,
destination_timeout
)
VALUES
( :ring_group_destination_uuid,
:domain_uuid,
:ring_group_uuid,
:destination,
:destination_delay,
:destination_timeout
)]];
params = {
ring_group_destination_uuid = ring_group_destination_uuid;
domain_uuid = domain_uuid;
ring_group_uuid = ring_group_uuid;
destination = destination;
destination_delay = destination_delay;
destination_timeout = destination_timeout;
};
if debug["sql"] then
freeswitch.consoleLog("NOTICE", "[ring_group][destination] SQL: " .. sql .. "; params: " .. json.encode(params) .. "\n");
end
dbh:query(sql, params);
freeswitch.consoleLog("NOTICE", "[ring_group][destination] LOG IN\n");
session:streamFile("ivr/ivr-you_are_now_logged_in.wav");
else
freeswitch.consoleLog("NOTICE", "[ring_group][destination] ALREADY LOGGED IN\n");
session:streamFile("ivr/ivr-you_are_now_logged_in.wav");
end
end);
end
if (menu_selection == "2") then
local sql = [[
DELETE FROM
v_ring_group_destinations
WHERE
domain_uuid =:domain_uuid
AND ring_group_uuid=:ring_group_uuid
AND destination_number=:destination
]];
local params = {domain_uuid = domain_uuid, ring_group_uuid = ring_group_uuid,
destination = destination};
if debug["sql"] then
freeswitch.consoleLog("NOTICE", "[ring_group][destination] SQL: " .. sql .. "; params: " .. json.encode(params) .. "\n");
end
dbh:query(sql, params);
freeswitch.consoleLog("NOTICE", "[ring_group][destination] LOG OUT\n");
session:streamFile("ivr/ivr-you_are_now_logged_out.wav");
end
end
--wait for the file to be written before proceeding
if (session:ready()) then
--session:sleep(1000);
end
--hangup
if (session:ready()) then
session:hangup();
end
@@ -0,0 +1,82 @@
-- uuid_hangup.lua
-- Part of FusionPBX
-- Copyright (C) 2013 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--Description:
--if the uuid does not exist
--then run commands
--get the argv values
script_name = argv[0];
uuid = argv[1];
timeout = argv[2];
--define the trim function
require "resources.functions.trim";
--define the explode function
require "resources.functions.explode";
--prepare the api
api = freeswitch.API();
--get the list of uuids
cmd = "uuid_getvar "..uuid.." uuids";
--freeswitch.consoleLog("NOTICE", "[confirm] cmd: "..cmd.."\n");
uuids = trim(api:executeString(cmd));
--monitor the uuid
x = 0
while true do
--sleep a moment to prevent using unecessary resources
freeswitch.msleep(1000);
--check if the uuid exists
if (api:executeString("uuid_exists "..uuid) == "false") then
--unschedule the timeout
cmd = "sched_del ring_group:"..uuid;
--freeswitch.consoleLog("NOTICE", "[confirm] cmd: "..cmd.."\n");
results = trim(api:executeString(cmd));
--end the other uuids
u = explode(",", uuids);
for k,v in pairs(u) do
if (uuid ~= v) then
cmd = "uuid_kill "..v;
--freeswitch.consoleLog("NOTICE", "[confirm] cmd: "..cmd.."\n");
result = trim(api:executeString(cmd));
end
end
--end the loop
break;
end
--timeout
x = x + 1;
if (x > tonumber(timeout)) then
--end the loop
break;
end
end
@@ -0,0 +1,165 @@
--
-- FusionPBX
-- Version: MPL 1.1
--
-- The contents of this file are subject to the Mozilla Public License Version
-- 1.1 (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
-- http://www.mozilla.org/MPL/
--
-- Software distributed under the License is distributed on an "AS IS" basis,
-- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-- for the specific language governing rights and limitations under the
-- License.
--
-- The Original Code is FusionPBX
--
-- The Initial Developer of the Original Code is
-- Mark J Crane <markjcrane@fusionpbx.com>
-- Copyright (C) 2010-2016
-- All Rights Reserved.
--
-- Contributor(s):
-- Koldo A. Marcos <koldo.aingeru@sarenet.es>
--include config.lua
require "resources.functions.config";
--connect to the database
require "resources.functions.database_handle";
dbh = database_handle('system');
--set default variables
sounds_dir = "";
recordings_dir = "";
pin_number = "";
max_tries = "3";
digit_timeout = "3000";
--define uuid function
local random = math.random;
local function uuid()
local template ='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
return string.gsub(template, '[xy]', function (c)
local v = (c == 'x') and random(0, 0xf) or random(8, 0xb);
return string.format('%x', v);
end)
end
--get session variables
if (session:ready()) then
session:answer();
--session:execute("info", "");
destination_number = session:getVariable("destination_number");
pin_number = session:getVariable("pin_number");
sounds_dir = session:getVariable("sounds_dir");
ring_group_uuid = session:getVariable("ring_group_uuid");
domain_uuid = session:getVariable("domain_uuid");
end
--get the domain uuid and set other required variables
if (session:ready()) then
--get info for the ring group
--sql = "SELECT * FROM v_ring_groups ";
--sql = sql .. "where ring_group_uuid = '"..ring_group_uuid.."' ";
--status = dbh:query(sql, function(row)
-- domain_uuid = row["domain_uuid"];
--end);
destination_timeout = 15;
destination_delay = 0;
--create the primary key uuid
ring_group_destination_uuid = uuid();
end
--if the pin number is provided then require it
if (pin_number) then
min_digits = string.len(pin_number);
max_digits = string.len(pin_number)+1;
digits = session:playAndGetDigits(min_digits, max_digits, max_tries, digit_timeout, "#", sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/ivr/ivr-please_enter_pin_followed_by_pound.wav", "", "\\d+");
if (digits == pin_number) then
--pin is correct
else
session:streamFile(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/ivr/ivr-pin_or_extension_is-invalid.wav");
session:streamFile(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/ivr/ivr-im_sorry.wav");
session:streamFile(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/voicemail/vm-goodbye.wav");
session:hangup("NORMAL_CLEARING");
return;
end
end
--press 1 to login and 2 to logout
if (session:ready()) then
menu_selection = session:playAndGetDigits(1, 1, max_tries, digit_timeout, "#", "ivr/ivr-enter_destination_telephone_number.wav", "", "\\d+");
freeswitch.consoleLog("NOTICE", "[ring_group] menu_selection: "..menu_selection.."\n");
if (menu_selection == "1") then
--first, check to see if the destination is already in this ring group
sql = [[
SELECT COUNT(*) AS in_group FROM
v_ring_group_destinations
WHERE
domain_uuid = ']]..domain_uuid..[['
AND ring_group_uuid = ']]..ring_group_uuid..[['
AND destination_number = ']]..destination_number..[['
]];
--freeswitch.consoleLog("NOTICE", "[ring_group] SQL "..sql.."\n");
dbh:query(sql, function(row)
if (row.in_group == "0") then
sql = [[
INSERT INTO
v_ring_group_destinations
( ring_group_destination_uuid,
domain_uuid,
ring_group_uuid,
destination_number,
destination_delay,
destination_timeout
)
VALUES
( ']]..ring_group_destination_uuid..[[',
']]..domain_uuid..[[',
']]..ring_group_uuid..[[',
']]..destination..[[',
]]..destination_delay..[[,
]]..destination_timeout..[[
)]];
freeswitch.consoleLog("NOTICE", "[ring_group][destination] SQL "..sql.."\n");
dbh:query(sql);
freeswitch.consoleLog("NOTICE", "[ring_group][destination] LOG IN\n");
session:streamFile("ivr/ivr-you_are_now_logged_in.wav");
else
freeswitch.consoleLog("NOTICE", "[ring_group][destination] ALREADY LOGGED IN\n");
session:streamFile("ivr/ivr-you_are_now_logged_in.wav");
end
end);
end
if (menu_selection == "2") then
sql = [[
DELETE FROM
v_ring_group_destinations
WHERE
domain_uuid =']]..domain_uuid..[['
AND ring_group_uuid=']]..ring_group_uuid..[['
AND destination_number=']]..destination..[['
]];
freeswitch.consoleLog("NOTICE", "[ring_group][destination] SQL "..sql.."\n");
dbh:query(sql);
freeswitch.consoleLog("NOTICE", "[ring_group][destination] LOG OUT\n");
session:streamFile("ivr/ivr-you_are_now_logged_out.wav");
end
end
--wait for the file to be written before proceeding
if (session:ready()) then
--session:sleep(1000);
end
--hangup
if (session:ready()) then
session:hangup();
end
@@ -0,0 +1,37 @@
--includes
require "resources.functions.config";
local Database = require "resources.functions.database";
local Settings = require "resources.functions.lazy_settings"
dbh = Database.new('system');
local settings = Settings.new(dbh, domain_name, domain_uuid);
--define trim
function trim (s)
return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
end
--get the argv values
cmd = argv[1];
file = argv[2];
--get the cache directory
local cache_dir = settings:get('cache', 'location', 'text')
if (cmd ~= nil) then
cmd = trim(cmd);
freeswitch.consoleLog("NOTICE","api_command: "..cmd .. " cache\n");
end
if (cmd == "flush") then
os.execute("rm " .. cache_dir .. "/*");
end
if (cmd == "delete") then
if (file ~= nil) then
file = trim(file);
freeswitch.consoleLog("NOTICE","api_command: delete ".. cache_dir .. "/" .. file .. "\n");
os.remove(cache_dir.."/"..file);
end
end
@@ -0,0 +1,105 @@
--description
--monitor custom cache event and clear cache on remote servers
--protect xmlrpc using a firewall on the server to limit access by ip address
--further protect it by configuring ssl for xmlrpc on the remote server
--dependencies
--install mod_curl freeswitch module
--uncomment mod_curl from modules.conf when compiling freeswitch
--xmlrpc
--open port xmlrpc port for other master server IP addresses
--change the password for xmlrpc in system -> settings
--conf/autoload_configs/lua.conf.xml
-- <param name="startup-script" value="app/server/resources/file_cache.lua"/>
--iptables
-- /sbin/iptables -I INPUT -j ACCEPT -p tcp --dport 8080 -s x.x.x.x/32
-- ubuntu: service iptables-persistent save
--define the servers running freeswitch do not include local
--[[
#put this in default settings once for each server. Order doesn't matter.
Category: cache
Subcategory: remote_servers
Type: array
value: http://user:password@server_ip:8080
]]
--includes config.lua which will include local.lua if it exists
require "resources.functions.config"
local Database = require "resources.functions.database";
local Settings = require "resources.functions.lazy_settings";
local db = dbh or Database.new('system');
local settings = Settings.new(db, domain_name, domain_uuid)
local server_list = settings:get('cache', 'remote_servers', 'array')
--subscribe to the events
--events = freeswitch.EventConsumer("all");
events = freeswitch.EventConsumer("CUSTOM");
--define trim
function trim (s)
return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
end
--prepare the api object
api = freeswitch.API();
--get the events
for event in (function() return events:pop(1) end) do
--serialize the data for the console
--freeswitch.consoleLog("notice","event:" .. event:serialize("xml") .. "\n");
--freeswitch.consoleLog("notice","event:" .. event:serialize("json") .. "\n");
--get the uuid
local api_command = event:getHeader("API-Command");
if (api_command ~= nil) then
api_command = trim(api_command);
freeswitch.consoleLog("NOTICE","api_command: "..api_command .. "\n");
end
--check if cache clear command
if (api_command ~= "cache") then
goto continue
end
cache_updated = false;
local api_command_argument = event:getHeader("API-Command-Argument");
if (api_command_argument ~= nil) then
api_command_argument = trim(api_command_argument);
end
if (api_command_argument == nil) then
goto continue
end
if (api_command_argument == "flush") then
cache_updated = true
end
if (string.sub(api_command_argument, 0, 6) == "delete") then
cache_updated = true
end
if (not cache_updated) then
goto continue
end
--update the server_list
server_list = settings:get('cache', 'remote_servers', 'array')
--check that there is work to do
if (server_list == nil) then
freeswitch.consoleLog("NOTICE","file_cache.lua: Script loaded but no servers configured\n");
goto continue
end
--send the API commands
for _, server in ipairs(server_list) do
api_command_argument = api_command_argument:gsub(" ", "%%20");
url = server..[[/webapi/luarun?app/servers/resources/clear_cache.lua%20]]..api_command_argument;
api = freeswitch.API();
get_response = api:execute("curl", url);
freeswitch.consoleLog("INFO", "[notice] curl ".. url .. " \n");
end
::continue::
end
@@ -0,0 +1,96 @@
--description
--monitor custom memcache event and clear memcache on remote servers
--protect xmlrpc using a firewall on the server to limit access by ip address
--dependencies
--install mod_curl freeswitch module
--uncomment mod_curl from modules.conf when compiling freeswitch
--xmlrpc
--open port xmlrpc port for other master server IP addresses
--change the password for xmlrpc in system -> settings
--conf/autoload_configs/lua.conf.xml
-- <param name="startup-script" value="app/server/resources/memcache.lua"/>
--iptables
-- /sbin/iptables -I INPUT -j ACCEPT -p tcp --dport 8080 -s x.x.x.x/32
-- ubuntu: service iptables-persistent save
--define the servers running freeswitch do not include local
--[[
#put this in local.lua
servers = {}
x = 0;
servers[x] = {}
servers[x]['method'] = "curl";
servers[x]['username'] = "freeswitch";
servers[x]['password'] = "freeswitch";
servers[x]['hostname'] = "x.x.x.x";
servers[x]['port'] = "8080";
x = x + 1;
servers[x] = {}
servers[x]['method'] = "curl";
servers[x]['username'] = "freeswitch";
servers[x]['password'] = "freeswitch";
servers[x]['hostname'] = "x.x.x.x";
servers[x]['port'] = "8080";
]]
--includes config.lua which will include local.lua if it exists
require "resources.functions.config"
--subscribe to the events
--events = freeswitch.EventConsumer("all");
events = freeswitch.EventConsumer("CUSTOM");
--define trim
function trim (s)
return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
end
--prepare the api object
api = freeswitch.API();
--get the events
for event in (function() return events:pop(1) end) do
--serialize the data for the console
--freeswitch.consoleLog("notice","event:" .. event:serialize("xml") .. "\n");
--freeswitch.consoleLog("notice","event:" .. event:serialize("json") .. "\n");
--get the uuid
local api_command = event:getHeader("API-Command");
if (api_command ~= nil) then
api_command = trim(api_command);
freeswitch.consoleLog("NOTICE","api_command: "..api_command .. "\n");
end
if (api_command == "memcache") then
memcache_updated = false;
local api_command_argument = event:getHeader("API-Command-Argument");
if (api_command_argument ~= nil) then
api_command_argument = trim(api_command_argument);
end
if (api_command_argument ~= nil) then
if (api_command_argument == "flush") then
memcache_updated = true
end
if (string.sub(api_command_argument, 0, 6) == "delete") then
memcache_updated = true
end
if (memcache_updated) then
for key,row in pairs(servers) do
if (row.method == "ssh") then
api_command_argument = api_command_argument:gsub("%%20", " ");
cmd = [[ssh ]]..row.username..[[@]]..row.hostname..[[ "fs_cli -x 'memcache ]]..api_command_argument..[['"]];
freeswitch.consoleLog("INFO", "[notice] command: ".. cmd .. "\n");
os.execute(cmd);
end
if (row.method == "curl") then
api_command_argument = api_command_argument:gsub(" ", "%%20");
url = [[http://]]..row.username..[[:]]..row.password..[[@]]..row.hostname..[[:]]..row.port..[[/webapi/memcache?]]..api_command_argument;
os.execute("curl "..url);
freeswitch.consoleLog("INFO", "[notice] curl ".. url .. " \n");
end
end
end
end
end
end
@@ -0,0 +1,146 @@
-- FusionPBX
-- Version: MPL 1.1
-- The contents of this file are subject to the Mozilla Public License Version
-- 1.1 (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
-- http://www.mozilla.org/MPL/
-- Software distributed under the License is distributed on an "AS IS" basis,
-- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-- for the specific language governing rights and limitations under the
-- License.
-- The Original Code is FusionPBX
-- The Initial Developer of the Original Code is
-- Mark J Crane <markjcrane@fusionpbx.com>
-- Portions created by the Initial Developer are Copyright (C) 2019 - 2022
-- the Initial Developer. All Rights Reserved.
-- load config
require "resources.functions.config";
--set debug
-- debug["sql"] = true;
--load libraries
local log = require "resources.functions.log"["app:dialplan:outbound:speed_dial"]
local Database = require "resources.functions.database";
local cache = require "resources.functions.cache";
local json = require "resources.functions.lunajson";
--get the variables
domain_name = session:getVariable("domain_name");
domain_uuid = session:getVariable("domain_uuid");
context = session:getVariable("context");
permissions = session:getVariable("permissions");
global = session:getVariable("global") or 'false';
user = session:getVariable("sip_auth_username") or session:getVariable("username");
--set the default
if (not permissions) then permissions = 'false'; end
--get the argv values
destination = argv[2];
-- search in cache first
local key = "app:dialplan:speed_dial:" .. destination .. "@" .. domain_name
local value = cache.get(key)
-- decode value from cache
if value then
local t = json.decode(value)
if not (t and t.phone_number) then
log.warningf("can not decode value from the cache: %s", value)
value = nil
else
value = t
end
end
-- search in database
if not value then
-- set source flag
source = "database"
-- connect to database
local dbh = Database.new('system');
-- search for the phone number in database using the speed dial
if (permissions == 'true') then
--advanced, check on user and group permissions
sql = [[
-- find all contacts with correct user or withot users and groups at all
select t0.phone_number --, t6.extension, 'GROUP:' || t3.group_name as user_name
from v_contact_phones t0
inner join v_contacts t1 on t0.contact_uuid = t1.contact_uuid
left outer join v_contact_groups t2 on t1.contact_uuid = t2.contact_uuid
left outer join v_user_groups t3 on t2.group_uuid = t3.group_uuid
left outer join v_users t4 on t3.user_uuid = t4.user_uuid
left outer join v_extension_users t5 on t4.user_uuid = t5.user_uuid
left outer join v_extensions t6 on t5.extension_uuid = t6.extension_uuid
where t0.domain_uuid = :domain_uuid and t0.phone_speed_dial = :phone_speed_dial
and ( (1 = 0)
or (t6.domain_uuid = :domain_uuid and (t6.extension = :user or t6.number_alias = :user))
or (t2.contact_uuid is null and not exists(select 1 from v_contact_users t where t.contact_uuid = t0.contact_uuid))
)
union
-- find all contacts with correct group or withot users and groups at all
select t0.phone_number -- , t5.extension, 'USER:' || t3.username as user_name
from v_contact_phones t0
inner join v_contacts t1 on t0.contact_uuid = t1.contact_uuid
left outer join v_contact_users t2 on t1.contact_uuid = t2.contact_uuid
left outer join v_users t3 on t2.user_uuid = t3.user_uuid
left outer join v_extension_users t4 on t3.user_uuid = t4.user_uuid
left outer join v_extensions t5 on t4.extension_uuid = t5.extension_uuid
where t0.domain_uuid = :domain_uuid and t0.phone_speed_dial = :phone_speed_dial
and ( (1 = 0)
or (t5.domain_uuid = :domain_uuid and (t5.extension = :user or t5.number_alias = :user))
or (t2.contact_user_uuid is null and not exists(select 1 from v_contact_groups t where t.contact_uuid = t0.contact_uuid))
)
]];
log.noticef("[speed dial] advanced");
else
-- simple, skip looking up user or group permissions
sql = "select phone_number ";
sql = sql .. "from v_contact_phones ";
if (global == 'true') then
sql = sql .. "where phone_speed_dial = :phone_speed_dial ";
params = {phone_speed_dial = destination};
else
sql = sql .. "where domain_uuid = :domain_uuid ";
sql = sql .. "and phone_speed_dial = :phone_speed_dial ";
params = {phone_speed_dial = destination, domain_uuid = domain_uuid};
end
log.noticef("[speed dial] simple");
end
if (debug["sql"]) then
log.noticef("SQL: %s; params: %s", sql, json.encode(params));
end
local phone_number = dbh:first_value(sql, params)
-- release database connection
dbh:release()
-- set the cache
if phone_number then
value = {phone_number = phone_number}
cache.set(key, json.encode(value), expire["speed_dial"])
end
end
-- transfer
if value then
--log the result
log.noticef("%s XML %s source: %s", destination, context, source)
--transfer the call
session:transfer(value.phone_number, "XML", context);
else
log.warningf('can not find number: %s in domain: %s', destination, domain_name)
end
@@ -0,0 +1,212 @@
--
-- FusionPBX
-- Version: MPL 1.1
--
-- The contents of this file are subject to the Mozilla Public License Version
-- 1.1 (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
-- http://www.mozilla.org/MPL/
--
-- Software distributed under the License is distributed on an "AS IS" basis,
-- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-- for the specific language governing rights and limitations under the
-- License.
--
-- The Original Code is FusionPBX
--
-- The Initial Developer of the Original Code is
-- Mark J Crane <markjcrane@fusionpbx.com>
-- Copyright (C) 2010-2014
-- the Initial Developer. All Rights Reserved.
--
-- Contributor(s):
-- Riccardo Granchi <riccardo.granchi@nems.it>
-- Philippe Rioual <bhouba@gmail.com>
-- Alexey Melnichuck <alexeymelnichuck@gmail.com>
--Configuration
-- Define known template names
local known_templates = {
["mobile" ] = true,
["landline" ] = true,
["international"] = true,
["tollfree" ] = true,
["sharedcharge" ] = true,
["premium" ] = true,
["unknown" ] = true,
}
--Define templates for every toll type for your country
local templates = {
IT = {
{"mobile", "[35]%d%d%d%d%d%d+" },
{"landline", "0[123456789]%d+" },
{"international", "00%d+" },
{"tollfree", "119|1[3456789]%d|19[24]%d|192[01]%d%d|800%d%d%d%d%d+|803%d%d%d+|456%d%d%d%d%d%d+|11[2345678]|15%d%d|116%d%d%d|196%d%d" },
{"sharedcharge", "84[0178]%d%d%d%d+|199%d%d%d%d%d+|178%d%d%d%d%d+|12%d%d|10%d%d%d+|1482|149%d+|4[012]%d+|70%d%d%d%d%d+" },
{"premium", "89[2459]%d%d%d+|16[456]%d%d%d+|144%d%d%d+|4[346789]%d%d+" },
{"unknown", "%d%d+" },
};
FR = {
{"mobile", "0[67]%d%d%d%d%d%d%d%d" },
{"landline", "0[1234589]%d%d%d%d%d%d%d%d" },
{"international", "00%d+" },
{"tollfree", "15|17|18|112|114|115|116%d%d%d|118%d%d%d|119|19[16]|1[06]%d%d|080%d+" },
{"sharedcharge", "081%d+|082[0156]%d+|0884%d+|089[0123789]%d+" },
{"premium", "%d%d+" },
{"unknown", "%d%d+" },
};
US = {
{"unknown", "%d+"},
};
RU = {
{"international", "810%d+|8[89]40%d+|87%d%d%d%d%d%d%d%d%d"};
{"mobile", "89%d%d%d%d%d%d%d%d%d" };
{"tollfree", "8800%d%d%d%d%d%d%d|10[1-9]" };
{"landline", "8[3-68]%d%d%d%d%d%d%d%d%d" };
{"unknown", "" };
};
ZA = {
{"international", "00%d+" };
{"mobile", "0[6-8]%d%d%d%d%d%d%d%d" };
{"landline", "0[1-5]%d%d%d%d%d%d%d%d" };
{"unknown", "" };
};
}
--Set to true to allow all calls for extensions without toll_allow
local ACCEPT_EMPTY_TOLL_ALLOW = false
--debug
debug["toll_type"] = false
require "resources.functions.explode";
--create the api object and get variables
local api = freeswitch.API()
local uuid = argv[2]
if not uuid or uuid == "" then
return
end
local function hungup()
session:hangup("OUTGOING_CALL_BARRED")
end
local function log(level, msg)
freeswitch.consoleLog(level, "[toll_allow] " .. msg .. "\n")
end
local function logf(level, ...)
return log(level, string.format(...))
end
local function trace(type, ...)
if debug[type] then log(...) end
end
local function tracef(type, ...)
if debug[type] then logf(...) end
end
local function channel_variable(uuid, name)
local result = api:executeString("uuid_getvar " .. uuid .. " " .. name)
tracef("toll_type", "NOTICE", "channel_variable %s - %s", name, result)
if result:sub(1, 4) == '-ERR' then return nil end
return result
end
local function template_match(prefix, template, called)
local parts = explode("|", template)
for index,part in ipairs(parts) do
local pattern = "^" .. prefix .. part .. "$"
if ( string.match(called, pattern) ~= nil ) then
return pattern
end
end
end
local function get_toll_type(prefix, templates, called)
for _,params in ipairs(templates) do
local label, template = params[1], params[2]
if not known_templates[label] then
logf("WARNING", "unknown template name: %s in country template array", label)
end
trace("toll_type", "NOTICE", "checking toll type " .. label .. " template: " .. template)
local pattern = template_match(prefix, template, called)
if pattern then
trace("toll_type", "NOTICE", "destination number " .. called .. " matches " .. label .. " pattern: " .. pattern)
return label
end
end
end
local function is_undef(str)
return (not str) or (#str == 0) or (str == "_undef_")
end
local called = channel_variable(uuid, "destination_number") or ""
local caller = channel_variable(uuid, "caller_id_number") or ""
local prefix = channel_variable(uuid, "outbound_prefix") or ""
local country = channel_variable(uuid, "default_country") or ""
local toll_allow = channel_variable(uuid, "toll_allow") or ""
if (debug["toll_type"]) then
logf("NOTICE", "called: %s", called)
logf("NOTICE", "prefix: %s", prefix)
logf("NOTICE", "country: %s", country)
logf("NOTICE", "tollAllow: %s", toll_allow)
end
if is_undef(toll_allow) then
if ACCEPT_EMPTY_TOLL_ALLOW then
logf("NOTICE", "unknown call authorized from %s to %s", caller, called)
return
end
logf("WARNING", "unknown call not authorized from %s to %s : OUTGOING_CALL_BARRED", caller, called)
return hungup()
end
if is_undef(prefix) then
prefix = ""
end
if is_undef(country) then
log("WARNING", "undefined country")
return
end
local templates = templates[country]
if not templates then
log("WARNING", "undefined templates")
return
end
--set toll_type
local toll_type = get_toll_type(prefix, templates, called) or "unknown"
log("NOTICE", "toll type: " .. toll_type)
local parts = explode(",", toll_allow)
for i,part in ipairs(parts) do
if not known_templates[part] then
logf("WARNING", "unknown toll_allow name: %s in extension", part)
end
tracef("toll_type", "NOTICE", "checking toll allow part " .. part)
if ( part == toll_type ) then
logf("NOTICE", "%s call authorized from %s to %s", toll_type, caller, called)
return
end
end
logf("WARNING", "%s call not authorized from %s to %s : OUTGOING_CALL_BARRED", toll_type, caller, called)
return hungup()
@@ -0,0 +1,30 @@
text = text or {};
text['label-download'] = {};
text['label-download']['en-us'] = "Download";
text['label-download']['es-cl'] = "Descargar";
text['label-download']['pt-pt'] = "Baixar";
text['label-download']['ru-ru'] = "Скачать";
text['label-download']['fr-fr'] = "Télécharger";
text['label-download']['de-de'] = "Download";
text['label-download']['de-at'] = "Download";
text['label-listen'] = {};
text['label-listen']['en-us'] = "Listen";
text['label-listen']['es-cl'] = "Escuchar";
text['label-listen']['pt-pt'] = "Ouvir";
text['label-listen']['ru-ru'] = "Слушать";
text['label-listen']['fr-fr'] = "Écouter";
text['label-listen']['de-de'] = "Anhören";
text['label-listen']['de-at'] = "Anhören";
text['label-attached'] = {};
text['label-attached']['en-us'] = "Attached";
text['label-attached']['es-cl'] = "Adjunto";
text['label-attached']['pt-pt'] = "Ligado";
text['label-attached']['ru-ru'] = "Вложение";
text['label-attached']['fr-fr'] = "Attaché";
text['label-attached']['de-de'] = "im Anhang";
text['label-attached']['de-at'] = "im Anhang";
return text
@@ -0,0 +1,707 @@
-- Part of FusionPBX
-- Copyright (C) 2013-2023 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--set default values
min_digits = 1;
max_digits = 8;
max_tries = 3;
max_timeouts = 3;
digit_timeout = 3000;
stream_seek = false;
--direct dial
direct_dial = {}
direct_dial["enabled"] = "false";
direct_dial["max_digits"] = 4;
--debug
--debug["info"] = true;
--debug["sql"] = true;
--get the argv values
script_name = argv[1];
voicemail_action = argv[2];
--starting values
dtmf_digits = '';
timeouts = 0;
password_tries = 0;
--connect to the database
Database = require "resources.functions.database";
dbh = Database.new('system');
--include json library (as global object)
if (debug["sql"]) then
json = require "resources.functions.lunajson"
end
--set the api
api = freeswitch.API();
--if the session exists
if (session ~= nil) then
--get session variables
context = session:getVariable("context");
sounds_dir = session:getVariable("sounds_dir");
domain_name = session:getVariable("domain_name");
uuid = session:getVariable("uuid");
voicemail_id = session:getVariable("voicemail_id");
voicemail_action = session:getVariable("voicemail_action");
destination_number = session:getVariable("destination_number");
caller_id_name = session:getVariable("caller_id_name");
caller_id_number = session:getVariable("caller_id_number");
current_time_zone = session:getVariable("timezone");
effective_caller_id_number = session:getVariable("effective_caller_id_number");
effective_caller_id_name = session:getVariable("effective_caller_id_name");
voicemail_greeting_number = session:getVariable("voicemail_greeting_number");
skip_instructions = session:getVariable("skip_instructions");
skip_greeting = session:getVariable("skip_greeting");
vm_message_ext = session:getVariable("vm_message_ext");
vm_say_caller_id_number = session:getVariable("vm_say_caller_id_number");
vm_say_date_time = session:getVariable("vm_say_date_time");
vm_disk_quota = session:getVariable("vm-disk-quota");
record_silence_threshold = session:getVariable("record-silence-threshold");
voicemail_authorized = session:getVariable("voicemail_authorized");
sip_from_user = session:getVariable("sip_from_user");
user_name = session:getVariable("user_name");
sip_number_alias = session:getVariable("sip_number_alias");
origination_callee_id_name = session:getVariable("origination_callee_id_name");
--modify caller_id_number if effective_caller_id_number is set
if (effective_caller_id_number ~= nil) then
caller_id_number = effective_caller_id_number;
end
--modify caller_id_name if effective_caller_id_name is set
if (effective_caller_id_name ~= nil) then
caller_id_name = effective_caller_id_name;
end
--set default values
if (string.sub(caller_id_number, 1, 1) == "/") then
caller_id_number = string.sub(caller_id_number, 2, -1);
end
if (not record_silence_threshold) then
record_silence_threshold = 300;
end
if (not vm_disk_quota) then
vm_disk_quota = session:getVariable("vm_disk_quota");
end
if (not vm_message_ext) then
vm_message_ext = 'wav';
end
--set the sounds path for the language, dialect and voice
default_language = session:getVariable("default_language");
default_dialect = session:getVariable("default_dialect");
default_voice = session:getVariable("default_voice");
if (not default_language) then default_language = 'en'; end
if (not default_dialect) then default_dialect = 'us'; end
if (not default_voice) then default_voice = 'callie'; end
--get the domain_uuid
domain_uuid = session:getVariable("domain_uuid");
if (domain_uuid == nil) then
if (domain_name ~= nil) then
local sql = "SELECT domain_uuid FROM v_domains ";
sql = sql .. "WHERE domain_name = :domain_name ";
local params = {domain_name = domain_name};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(rows)
domain_uuid = rows["domain_uuid"];
end);
end
end
if (domain_uuid ~= nil) then
domain_uuid = string.lower(domain_uuid);
end
--if voicemail_id is non numeric then get the number-alias
if (voicemail_id ~= nil) then
if (voicemail_id and #voicemail_id == nil) then
voicemail_id = api:execute("user_data", voicemail_id .. "@" .. domain_name .. " attr number-alias");
end
end
--set the voicemail_dir
voicemail_dir = voicemail_dir.."/default/"..domain_name;
if (debug["info"]) then
freeswitch.consoleLog("notice", "[voicemail] voicemail_dir: " .. voicemail_dir .. "\n");
end
--settings
require "resources.functions.settings";
settings = settings(domain_uuid);
if (settings['voicemail'] ~= nil) then
storage_type = '';
if (settings['voicemail']['storage_type'] ~= nil) then
if (settings['voicemail']['storage_type']['text'] ~= nil) then
storage_type = settings['voicemail']['storage_type']['text'];
end
end
storage_path = '';
if (settings['voicemail']['storage_path'] ~= nil) then
if (settings['voicemail']['storage_path']['text'] ~= nil) then
storage_path = settings['voicemail']['storage_path']['text'];
storage_path = storage_path:gsub("${domain_name}", domain_name);
storage_path = storage_path:gsub("${voicemail_id}", voicemail_id);
storage_path = storage_path:gsub("${voicemail_dir}", voicemail_dir);
end
end
message_order = '';
if (settings['voicemail']['message_order'] ~= nil) then
if (settings['voicemail']['message_order']['text'] ~= nil) then
message_order = settings['voicemail']['message_order']['text'];
end
end
if (settings['voicemail']['message_caller_id_number'] ~= nil) then
if (settings['voicemail']['message_caller_id_number']['text'] ~= nil) then
vm_say_caller_id_number = settings['voicemail']['message_caller_id_number']['text'];
end
end;
if (settings['voicemail']['message_date_time'] ~= nil) then
if (settings['voicemail']['message_date_time']['text'] ~= nil) then
vm_say_date_time = settings['voicemail']['message_date_time']['text'];
end
end
if (not vm_say_caller_id_number) then
vm_say_caller_id_number = "before";
end
if (not vm_say_date_time) then
vm_say_date_time = "before";
end
remote_access = '';
if (settings['voicemail']['remote_access'] ~= nil) then
if (settings['voicemail']['remote_access']['boolean'] ~= nil) then
remote_access = settings['voicemail']['remote_access']['boolean'];
end
end
password_complexity = '';
if (settings['voicemail']['password_complexity'] ~= nil) then
if (settings['voicemail']['password_complexity']['boolean'] ~= nil) then
password_complexity = settings['voicemail']['password_complexity']['boolean'];
end
end
password_min_length = '';
if (settings['voicemail']['password_min_length'] ~= nil) then
if (settings['voicemail']['password_min_length']['numeric'] ~= nil) then
password_min_length = settings['voicemail']['password_min_length']['numeric'];
end
end
not_found_message = 'false';
if (settings['voicemail']['not_found_message'] ~= nil) then
if (settings['voicemail']['not_found_message']['boolean'] ~= nil) then
not_found_message = settings['voicemail']['not_found_message']['boolean'];
end
end
end
if (settings['voicemail']) then
if settings['voicemail']['voicemail_to_sms'] then
voicemail_to_sms = (settings['voicemail']['voicemail_to_sms']['boolean'] == 'true');
end
if settings['voicemail']['voicemail_to_sms_did'] then
voicemail_to_sms_did = settings['voicemail']['voicemail_to_sms_did']['text'];
end
voicemail_to_sms_did = voicemail_to_sms_did or '';
end
if (not temp_dir) or (#temp_dir == 0) then
if (settings['server'] ~= nil) then
if (settings['server']['temp'] ~= nil) then
if (settings['server']['temp']['dir'] ~= nil) then
temp_dir = settings['server']['temp']['dir'];
end
end
end
end
--get the voicemail settings
if (voicemail_id ~= nil) then
if (session ~= nil and session:ready()) then
--get the information from the database
local sql = [[SELECT * FROM v_voicemails
WHERE domain_uuid = :domain_uuid
AND voicemail_id = :voicemail_id
AND voicemail_enabled = 'true' ]];
local params = {domain_uuid = domain_uuid, voicemail_id = voicemail_id};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
voicemail_uuid = string.lower(row["voicemail_uuid"]);
voicemail_password = row["voicemail_password"];
greeting_id = row["greeting_id"];
voicemail_alternate_greet_id = row["voicemail_alternate_greet_id"];
voicemail_mail_to = row["voicemail_mail_to"];
voicemail_attach_file = row["voicemail_attach_file"];
voicemail_local_after_email = row["voicemail_local_after_email"];
voicemail_transcription_enabled = row["voicemail_transcription_enabled"];
voicemail_tutorial = row["voicemail_tutorial"];
end);
--set default values
if (voicemail_local_after_email == nil) then
voicemail_local_after_email = "true";
end
if (voicemail_attach_file == nil) then
voicemail_attach_file = "true";
end
--valid voicemail
if (voicemail_uuid ~= nil and string.len(voicemail_uuid) > 0) then
--answer the session
if (session ~= nil and session:ready()) then
session:answer();
session:sleep('1000');
end
--unset bind meta app
session:execute("unbind_meta_app", "");
end
end
end
end
--set the callback function
if (session ~= nil and session:ready()) then
session:setVariable("playback_terminators", "#");
session:setInputCallback("on_dtmf", "");
end
--general functions
require "resources.functions.base64";
require "resources.functions.trim";
require "resources.functions.file_exists";
require "resources.functions.explode";
require "resources.functions.format_seconds";
require "resources.functions.mkdir";
require "resources.functions.copy";
--voicemail functions
require "app.voicemail.resources.functions.on_dtmf";
require "app.voicemail.resources.functions.get_voicemail_id";
require "app.voicemail.resources.functions.check_password";
require "app.voicemail.resources.functions.change_password";
require "app.voicemail.resources.functions.macro";
require "app.voicemail.resources.functions.play_greeting";
require "app.voicemail.resources.functions.record_message";
require "app.voicemail.resources.functions.record_menu";
require "app.voicemail.resources.functions.forward_add_intro";
require "app.voicemail.resources.functions.forward_to_extension";
require "app.voicemail.resources.functions.main_menu";
require "app.voicemail.resources.functions.listen_to_recording";
require "app.voicemail.resources.functions.message_waiting";
require "app.voicemail.resources.functions.send_email";
require "app.voicemail.resources.functions.send_sms";
require "app.voicemail.resources.functions.delete_recording";
require "app.voicemail.resources.functions.message_saved";
require "app.voicemail.resources.functions.return_call";
require "app.voicemail.resources.functions.menu_messages";
require "app.voicemail.resources.functions.advanced";
require "app.voicemail.resources.functions.record_greeting";
require "app.voicemail.resources.functions.choose_greeting";
require "app.voicemail.resources.functions.record_name";
require "app.voicemail.resources.functions.message_count"
require "app.voicemail.resources.functions.mwi_notify";
require "app.voicemail.resources.functions.blf_notify";
require "app.voicemail.resources.functions.tutorial";
--send a message waiting event
if (voicemail_action == "mwi") then
--get the mailbox info
account = argv[3];
array = explode("@", account);
voicemail_id = array[1];
domain_name = array[2];
--send information the console
debug["info"] = "true";
--get voicemail message details
local sql = [[SELECT * FROM v_domains WHERE domain_name = :domain_name]];
local params = {domain_name = domain_name};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
domain_uuid = string.lower(row["domain_uuid"]);
end);
--get voicemail message details
if (voicemail_id) then
local sql = [[SELECT * FROM v_voicemails
WHERE domain_uuid = :domain_uuid
AND voicemail_id = :voicemail_id]]
local params = {domain_uuid = domain_uuid, voicemail_id = voicemail_id};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
voicemail_local_after_email = row["voicemail_local_after_email"];
end);
--set default values
if (voicemail_local_after_email == nil) then
voicemail_local_after_email = "true";
end
--get the message count and send the mwi event
if (voicemail_local_after_email == 'true') then
message_waiting(voicemail_id, domain_uuid);
end
end
end
--check messages
if (voicemail_action == "check") then
if (session ~= nil and session:ready()) then
--check the voicemail password
if (voicemail_id) then
if (voicemail_authorized) then
if (voicemail_authorized == "true") then
if (voicemail_id == user_name or voicemail_id == sip_number_alias) then
--skip the password check
else
check_password(voicemail_id, password_tries);
end
else
check_password(voicemail_id, password_tries);
end
else
check_password(voicemail_id, password_tries);
end
else
check_password(voicemail_id, password_tries);
end
--send to the main menu
timeouts = 0;
if (voicemail_tutorial == "true") then
tutorial("intro");
else
main_menu();
end
end
end
--leave a message
if (voicemail_action == "save") then
--set the variables
if (session ~= nil and session:ready()) then
session:setVariable("missed_call", "true");
session:setVariable("voicemail_answer_stamp", api:execute("strftime"));
session:setVariable("voicemail_answer_epoch", api:execute("strepoch"));
end
--check the voicemail quota
if (voicemail_uuid ~= nil and vm_disk_quota ~= nil) then
--get voicemail message seconds
local sql = [[SELECT coalesce(sum(message_length), 0) as message_sum FROM v_voicemail_messages
WHERE domain_uuid = :domain_uuid
AND voicemail_uuid = :voicemail_uuid]]
local params = {domain_uuid = domain_uuid, voicemail_uuid = voicemail_uuid};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
message_sum = row["message_sum"];
end);
if (vm_disk_quota and message_sum and #vm_disk_quota <= #message_sum) then
--play message mailbox full
session:execute("playback", sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/voicemail/vm-mailbox_full.wav")
--hangup
session:hangup("NORMAL_CLEARING");
--set the voicemail_uuid to nil to prevent saving the voicemail
voicemail_uuid = nil;
end
end
--valid voicemail
if (voicemail_uuid ~= nil) then
--play the greeting
timeouts = 0;
play_greeting();
--save the message
record_message();
--process base64
if (storage_type == "base64") then
--show the storage type
freeswitch.consoleLog("notice", "[voicemail] ".. storage_type .. "\n");
--include the file io
local file = require "resources.functions.file"
-- build full path to file
local full_path = voicemail_dir.."/"..voicemail_id.."/msg_"..uuid.."."..vm_message_ext
if file_exists(full_path) then
--read file content as base64 string
message_base64 = file.read_base64(full_path);
--freeswitch.consoleLog("notice", "[voicemail] ".. message_base64 .. "\n");
--delete the file
os.remove(full_path);
end
end
--get the voicemail destinations
sql = [[select * from v_voicemail_destinations
where voicemail_uuid = :voicemail_uuid]]
params = {voicemail_uuid=voicemail_uuid};
--freeswitch.consoleLog("notice", "[voicemail][destinations] SQL:" .. sql .. "; params:" .. json.encode(params) .. "\n");
destinations = {};
x = 1;
dbh:query(sql, params, function(row)
destinations[x] = row;
x = x + 1;
end);
table.insert(destinations, {domain_uuid=domain_uuid,voicemail_destination_uuid=voicemail_uuid,voicemail_uuid=voicemail_uuid,voicemail_uuid_copy=voicemail_uuid});
--show the storage type
freeswitch.consoleLog("notice", "[voicemail] ".. storage_type .. "\n");
count = 0
for k,v in pairs(destinations) do
count = count + 1
end
--loop through the voicemail destinations
y = 1;
for key,row in pairs(destinations) do
--determine uuid
if (y == count) then
voicemail_message_uuid = uuid;
else
voicemail_message_uuid = api:execute("create_uuid");
end
y = y + 1;
--save the message to the voicemail messages
if (message_length ~= nil and message_length > 2) then
caller_id_name = string.gsub(caller_id_name,"'","''");
local sql = {}
table.insert(sql, "INSERT INTO v_voicemail_messages ");
table.insert(sql, "(");
table.insert(sql, "voicemail_message_uuid, ");
table.insert(sql, "domain_uuid, ");
table.insert(sql, "voicemail_uuid, ");
table.insert(sql, "created_epoch, ");
table.insert(sql, "caller_id_name, ");
table.insert(sql, "caller_id_number, ");
if (storage_type == "base64") then
table.insert(sql, "message_base64, ");
end
if (transcribe_enabled == "true") and (voicemail_transcription_enabled == "true") then
table.insert(sql, "message_transcription, ");
end
table.insert(sql, "message_length ");
--table.insert(sql, "message_status, ");
--table.insert(sql, "message_priority, ");
table.insert(sql, ") ");
table.insert(sql, "VALUES ");
table.insert(sql, "( ");
table.insert(sql, ":voicemail_message_uuid, ");
table.insert(sql, ":domain_uuid, ");
table.insert(sql, ":voicemail_uuid, ");
table.insert(sql, ":start_epoch, ");
table.insert(sql, ":caller_id_name, ");
table.insert(sql, ":caller_id_number, ");
if (storage_type == "base64") then
table.insert(sql, ":message_base64, ");
end
if (transcribe_enabled == "true") and (voicemail_transcription_enabled == "true") then
table.insert(sql, ":transcription, ");
end
table.insert(sql, ":message_length ");
--table.insert(sql, ":message_status, ");
--table.insert(sql, ":message_priority ");
table.insert(sql, ") ");
sql = table.concat(sql, "\n");
local params = {
voicemail_message_uuid = voicemail_message_uuid;
domain_uuid = domain_uuid;
voicemail_uuid = row.voicemail_uuid_copy;
start_epoch = start_epoch;
caller_id_name = caller_id_name;
caller_id_number = caller_id_number;
message_base64 = message_base64;
transcription = transcription;
message_length = message_length;
--message_status = message_status;
--message_priority = message_priority;
};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
if (storage_type == "base64") then
local Database = require "resources.functions.database"
local dbh = Database.new('system', 'base64');
dbh:query(sql, params);
dbh:release();
else
dbh:query(sql, params);
end
end
local params = {domain_uuid = domain_uuid, voicemail_uuid = row.voicemail_uuid_copy};
--get new message count
sql = [[SELECT count(*) as new_messages FROM v_voicemail_messages
WHERE domain_uuid = :domain_uuid
AND voicemail_uuid = :voicemail_uuid
AND (message_status is null or message_status = '') ]];
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(result)
new_messages = result["new_messages"];
end);
--get saved message count
sql = [[SELECT count(*) as saved_messages FROM v_voicemail_messages
WHERE domain_uuid = :domain_uuid
AND voicemail_uuid = :voicemail_uuid
AND message_status = 'saved' ]];
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(result)
saved_messages = result["saved_messages"];
end);
--get the voicemail_id
sql = [[SELECT voicemail_id FROM v_voicemails WHERE voicemail_uuid = :voicemail_uuid]];
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(result)
voicemail_id_copy = result["voicemail_id"];
end);
--make sure the voicemail directory exists
mkdir(voicemail_dir.."/"..voicemail_id_copy);
--copy the voicemail to each destination
if (file_exists(voicemail_dir.."/"..voicemail_id.."/msg_"..uuid.."."..vm_message_ext)) then
local src = voicemail_dir.."/"..voicemail_id.."/msg_"..uuid.."."..vm_message_ext
local dst = voicemail_dir.."/"..voicemail_id_copy.."/msg_"..voicemail_message_uuid.."."..vm_message_ext
if src ~= dst then
copy(src, dst)
end
end
--send the message waiting event
if (message_length ~= nil and message_length > 2) then
message_waiting(voicemail_id_copy, domain_uuid);
end
--send the email with the voicemail recording attached
if (message_length ~= nil and message_length > 2) then
send_email(voicemail_id_copy, voicemail_message_uuid);
if (voicemail_to_sms) then
send_sms(voicemail_id_copy, voicemail_message_uuid);
end
end
end --for
else
--voicemail not enabled or does not exist
if (session ~= nil and session:ready()) then
referred_by = session:getVariable("sip_h_Referred-By");
if (referred_by) then
referred_by = referred_by:match('[%d]+');
session:transfer(referred_by, "XML", context);
else
if (not_found_message == "true") then
session:answer();
session:execute("sleep", "1000");
session:execute("playback", sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/voicemail/vm-no_answer_no_vm.wav");
session:hangup();
end
end
end
end
end
--close the database connection
dbh:release();
--notes
--record the video
--records audio only
--result = session:execute("set", "enable_file_write_buffering=false");
--mkdir(voicemail_dir.."/"..voicemail_id);
--session:recordFile("/tmp/recording.fsv", 200, 200, 200);
--records audio and video
--result = session:execute("record_fsv", "file.fsv");
--freeswitch.consoleLog("notice", "[voicemail] SQL: " .. result .. "\n");
--play the video recording
--plays the video
--result = session:execute("play_fsv", "/tmp/recording.fsv");
--plays the file but without the video
--dtmf = session:playAndGetDigits(min_digits, max_digits, max_tries, digit_timeout, "#", "/tmp/recording.fsv", "", "\\d+");
--freeswitch.consoleLog("notice", "[voicemail] SQL: " .. result .. "\n");
--callback (works with DTMF)
--http://wiki.freeswitch.org/wiki/Mod_fsv
--mkdir(voicemail_dir.."/"..voicemail_id);
--session:recordFile(file_name, max_len_secs, silence_threshold, silence_secs)
--session:sayPhrase(macro_name [,macro_data] [,language]);
--session:sayPhrase("voicemail_menu", "1:2:3:#", default_language);
--session:streamFile("directory/dir-to_select_entry.wav"); --works with setInputCallback
--session:streamFile("tone_stream://L=1;%(1000, 0, 640)");
--session:say("12345", default_language, "number", "pronounced");
--speak
--session:set_tts_parms("flite", "kal");
--session:speak("Please say the name of the person you're trying to contact");
--callback (execute and executeString does not work with DTMF)
--session:execute(api_string);
--session:executeString("playback "..mySound);
--uuid_video_refresh
--uuid_video_refresh,<uuid>,Send video refresh.,mod_commands
--may be used to clear video buffer before using record_fsv
@@ -0,0 +1,68 @@
-- Part of FusionPBX
-- Copyright (C) 2013 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--define a function for the advanced menu
function advanced ()
--clear the dtmf
dtmf_digits = '';
--flush dtmf digits from the input buffer
session:flushDigits();
--play entire menu using phrases
if (session:ready()) then
dtmf_digits = session:playAndGetDigits(1, 1, max_tries, digit_timeout, "#", "phrase:voicemail_config_menu:1:2:3:6:0", "", "\\d+");
end
--process the dtmf
if (session:ready()) then
if (dtmf_digits == "1") then
--To record a greeting press 1
timeouts = 0;
record_greeting(nil,"advanced");
elseif (dtmf_digits == "2") then
--To choose greeting press 2
timeouts = 0;
choose_greeting();
elseif (dtmf_digits == "3") then
--To record your name 3
record_name("advanced");
elseif (dtmf_digits == "6") then
--To change your password press 6
change_password(voicemail_id, "advanced");
elseif (dtmf_digits == "0") then
--For the main menu press 0
timeouts = 0;
main_menu();
else
timeouts = timeouts + 1;
if (timeouts <= max_timeouts) then
advanced();
else
session:execute("playback", "phrase:voicemail_goodbye");
session:hangup();
end
end
end
end
@@ -0,0 +1,43 @@
function blf_notify(on, user, uuid)
require "resources.functions.split"
local api = require "resources.functions.api"
local log = require "resources.functions.log".presence
log.debugf('turn_lamp: %s - %s(%s)', tostring(user), tostring(on), type(on))
local userid, domain, proto = split_first(user, "@", true)
proto, userid = split_first(userid, "+", true)
if userid then
user = userid .. "@" .. domain
else
proto = "sip"
end
uuid = uuid or api:execute('create_uuid')
local event = freeswitch.Event("PRESENCE_IN");
event:addHeader("proto", proto);
event:addHeader("event_type", "presence");
event:addHeader("alt_event_type", "dialog");
event:addHeader("Presence-Call-Direction", "outbound");
event:addHeader("from", user);
event:addHeader("login", user);
event:addHeader("unique-id", uuid);
event:addHeader("status", "Active (1 waiting)");
if on then
event:addHeader("answer-state", "confirmed");
event:addHeader("rpid", "unknown");
event:addHeader("event_count", "1");
else
event:addHeader("answer-state", "terminated");
end
-- log.debug(event:serialize())
event:fire();
end
return {
turn_lamp = turn_lamp;
}
@@ -0,0 +1,123 @@
-- Part of FusionPBX
-- Copyright (C) 2013 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--check the voicemail password
function change_password(voicemail_id, menu)
if (session:ready()) then
--flush dtmf digits from the input buffer
session:flushDigits();
--set password valitity in case of hangup
valid_password = "false";
--please enter your password followed by pound
dtmf_digits = '';
password = session:playAndGetDigits(1, 20, max_tries, 5000, "#", "phrase:voicemail_enter_new_pass", "", "\\d+");
if (password_complexity ~= "true") then
valid_password = "true";
end
--check password comlexity
if (password_complexity == "true") then
--check for length
if (string.len(password) < tonumber(password_min_length)) then
password_error_flag = "1";
dtmf_digits = '';
--freeswitch.consoleLog("notice", "[voicemail] Not long enough \n");
session:execute("playback", "phrase:voicemail_password_below_minimum:" .. password_min_length);
timeouts = 0;
if (menu == "tutorial") then
change_password(voicemail_id, "tutorial");
end
if (menu == "advanced") then
change_password(voicemail_id, "advanced");
end
end
--check for repeating digits
local repeating = {"000", "111", "222", "333", "444", "555", "666", "777", "888", "999"};
for i = 1, 10 do
if (string.match(password, repeating[i])) then
password_error_flag = "1";
dtmf_digits = '';
--freeswitch.consoleLog("notice", "[voicemail] You can't use repeating digits like ".. repeating[i] .." \n");
session:execute("playback", "phrase:voicemail_password_not_secure");
timeouts = 0;
if (menu == "tutorial") then
change_password(voicemail_id, "tutorial");
end
if (menu == "advanced") then
change_password(voicemail_id, "advanced");
end
end
end
--check for squential digits
local sequential = {"012", "123", "234", "345", "456", "567", "678", "789", "987", "876", "765", "654", "543", "432", "321", "210"};
for i = 1, 8 do
if (string.match(password, sequential[i])) then
password_error_flag = "1";
dtmf_digits = '';
--freeswitch.consoleLog("notice", "[voicemail] You can't use sequential digits like ".. sequential[i] .." \n");
session:execute("playback", "phrase:voicemail_password_not_secure");
timeouts = 0;
if (menu == "tutorial") then
change_password(voicemail_id, "tutorial");
end
if (menu == "advanced") then
change_password(voicemail_id, "advanced");
end
end
end
--password is valid
if (password_error_flag ~= "1") then
freeswitch.consoleLog("notice", "[voicemail] Password is valid! \n");
valid_password = "true";
end
end
--update the voicemail password
if (valid_password == "true") then
local sql = [[UPDATE v_voicemails
set voicemail_password = :password
WHERE domain_uuid = :domain_uuid
AND voicemail_id = :voicemail_id
AND voicemail_enabled = 'true' ]];
local params = {password = password, domain_uuid = domain_uuid,
voicemail_id = voicemail_id};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params);
end
--has been changed to
dtmf_digits = '';
session:execute("playback", "phrase:voicemail_change_pass_repeat_success:" .. password);
--advanced menu
timeouts = 0;
if (menu == "advanced") then
advanced();
end
if (menu == "tutorial") then
tutorial("record_greeting");
end
end
end
@@ -0,0 +1,95 @@
-- Part of FusionPBX
-- Copyright (C) 2013 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--check the voicemail password
function check_password(voicemail_id, password_tries)
if (session:ready()) then
--flush dtmf digits from the input buffer
session:flushDigits();
--please enter your id followed by pound
if (voicemail_id) then
--do nothing
else
timeouts = 0;
voicemail_id = get_voicemail_id();
if (debug["info"]) then
freeswitch.consoleLog("notice", "[voicemail] voicemail id: " .. voicemail_id .. "\n");
end
end
--get the voicemail settings from the database
if (voicemail_id) then
if (session:ready()) then
local sql = [[SELECT * FROM v_voicemails
WHERE domain_uuid = :domain_uuid
AND voicemail_id = :voicemail_id
AND voicemail_enabled = 'true' ]];
local params = {domain_uuid = domain_uuid, voicemail_id = voicemail_id};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
voicemail_uuid = string.lower(row["voicemail_uuid"]);
voicemail_password = row["voicemail_password"];
greeting_id = row["greeting_id"];
voicemail_mail_to = row["voicemail_mail_to"];
voicemail_attach_file = row["voicemail_attach_file"];
voicemail_local_after_email = row["voicemail_local_after_email"];
end);
end
end
--end the session if this is an invalid voicemail box
if (not voicemail_uuid) or (#voicemail_uuid == 0) then
return session:hangup();
end
--please enter your password followed by pound
min_digits = 2;
max_digits = 20;
digit_timeout = 5000;
max_tries = 3;
password = session:playAndGetDigits(min_digits, max_digits, max_tries, digit_timeout, "#", "phrase:voicemail_enter_pass:#", "", "\\d+");
--freeswitch.consoleLog("notice", "[voicemail] password: " .. password .. "\n");
--compare the password from the database with the password provided by the user
if (voicemail_password ~= password) then
--incorrect password
dtmf_digits = '';
session:execute("playback", "phrase:voicemail_fail_auth");
if (session:ready()) then
password_tries = password_tries + 1;
if (password_tries < max_tries) then
check_password(voicemail_id, password_tries);
else
session:execute("playback", "phrase:voicemail_goodbye");
session:hangup();
end
end
end
end
end
@@ -0,0 +1,167 @@
-- Part of FusionPBX
-- Copyright (C) 2013 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
local Database = require "resources.functions.database"
--define a function to choose the greeting
function choose_greeting()
--flush dtmf digits from the input buffer
session:flushDigits();
--select the greeting
if (session:ready()) then
dtmf_digits = '';
greeting_id = session:playAndGetDigits(1, 1, max_tries, 5000, "#", "phrase:voicemail_choose_greeting", "", "\\d+");
end
--check to see if the greeting file exists
if (storage_type == "base64" or storage_type == "http_cache") then
greeting_invalid = true;
local sql = [[SELECT * FROM v_voicemail_greetings
WHERE domain_uuid = :domain_uuid
AND voicemail_id = :voicemail_id
AND greeting_id = :greeting_id]];
local params = {domain_uuid = domain_uuid, voicemail_id = voicemail_id,
greeting_id = greeting_id};
dbh:query(sql, params, function(row)
--greeting found
greeting_invalid = false;
end);
if (greeting_invalid) then
greeting_id = "invalid";
end
else
if (greeting_id ~= "0") then
if (not file_exists(voicemail_dir.."/"..voicemail_id.."/greeting_"..greeting_id..".wav")) then
--invalid greeting_id file does not exist
greeting_id = "invalid";
end
end
end
--validate the greeting_id
if (greeting_id == "0"
or greeting_id == "1"
or greeting_id == "2"
or greeting_id == "3"
or greeting_id == "4"
or greeting_id == "5"
or greeting_id == "6"
or greeting_id == "7"
or greeting_id == "8"
or greeting_id == "9") then
--valid greeting_id update the database
if (session:ready()) then
local params = {domain_uuid = domain_uuid, voicemail_uuid = voicemail_uuid};
local sql = "UPDATE v_voicemails SET "
if (greeting_id == "0") then
sql = sql .. "greeting_id = null ";
else
sql = sql .. "greeting_id = :greeting_id ";
params.greeting_id = greeting_id;
end
sql = sql .. "WHERE domain_uuid = :domain_uuid ";
sql = sql .. "AND voicemail_uuid = :voicemail_uuid ";
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params);
end
--get the greeting from the database
if (storage_type == "base64") then
local dbh = Database.new('system', 'base64/read')
local sql = [[SELECT greeting_base64
FROM v_voicemail_greetings
WHERE domain_uuid = :domain_uuid
AND voicemail_id = :voicemail_id
AND greeting_id = :greeting_id]];
local params = {
domain_uuid = domain_uuid;
voicemail_id = voicemail_id;
greeting_id = greeting_id;
};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
--set the voicemail message path
greeting_location = voicemail_dir.."/"..voicemail_id.."/greeting_"..greeting_id..".wav"; --vm_message_ext;
--save the greeting to the file system
if (string.len(row["greeting_base64"]) > 32) then
--include the file io
local file = require "resources.functions.file"
--write decoded string to file
assert(file.write_base64(greeting_location, row["greeting_base64"]));
end
end);
dbh:release()
elseif (storage_type == "http_cache") then
greeting_location = storage_path.."/"..voicemail_id.."/greeting_"..greeting_id..".wav"; --vm_message_ext;
end
--play the greeting
if (session:ready()) then
if (file_exists(voicemail_dir.."/"..voicemail_id.."/greeting_"..greeting_id..".wav")) then
session:streamFile(voicemail_dir.."/"..voicemail_id.."/greeting_"..greeting_id..".wav");
end
end
--greeting selected
if (session:ready()) then
dtmf_digits = '';
session:execute("playback", "phrase:voicemail_greeting_selected:" .. greeting_id);
end
--advanced menu
if (session:ready()) then
timeouts = 0;
advanced();
end
else
--invalid greeting_id
if (session:ready()) then
dtmf_digits = '';
session:execute("playback", "phrase:voicemail_choose_greeting_fail");
end
--send back to choose the greeting
if (session:ready()) then
timeouts = timeouts + 1;
if (timeouts < max_timeouts) then
choose_greeting();
else
timeouts = 0;
advanced();
end
end
end
end
@@ -0,0 +1,65 @@
-- Part of FusionPBX
-- Copyright (C) 2013-2016 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--delete the message
function delete_recording(voicemail_id, uuid)
--message deleted
if (session ~= nil) then
if (session:ready()) then
dtmf_digits = '';
session:execute("playback", "phrase:voicemail_ack:deleted");
end
end
--get the voicemail_uuid
local sql = [[SELECT * FROM v_voicemails
WHERE domain_uuid = :domain_uuid
AND voicemail_id = :voicemail_id]];
local params = {domain_uuid = domain_uuid, voicemail_id = voicemail_id};
dbh:query(sql, params, function(row)
db_voicemail_uuid = row["voicemail_uuid"];
end);
--flush dtmf digits from the input buffer
session:flushDigits();
--delete the file
os.remove(voicemail_dir.."/"..voicemail_id.."/intro_"..uuid.."."..vm_message_ext);
os.remove(voicemail_dir.."/"..voicemail_id.."/msg_"..uuid.."."..vm_message_ext);
--delete from the database
sql = [[DELETE FROM v_voicemail_messages
WHERE domain_uuid = :domain_uuid
AND voicemail_uuid = :voicemail_uuid
AND voicemail_message_uuid = :uuid]];
params = {domain_uuid = domain_uuid, voicemail_uuid = db_voicemail_uuid, uuid = uuid};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params);
--log to console
if (debug["info"]) then
freeswitch.consoleLog("notice", "[voicemail][deleted] message: " .. uuid .. "\n");
end
--clear the variable
db_voicemail_uuid = '';
end
@@ -0,0 +1,122 @@
-- Part of FusionPBX
-- Copyright (C) 2016 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--define a function to forward a message to an extension
function forward_add_intro(voicemail_id, uuid)
--flush dtmf digits from the input buffer
session:flushDigits();
--request whether to add the intro
--To add an introduction to this message press 1
add_intro_id = session:playAndGetDigits(1, 1, 3, 5000, "#*", "phrase:voicemail_forward_prepend:1:2", "", "\\d+");
freeswitch.consoleLog("notice", "[voicemail][forward add intro] "..add_intro_id.."\n");
if (add_intro_id == '1') then
--load libraries
local Database = require "resources.functions.database";
local Settings = require "resources.functions.lazy_settings";
--connect to the database
local db = dbh or Database.new('system');
--get the settings.
local settings = Settings.new(db, domain_name, domain_uuid);
local max_len_seconds = settings:get('voicemail', 'max_len_seconds', 'boolean') or 300;
--record your message at the tone press any key or stop talking to end the recording
if (session:ready()) then
session:execute("playback", "phrase:voicemail_record_greeting");
session:execute("sleep", "1000");
session:streamFile("tone_stream://L=1;%(1000, 0, 640)");
end
--set the file full path
message_location = voicemail_dir.."/"..voicemail_id.."/msg_"..uuid.."."..vm_message_ext;
message_intro_location = voicemail_dir.."/"..voicemail_id.."/intro_"..uuid.."."..vm_message_ext;
--record the message introduction
-- syntax is session:recordFile(file_name, max_len_secs, silence_threshold, silence_secs)
silence_seconds = 5;
if (storage_path == "http_cache") then
result = session:recordFile(message_intro_location, max_len_seconds, record_silence_threshold, silence_seconds);
else
mkdir(voicemail_dir.."/"..voicemail_id);
if (vm_message_ext == "mp3") then
shout_exists = trim(api:execute("module_exists", "mod_shout"));
if (shout_exists == "true") then
freeswitch.consoleLog("notice", "using mod_shout for mp3 encoding\n");
--record in mp3 directly
result = session:recordFile(message_intro_location, max_len_seconds, record_silence_threshold, silence_seconds);
else
--create initial wav recording
result = session:recordFile(message_intro_location, max_len_seconds, record_silence_threshold, silence_seconds);
--use lame to encode, if available
if (file_exists("/usr/bin/lame")) then
freeswitch.consoleLog("notice", "using lame for mp3 encoding\n");
--convert the wav to an mp3 (lame required)
resample = "/usr/bin/lame -b 32 --resample 8 -m s "..voicemail_dir.."/"..voicemail_id.."/intro_"..uuid..".wav "..message_intro_location;
session:execute("system", resample);
--delete the wav file, if mp3 exists
if (file_exists(message_intro_location)) then
os.remove(voicemail_dir.."/"..voicemail_id.."/intro_"..uuid..".wav");
else
vm_message_ext = "wav";
end
else
freeswitch.consoleLog("notice", "neither mod_shout or lame found, defaulting to wav\n");
vm_message_ext = "wav";
end
end
else
result = session:recordFile(message_intro_location, max_len_seconds, record_silence_threshold, silence_seconds);
end
end
--save the merged file into the database as base64
if (storage_type == "base64") then
local file = require "resources.functions.file"
--get the content of the file
local file_content = assert(file.read_base64(message_intro_location));
--save the merged file as base64
local sql = [[UPDATE SET v_voicemail_messages
SET message_intro_base64 = :file_content
WHERE domain_uuid = :domain_uuid
AND voicemail_message_uuid = :uuid]];
local params = {file_content = file_content, domain_uuid = domain_uuid, uuid = uuid};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params: " .. json.encode(params) .. "\n");
end
local dbh = Database.new('system', 'base64')
dbh:query(sql, params)
dbh:release()
end
end
end
@@ -0,0 +1,181 @@
-- Part of FusionPBX
-- Copyright (C) 2013 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--define a function to forward a message to an extension
function forward_to_extension(voicemail_id, uuid)
--flush dtmf digits from the input buffer
session:flushDigits();
--request the forward_voicemail_id
if (session:ready()) then
dtmf_digits = '';
forward_voicemail_id = session:playAndGetDigits(1, 20, max_tries, digit_timeout, "#", "phrase:voicemail_forward_message_enter_extension:#", "", "\\d+");
if (session:ready()) then
if (string.len(forward_voicemail_id) == 0) then
dtmf_digits = '';
forward_voicemail_id = session:playAndGetDigits(1, 20, max_tries, digit_timeout, "#", "phrase:voicemail_forward_message_enter_extension:#", "", "\\d+");
end
end
end
if (session:ready()) then
if (string.len(forward_voicemail_id) == 0) then
dtmf_digits = '';
forward_voicemail_id = session:playAndGetDigits(1, 20, max_tries, digit_timeout, "#", "phrase:voicemail_forward_message_enter_extension:#", "", "\\d+");
end
end
--get voicemail message details
if (session:ready()) then
local sql = [[SELECT * FROM v_voicemail_messages
WHERE domain_uuid = :domain_uuid
AND voicemail_uuid = :voicemail_uuid
AND voicemail_message_uuid = :uuid]]
local params = {domain_uuid = domain_uuid, voicemail_uuid = voicemail_uuid, uuid = uuid};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
--get the values from the database
created_epoch = row["created_epoch"];
caller_id_name = row["caller_id_name"];
caller_id_number = row["caller_id_number"];
message_length = row["message_length"];
message_status = row["message_status"];
message_priority = row["message_priority"];
message_base64 = row["message_base64"];
end);
end
--get the voicemail settings
local sql = [[SELECT * FROM v_voicemails
WHERE domain_uuid = :domain_uuid
AND voicemail_id = :voicemail_id
AND voicemail_enabled = 'true' ]];
local params = {domain_uuid = domain_uuid, voicemail_id = forward_voicemail_id};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
forward_voicemail_uuid = string.lower(row["voicemail_uuid"]);
forward_voicemail_mail_to = row["voicemail_mail_to"];
forward_voicemail_attach_file = row["voicemail_attach_file"];
forward_voicemail_local_after_email = row["voicemail_local_after_email"];
end);
--get a new uuid
api = freeswitch.API();
voicemail_message_uuid = trim(api:execute("create_uuid", ""));
--save the message to the voicemail messages
local sql = {}
table.insert(sql, "INSERT INTO v_voicemail_messages ");
table.insert(sql, "(");
table.insert(sql, "voicemail_message_uuid, ");
table.insert(sql, "domain_uuid, ");
table.insert(sql, "voicemail_uuid, ");
if (storage_type == "base64") then
table.insert(sql, "message_base64, ");
end
table.insert(sql, "created_epoch, ");
table.insert(sql, "caller_id_name, ");
table.insert(sql, "caller_id_number, ");
table.insert(sql, "message_length ");
--table.insert(sql, "message_status, ");
--table.insert(sql, "message_priority, ");
table.insert(sql, ") ");
table.insert(sql, "VALUES ");
table.insert(sql, "( ");
table.insert(sql, ":voicemail_message_uuid, ");
table.insert(sql, ":domain_uuid, ");
table.insert(sql, ":forward_voicemail_uuid, ");
if (storage_type == "base64") then
table.insert(sql, ":message_base64, ");
end
table.insert(sql, ":created_epoch, ");
table.insert(sql, ":caller_id_name, ");
table.insert(sql, ":caller_id_number, ");
table.insert(sql, ":message_length ");
--table.insert(sql, ":message_status, ");
--table.insert(sql, ":message_priority ");
table.insert(sql, ") ");
sql = table.concat(sql, "\n");
local params = {
voicemail_message_uuid = voicemail_message_uuid;
domain_uuid = domain_uuid;
forward_voicemail_uuid = forward_voicemail_uuid;
message_base64 = message_base64;
created_epoch = created_epoch;
caller_id_name = caller_id_name;
caller_id_number = caller_id_number;
message_length = message_length;
-- message_status = message_status;
-- message_priority = message_priority;
};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
if (storage_type == "base64") then
local dbh = Database.new('system', 'base64')
dbh:query(sql, params);
dbh:release();
else
dbh:query(sql, params);
end
--offer to add an intro to the forwarded message
forward_add_intro(forward_voicemail_id, voicemail_message_uuid);
--get new and saved message counts
local new_messages, saved_messages = message_count_by_id(
forward_voicemail_id, domain_uuid
)
--send the message waiting event
mwi_notify(forward_voicemail_id.."@"..domain_name, new_messages, saved_messages)
blf_notify(tonumber(new_messages) > 0, "voicemail+" .. forward_voicemail_id .. "@" .. domain_name)
--if local after email is true then copy the recording file
if (storage_type ~= "base64") then
mkdir(voicemail_dir.."/"..forward_voicemail_id);
copy(voicemail_dir.."/"..voicemail_id.."/msg_"..uuid.."."..vm_message_ext, voicemail_dir.."/"..forward_voicemail_id.."/msg_"..voicemail_message_uuid.."."..vm_message_ext);
end
--send the email with the voicemail recording attached
send_email(forward_voicemail_id, voicemail_message_uuid);
session:streamFile(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/voicemail/vm-message_forwarded.wav");
--delete or save the message
local action = session:playAndGetDigits(1, 1, max_tries, digit_timeout, "#", "phrase:voicemail_post_forward_action:1:2", "", "^[1-2]$");
if (action == "1") then
delete_recording(voicemail_id, uuid);
message_waiting(voicemail_id, domain_uuid);
else
message_saved(voicemail_id, uuid);
session:execute("playback", "phrase:voicemail_ack:saved");
end
end
@@ -0,0 +1,43 @@
-- Part of FusionPBX
-- Copyright (C) 2013 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--get the voicemail id
function get_voicemail_id()
session:flushDigits();
dtmf_digits = '';
voicemail_id = session:playAndGetDigits(1, 20, max_tries, digit_timeout, "#", "phrase:voicemail_enter_id:#", "", "\\d+");
if (string.len(voicemail_id) == 0) then
if (session:ready()) then
timeouts = timeouts + 1;
if (timeouts < max_timeouts) then
voicemail_id = get_voicemail_id();
else
session:execute("playback", "phrase:voicemail_goodbye");
session:hangup();
end
end
end
return voicemail_id;
end
@@ -0,0 +1,284 @@
-- Part of FusionPBX
-- Copyright (C) 2013-2022 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--define function to listen to the recording
function listen_to_recording (message_number, uuid, created_epoch, caller_id_name, caller_id_number, message_status)
--set default values
dtmf_digits = '';
max_digits = 1;
--flush dtmf digits from the input buffer
session:flushDigits();
--set the callback function
if (session:ready()) then
session:setVariable("playback_terminators", "#");
session:setInputCallback("on_dtmf", "");
end
--set the display
if (session:ready()) then
reply = api:executeString("uuid_display "..session:get_uuid().." "..caller_id_number);
end
--say the message number
if (session:ready()) then
if (string.len(dtmf_digits) == 0) then
session:execute("playback", "phrase:voicemail_say_message_number:" .. message_status .. ":" .. message_number);
end
end
--say the caller id number first (default)
if (
session:ready() and
caller_id_number ~= nil and (
vm_say_caller_id_number == nil or
vm_say_caller_id_number == "true" or
vm_say_caller_id_number == "before"
)) then
session:streamFile(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/voicemail/vm-from.wav");
session:say(caller_id_number, default_language, "name_spelled", "iterated");
end
--say the message date first (default)
if (
session:ready() and
string.len(dtmf_digits) == 0 and (
vm_say_date_time == nil or
vm_say_date_time == "true" or
vm_say_date_time == "before"
)) then
if (current_time_zone ~= nil) then
session:execute("set", "timezone="..current_time_zone.."");
end
session:say(created_epoch, default_language, "current_date_time", "pronounced");
end
--get the recordings from the database
if (storage_type == "base64") then
local dbh = Database.new('system', 'base64/read')
local sql = [[SELECT * FROM v_voicemail_messages
WHERE domain_uuid = :domain_uuid
AND voicemail_message_uuid = :uuid]];
local params = {domain_uuid = domain_uuid, uuid = uuid};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
--set the voicemail message path
mkdir(voicemail_dir.."/"..voicemail_id);
message_intro_location = voicemail_dir.."/"..voicemail_id.."/intro_"..uuid.."."..vm_message_ext;
message_location = voicemail_dir.."/"..voicemail_id.."/msg_"..uuid;
--save the recording to the file system
if (string.len(row["message_intro_base64"]) > 32) then
local file = io.open(message_intro_location, "w");
file:write(base64.decode(row["message_intro_base64"]));
file:close();
end
if (string.len(row["message_base64"]) > 32) then
--include the file io
local file = require "resources.functions.file"
--write decoded string to file
assert(file.write_base64(message_location, row["message_base64"]));
end
--get the file type
command = "file -b --mime-type "..message_location;
local handle = io.popen(command);
local mime_type = trim(handle:read("*a"));
handle:close();
if (mime_type == 'audio/x-wav') then
vm_message_ext = 'wav';
end
if (mime_type == 'audio/mpeg') then
vm_message_ext = 'mp3';
end
--rename the file
os.execute('mv '..message_location..' '..message_location..'.'..vm_message_ext);
end);
dbh:release()
elseif (storage_type == "http_cache") then
message_location = storage_path.."/"..voicemail_id.."/msg_"..uuid.."."..vm_message_ext;
end
--play the message intro
if (session:ready()) then
if (string.len(dtmf_digits) == 0) then
if (file_exists(voicemail_dir.."/"..voicemail_id.."/intro_"..uuid.."."..vm_message_ext)) then
stream_seek = true;
if (storage_type == "http_cache") then
message_intro_location = storage_path.."/"..voicemail_id.."/intro_"..uuid.."."..vm_message_ext;
session:streamFile(storage_path.."/"..voicemail_id.."/intro_"..uuid.."."..vm_message_ext);
else
if (vm_message_ext == "mp3") then
if (api:executeString("module_exists mod_vlc") == "true") then
session:streamFile("vlc://"..voicemail_dir.."/"..voicemail_id.."/intro_"..uuid.."."..vm_message_ext);
else
session:streamFile(voicemail_dir.."/"..voicemail_id.."/intro_"..uuid.."."..vm_message_ext);
end
else
session:streamFile(voicemail_dir.."/"..voicemail_id.."/intro_"..uuid.."."..vm_message_ext);
end
end
stream_seek = false;
--session:streamFile("silence_stream://1000");
end
end
end
--play the message
if (session:ready()) then
if (string.len(dtmf_digits) == 0) then
--check if wav file exists then play the file
if (file_exists(voicemail_dir.."/"..voicemail_id.."/msg_"..uuid..".wav")) then
stream_seek = true;
if (storage_type == "http_cache") then
message_location = storage_path.."/"..voicemail_id.."/msg_"..uuid..".wav";
session:streamFile(storage_path.."/"..voicemail_id.."/msg_"..uuid..".wav");
else
session:streamFile(voicemail_dir.."/"..voicemail_id.."/msg_"..uuid..".wav");
end
stream_seek = false;
session:streamFile("silence_stream://1000");
end
--check if mp3 file exists then play the file
if (file_exists(voicemail_dir.."/"..voicemail_id.."/msg_"..uuid..".mp3")) then
stream_seek = true;
if (storage_type == "http_cache") then
message_location = storage_path.."/"..voicemail_id.."/msg_"..uuid..".mp3";
session:streamFile(storage_path.."/"..voicemail_id.."/msg_"..uuid..".mp3");
else
if (api:executeString("module_exists mod_vlc") == "true") then
session:streamFile("vlc://"..voicemail_dir.."/"..voicemail_id.."/msg_"..uuid..".mp3");
else
session:streamFile(voicemail_dir.."/"..voicemail_id.."/msg_"..uuid..".mp3");
end
end
stream_seek = false;
session:streamFile("silence_stream://1000");
end
end
end
--remove the voicemail message
if (storage_type == "base64") then
os.remove(voicemail_dir.."/"..voicemail_id.."/intro_"..uuid.."."..vm_message_ext);
os.remove(voicemail_dir.."/"..voicemail_id.."/msg_"..uuid.."."..vm_message_ext);
end
--say the caller id number last (optional)
if (
session:ready() and
caller_id_number ~= nil and
vm_say_caller_id_number ~= nil and
(
vm_say_caller_id_number == "last" or
vm_say_caller_id_number == "after"
)) then
session:streamFile(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/voicemail/vm-from.wav");
session:say(caller_id_number, default_language, "name_spelled", "iterated");
end
--say the message date last (optional)
if (
session:ready() and
string.len(dtmf_digits) == 0 and
vm_say_date_time ~= nil and
(
vm_say_date_time == "last" or
vm_say_date_time == "after"
)) then
if (current_time_zone ~= nil) then
session:execute("set", "timezone="..current_time_zone.."");
end
session:say(created_epoch, default_language, "current_date_time", "pronounced");
end
--post listen options
if (session:ready()) then
if (string.len(dtmf_digits) == 0) then
dtmf_digits = session:playAndGetDigits(1, 1, max_tries, digit_timeout, "#", "phrase:voicemail_listen_file_options:1:2:3:5:7:8:9:0", "", "^[\\d\\*#]$");
end
end
--wait for more digits
--if (session:ready()) then
-- if (string.len(dtmf_digits) == 0) then
-- dtmf_digits = session:getDigits(max_digits, "#", 1, 3000);
-- end
--end
--process the dtmf
if (session:ready()) then
if (dtmf_digits == "1") then
return listen_to_recording(message_number, uuid, created_epoch, caller_id_name, caller_id_number, message_status);
elseif (dtmf_digits == "2") then
message_saved(voicemail_id, uuid);
session:execute("playback", "phrase:voicemail_ack:saved");
elseif (dtmf_digits == "3") then
session:streamFile(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/voicemail/vm-from.wav");
session:say(caller_id_number, default_language, "name_spelled", "iterated");
if (current_time_zone ~= nil) then
session:execute("set", "timezone="..current_time_zone.."");
end
session:say(created_epoch, default_language, "current_date_time", "pronounced");
elseif (dtmf_digits == "5") then
message_saved(voicemail_id, uuid);
return_call(caller_id_number);
elseif (dtmf_digits == "7") then
delete_recording(voicemail_id, uuid);
message_waiting(voicemail_id, domain_uuid);
--fix for extensions that start with 0 (Ex: 0712)
if (voicemail_id_copy ~= voicemail_id and voicemail_id_copy ~= nil) then
message_waiting(voicemail_id_copy, domain_uuid);
end
elseif (dtmf_digits == "8") then
forward_to_extension(voicemail_id, uuid);
dtmf_digits = '';
elseif (dtmf_digits == "9") then
send_email(voicemail_id, uuid);
dtmf_digits = '';
session:execute("playback", "phrase:voicemail_ack:emailed");
elseif (dtmf_digits == "*") then
timeouts = 0;
return main_menu();
elseif (dtmf_digits == "0") then
message_saved(voicemail_id, uuid);
session:transfer("0", "XML", context);
elseif (dtmf_digits == "#") then
return;
else
message_saved(voicemail_id, uuid);
session:execute("playback", "phrase:voicemail_ack:saved");
end
session:execute("sleep", "400");
end
end
@@ -0,0 +1,365 @@
-- Part of FusionPBX
-- Copyright (C) 2013 - 2016 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--define the macro function
function macro(session, name, max_digits, max_timeout, param)
if (session:ready()) then
--create an empty table
actions = {}
--Please enter your id followed by
if (name == "voicemail_id") then
table.insert(actions, {app="streamFile",data="phrase:voicemail_enter_id:#"});
end
--Please enter your id followed by
if (name == "voicemail_password") then
table.insert(actions, {app="streamFile",data="phrase:voicemail_enter_pass:#"});
end
--the person at extension 101 is not available record your message at the tone press any key or stop talking to end the recording
if (name == "person_not_available_record_message") then
table.insert(actions, {app="streamFile",data="voicemail/vm-person.wav"});
--pronounce the voicemail_id
if (voicemail_alternate_greet_id and string.len(voicemail_alternate_greet_id) > 0) then
table.insert(actions, {app="say.number.iterated",data=voicemail_alternate_greet_id});
elseif (voicemail_greet_id and string.len(voicemail_greet_id) > 0) then
table.insert(actions, {app="say.number.iterated",data=voicemail_greet_id});
else
table.insert(actions, {app="say.number.iterated",data=voicemail_id});
end
table.insert(actions, {app="streamFile",data="voicemail/vm-not_available.wav"});
end
--record your message at the tone press any key or stop talking to end the recording
if (name == "record_message") then
table.insert(actions, {app="streamFile",data="voicemail/vm-record_message.wav"});
end
--beep
if (name == "record_beep") then
table.insert(actions, {app="tone_stream",data="L=1;%(1000, 0, 640)"});
end
--to listen to the recording press 1
if (name == "to_listen_to_recording") then
table.insert(actions, {app="streamFile",data="voicemail/vm-listen_to_recording.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-press.wav"});
table.insert(actions, {app="streamFile",data="digits/1.wav"});
end
--to save the recording press 2
if (name == "to_save_recording") then
table.insert(actions, {app="streamFile",data="voicemail/vm-save_recording.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-press.wav"});
table.insert(actions, {app="streamFile",data="digits/2.wav"});
end
--to rerecord press 3
if (name == "to_rerecord") then
table.insert(actions, {app="streamFile",data="voicemail/vm-rerecord.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-press.wav"});
table.insert(actions, {app="streamFile",data="digits/3.wav"});
end
--You have zero new messages
if (name == "new_messages") then
table.insert(actions, {app="streamFile",data="phrase:voicemail_message_count:" .. param .. ":new"})
end
--You have zero saved messages
if (name == "saved_messages") then
table.insert(actions, {app="streamFile",data="phrase:voicemail_message_count:" .. param .. ":saved"})
end
--To listen to new messages press 1
if (name == "listen_to_new_messages") then
table.insert(actions, {app="streamFile",data="voicemail/vm-listen_new.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-press.wav"});
table.insert(actions, {app="streamFile",data="digits/1.wav"});
end
--To listen to saved messages press 2
if (name == "listen_to_saved_messages") then
table.insert(actions, {app="streamFile",data="voicemail/vm-listen_saved.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-press.wav"});
table.insert(actions, {app="streamFile",data="digits/2.wav"});
end
--For advanced options press 5
if (name == "advanced") then
table.insert(actions, {app="streamFile",data="voicemail/vm-advanced.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-press.wav"});
table.insert(actions, {app="streamFile",data="digits/5.wav"});
end
--Advanced Options Menu
--To record a greeting press 1
if (name == "to_record_greeting") then
table.insert(actions, {app="streamFile",data="voicemail/vm-to_record_greeting.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-press.wav"});
table.insert(actions, {app="streamFile",data="digits/1.wav"});
end
--Choose a greeting between 1 and 9
if (name == "choose_greeting_choose") then
table.insert(actions, {app="streamFile",data="voicemail/vm-choose_greeting_choose.wav"});
end
--Greeting invalid value
if (name == "choose_greeting_fail") then
table.insert(actions, {app="streamFile",data="voicemail/vm-choose_greeting_fail.wav"});
end
--Record your greeting at the tone press any key or stop talking to end the recording
if (name == "record_greeting") then
table.insert(actions, {app="streamFile",data="voicemail/vm-record_greeting.wav"});
table.insert(actions, {app="tone_stream",data="L=1;%(1000, 0, 640)"});
end
--To choose greeting press 2
if (name == "choose_greeting") then
table.insert(actions, {app="streamFile",data="voicemail/vm-choose_greeting.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-press.wav"});
table.insert(actions, {app="streamFile",data="digits/2.wav"});
end
--Greeting 1 selected
if (name == "greeting_selected") then
table.insert(actions, {app="streamFile",data="voicemail/vm-greeting.wav"});
table.insert(actions, {app="streamFile",data="digits/"..param..".wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-selected.wav"});
end
--To record your name 3
if (name == "to_record_name") then
table.insert(actions, {app="streamFile",data="voicemail/vm-record_name2.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-press.wav"});
table.insert(actions, {app="streamFile",data="digits/3.wav"});
end
--At the tone please record your name press any key or stop talking to end the recording
if (name == "record_name") then
table.insert(actions, {app="streamFile",data="voicemail/vm-record_name1.wav"});
table.insert(actions, {app="tone_stream",data="L=1;%(2000, 0, 640)"});
end
--To change your password press 6
if (name == "change_password") then
table.insert(actions, {app="streamFile",data="voicemail/vm-change_password.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-press.wav"});
table.insert(actions, {app="streamFile",data="digits/6.wav"});
end
--For the main menu press 0
if (name == "main_menu") then
table.insert(actions, {app="streamFile",data="voicemail/vm-main_menu.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-press.wav"});
table.insert(actions, {app="streamFile",data="digits/0.wav"});
end
--To exit press *
if (name == "to_exit_press") then
table.insert(actions, {app="streamFile",data="voicemail/vm-to_exit.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-press.wav"});
table.insert(actions, {app="streamFile",data="digits/star.wav"});
end
--Additional Macros
--Please enter your new password then press the # key #
if (name == "password_new") then
table.insert(actions, {app="streamFile",data="voicemail/vm-enter_new_pin.wav"});
end
--Has been changed to
if (name == "password_changed") then
table.insert(actions, {app="streamFile",data="voicemail/vm-has_been_changed_to.wav"});
table.insert(actions, {app="say.number.iterated",data=param});
end
--Login Incorrect
--if (name == "password_not_valid") then
-- table.insert(actions, {app="streamFile",data="voicemail/vm-password_not_valid.wav"});
--end
--Login Incorrect
if (name == "password_not_valid") then
table.insert(actions, {app="streamFile",data="voicemail/vm-fail_auth.wav"});
end
--Too many failed attempts
if (name == "too_many_failed_attempts") then
table.insert(actions, {app="streamFile",data="voicemail/vm-abort.wav"});
end
--Message number
if (name == "message_number") then
table.insert(actions, {app="streamFile",data="voicemail/vm-message_number.wav"});
end
--To listen to the recording press 1
if (name == "listen_to_recording") then
table.insert(actions, {app="streamFile",data="voicemail/vm-listen_to_recording.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-press.wav"});
table.insert(actions, {app="streamFile",data="digits/1.wav"});
end
--To save the recording press 2
if (name == "save_recording") then
table.insert(actions, {app="streamFile",data="voicemail/vm-save_recording.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-press.wav"});
table.insert(actions, {app="streamFile",data="digits/2.wav"});
end
--To delete the recording press 7
if (name == "delete_recording") then
table.insert(actions, {app="streamFile",data="voicemail/vm-delete_recording.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-press.wav"});
table.insert(actions, {app="streamFile",data="digits/7.wav"});
end
--Message deleted
if (name == "message_deleted") then
table.insert(actions, {app="streamFile",data="voicemail/vm-message.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-deleted.wav"});
end
--To return the call now press 5
if (name == "return_call") then
table.insert(actions, {app="streamFile",data="voicemail/vm-return_call.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-press.wav"});
table.insert(actions, {app="streamFile",data="digits/5.wav"});
end
--To add an introduction to this message press 1
if (name == "forward_add_intro") then
table.insert(actions, {app="streamFile",data="voicemail/vm-forward_add_intro.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-press.wav"});
table.insert(actions, {app="streamFile",data="digits/1.wav"});
end
--To forward this message press 8
if (name == "to_forward_message") then
table.insert(actions, {app="streamFile",data="voicemail/vm-to_forward.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-press.wav"});
table.insert(actions, {app="streamFile",data="digits/8.wav"});
end
--Please enter the extension to forward this message to followed by #
if (name == "forward_enter_extension") then
table.insert(actions, {app="streamFile",data="voicemail/vm-forward_enter_ext.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-followed_by.wav"});
table.insert(actions, {app="streamFile",data="ascii/35.wav"});
end
--To forward this recording to your email press 9
if (name == "forward_to_email") then
table.insert(actions, {app="streamFile",data="voicemail/vm-forward_to_email.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-press.wav"});
table.insert(actions, {app="streamFile",data="digits/9.wav"});
end
--Emailed
if (name == "emailed") then
table.insert(actions, {app="streamFile",data="voicemail/vm-emailed.wav"});
end
--Please enter the extension to send this message to followed by #
--if (name == "send_message_to_extension") then
-- table.insert(actions, {app="streamFile",data="voicemail/vm-zzz.wav"});
--end
--Message saved
if (name == "message_saved") then
table.insert(actions, {app="streamFile",data="voicemail/vm-message.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-saved.wav"});
end
--Your recording is below the minimal acceptable length, please try again.
if (name == "too_small") then
table.insert(actions, {app="streamFile",data="voicemail/vm-too-small.wav"});
end
--Goodbye
if (name == "goodbye") then
table.insert(actions, {app="streamFile",data="voicemail/vm-goodbye.wav"});
end
--Password is not secure
if (name == "password_not_secure") then
table.insert(actions, {app="streamFile",data="voicemail/vm-password_is_not_secure.wav"});
end
--Password is below minimum length
if (name == "password_below_minimum") then
table.insert(actions, {app="streamFile",data="voicemail/vm-pin_below_minimum_length.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-minimum_pin_length_is.wav"});
table.insert(actions, {app="streamFile",data="digits/"..param..".wav"});
end
--Tutorial
--Tutorial intro
if (name == "tutorial_intro") then
table.insert(actions, {app="streamFile",data="voicemail/vm-tutorial_yes_no.wav"});
end
--Tutorial to record your name 1
if (name == "tutorial_to_record_name") then
table.insert(actions, {app="streamFile",data="voicemail/vm-tutorial_record_name.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-record_name2.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-press.wav"});
table.insert(actions, {app="streamFile",data="digits/1.wav"});
end
--Tutorial to change your password press 1
if (name == "tutorial_change_password") then
table.insert(actions, {app="streamFile",data="voicemail/vm-tutorial_change_pin.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-change_password.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-press.wav"});
table.insert(actions, {app="streamFile",data="digits/1.wav"});
end
--Tutorial to record your greeting press 1
if (name == "tutorial_record_greeting") then
table.insert(actions, {app="streamFile",data="voicemail/vm-to_record_greeting.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-press.wav"});
table.insert(actions, {app="streamFile",data="digits/1.wav"});
end
--Tutorial To skip
if (name == "tutorial_skip") then
table.insert(actions, {app="streamFile",data="ivr/ivr-to_skip.wav"});
table.insert(actions, {app="streamFile",data="voicemail/vm-press.wav"});
table.insert(actions, {app="streamFile",data="digits/2.wav"});
end
--if actions table exists then process it
if (actions) then
--set default values
tries = 1;
timeout = 100;
--loop through the action and data
for key, row in pairs(actions) do
-- freeswitch.consoleLog("notice", "[voicemail] app: " .. row.app .. " data: " .. row.data .. "\n");
if (session:ready()) then
if (string.len(dtmf_digits) == 0) then
if (row.app == "streamFile") then
if string.find(row.data, ':', nil, true) then
session:streamFile(row.data);
else
session:streamFile(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/"..row.data);
end
elseif (row.app == "playback") then
session:streamFile(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/"..row.data);
elseif (row.app == "tone_stream") then
session:streamFile("tone_stream://"..row.data);
elseif (row.app == "silence_stream") then
session:streamFile("silence_stream://100"..row.data);
elseif (row.app == "playAndGetDigits") then
--playAndGetDigits <min> <max> <tries> <timeout> <terminators> <file> <invalid_file> <var_name> <regexp> <digit_timeout>
if (not file_exists(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/"..row.data)) then
dtmf_digits = session:playAndGetDigits(min_digits, max_digits, tries, timeout, "#", sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/"..row.data, "", "\\d+", max_timeout);
else
dtmf_digits = session:playAndGetDigits(min_digits, max_digits, tries, timeout, "#", row.data, "", "\\d+", max_timeout);
end
elseif (row.app == "say.number.pronounced") then
session:say(row.data, default_language, "number", "pronounced");
elseif (row.app == "say.number.iterated") then
session:say(row.data, default_language, "number", "iterated");
end
--session:streamFile("silence_stream://100");
end --if
end --session:ready
end --for
--get the remaining digits
if (session:ready()) then
if (string.len(dtmf_digits) < max_digits) then
dtmf_digits = dtmf_digits .. session:getDigits(max_digits, "#", max_timeout);
end
end
--return dtmf the digits
return dtmf_digits;
else
--no dtmf digits to return
return '';
end
end
end
@@ -0,0 +1,126 @@
-- Part of FusionPBX
-- Copyright (C) 2013 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--define function main menu
function main_menu ()
if (voicemail_uuid) then
--clear the value
dtmf_digits = '';
--flush dtmf digits from the input buffer
if (session ~= nil) then
session:flushDigits();
end
--answer the session
if (session ~= nil) then
session:answer();
session:execute("sleep", "1000");
end
--new voicemail count
if (session:ready()) then
local sql = [[SELECT count(*) as new_messages FROM v_voicemail_messages
WHERE domain_uuid = :domain_uuid
AND voicemail_uuid = :voicemail_uuid
AND (message_status is null or message_status = '') ]];
local params = {domain_uuid = domain_uuid, voicemail_uuid = voicemail_uuid};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
new_messages = row["new_messages"];
end);
dtmf_digits = session:playAndGetDigits(0, 1, 1, 300, "#", "phrase:voicemail_message_count:" .. new_messages .. ":new", "", "\\d+");
end
--saved voicemail count
if (session:ready()) then
if (string.len(dtmf_digits) == 0) then
sql = [[SELECT count(*) as saved_messages FROM v_voicemail_messages
WHERE domain_uuid = :domain_uuid
AND voicemail_uuid = :voicemail_uuid
AND message_status = 'saved' ]];
local params = {domain_uuid = domain_uuid, voicemail_uuid = voicemail_uuid};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
saved_messages = row["saved_messages"];
end);
dtmf_digits = session:playAndGetDigits(0, 1, 1, 300, "#", "phrase:voicemail_message_count:" .. saved_messages .. ":saved", "", "\\d+");
end
end
--to listen to new message
if (session:ready() and new_messages ~= '0') then
if (string.len(dtmf_digits) == 0) then
dtmf_digits = session:playAndGetDigits(0, 1, 1, 100, "#", "phrase:voicemail_main_menu:new:1", "", "\\d+");
end
end
--to listen to saved message
if (session:ready() and saved_messages ~= '0') then
if (string.len(dtmf_digits) == 0) then
dtmf_digits = session:playAndGetDigits(0, 1, 1, 100, "#", "phrase:voicemail_main_menu:saved:2", "", "\\d+");
end
end
--for advanced options
if (session:ready()) then
if (string.len(dtmf_digits) == 0) then
dtmf_digits = session:playAndGetDigits(0, 1, 1, 3000, "#", "phrase:voicemail_main_menu:advanced:5", "", "\\d+");
end
end
--to exit press #
--if (session:ready()) then
-- if (string.len(dtmf_digits) == 0) then
-- dtmf_digits = macro(session, "to_exit_press", 1, 3000, '');
-- end
--end
--process the dtmf
if (session:ready()) then
if (dtmf_digits == "1") then
menu_messages("new");
elseif (dtmf_digits == "2") then
menu_messages("saved");
elseif (dtmf_digits == "5") then
timeouts = 0;
advanced();
elseif (dtmf_digits == "0") then
main_menu();
elseif (dtmf_digits == "*") then
dtmf_digits = '';
session:execute("playback", "phrase:voicemail_goodbye");
session:hangup();
else
if (session:ready()) then
timeouts = timeouts + 1;
if (timeouts < max_timeouts) then
main_menu();
else
session:execute("playback", "phrase:voicemail_goodbye");
session:hangup();
end
end
end
end
end
end
@@ -0,0 +1,116 @@
-- Part of FusionPBX
-- Copyright (C) 2013-2017 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--define function for messages menu
function menu_messages (message_status)
--set default values
max_timeout = 2000;
min_digits = 1;
max_digits = 1;
tries = 1;
timeout = 2000;
--clear the dtmf
dtmf_digits = '';
--flush dtmf digits from the input buffer
--session:flushDigits();
--set the message number
message_number = 0;
--message_status new,saved
if (session:ready()) then
if (voicemail_id ~= nil) then
--get the voicemail_id
--fix for extensions that start with 0 (Ex: 0712)
sql = [[SELECT voicemail_id FROM v_voicemails WHERE voicemail_uuid = :voicemail_uuid]];
local params = {voicemail_uuid = voicemail_uuid};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(result)
voicemail_id_copy = result["voicemail_id"];
end);
local sql = [[SELECT voicemail_message_uuid, created_epoch, caller_id_name, caller_id_number
FROM v_voicemail_messages
WHERE domain_uuid = :domain_uuid
AND voicemail_uuid = :voicemail_uuid ]]
if (message_status == "new") then
sql = sql .. [[AND (message_status is null or message_status = '') ]];
elseif (message_status == "saved") then
sql = sql .. [[AND message_status = 'saved' ]];
end
sql = sql .. [[ORDER BY created_epoch ]]..message_order;
local params = {domain_uuid = domain_uuid, voicemail_uuid = voicemail_uuid};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
--get the values from the database
--row["voicemail_message_uuid"];
--row["created_epoch"];
--row["caller_id_name"];
--row["caller_id_number"];
--row["message_length"];
--row["message_status"];
--row["message_priority"];
--increment the message count
message_number = message_number + 1;
--listen to the message
if (session:ready()) then
if (debug["info"]) then
freeswitch.consoleLog("notice", message_number.." "..string.lower(row["voicemail_message_uuid"]).." "..row["created_epoch"]);
end
return listen_to_recording(message_number, string.lower(row["voicemail_message_uuid"]), row["created_epoch"], row["caller_id_name"], row["caller_id_number"], message_status);
end
end);
end
end
--voicemail count if zero new messages set the mwi to no
if session:ready() and voicemail_id and voicemail_uuid and #voicemail_uuid > 0 then
--get new and saved message counts
local new_messages, saved_messages = message_count_by_uuid(
voicemail_uuid, domain_uuid
)
--send the message waiting event
mwi_notify(voicemail_id.."@"..domain_name, new_messages, saved_messages)
blf_notify(tonumber(new_messages) > 0, "voicemail+" .. voicemail_id .. "@" .. domain_name)
--fix for extensions that start with 0 (Ex: 0712)
if (voicemail_id_copy ~= voicemail_id and voicemail_id_copy ~= nil) then
message_waiting(voicemail_id_copy, domain_uuid);
end
end
--set the display
if (session:ready()) then
reply = api:executeString("uuid_display "..session:get_uuid().." "..destination_number);
end
--send back to the main menu
if (session:ready()) then
timeouts = 0;
return main_menu();
end
end
@@ -0,0 +1,74 @@
local log = require "resources.functions.log"["voicemail-count"]
-- Tested SQL on SQLite 3, PgSQL 9.5, MySQL 5.5 and MariaDB 10
local message_count_by_uuid_sql = [[SELECT
( SELECT count(*)
FROM v_voicemail_messages
WHERE voicemail_uuid = :voicemail_uuid
AND (message_status is null or message_status = '')
) as new_messages,
( SELECT count(*)
FROM v_voicemail_messages
WHERE voicemail_uuid = :voicemail_uuid
AND message_status = 'saved'
) as saved_messages
]]
function message_count_by_uuid(voicemail_uuid)
local new_messages, saved_messages = "0", "0"
local params = {voicemail_uuid = voicemail_uuid};
if debug["sql"] then
log.noticef("SQL: %s; params: %s", message_count_by_uuid_sql, json.encode(params))
end
dbh:query(message_count_by_uuid_sql, params, function(row)
new_messages, saved_messages = row.new_messages, row.saved_messages
end)
if debug["info"] then
log.noticef("mailbox uuid: %s messages: %s/%s", voicemail_uuid, new_messages, saved_messages)
end
return new_messages, saved_messages
end
local message_count_by_id_sql = [[SELECT
( SELECT count(*)
FROM v_voicemail_messages as m inner join v_voicemails as v
on v.voicemail_uuid = m.voicemail_uuid
WHERE v.voicemail_id = :voicemail_id AND v.domain_uuid = :domain_uuid
AND (m.message_status is null or m.message_status = '')
) as new_messages,
( SELECT count(*)
FROM v_voicemail_messages as m inner join v_voicemails as v
on v.voicemail_uuid = m.voicemail_uuid
WHERE v.voicemail_id = :voicemail_id AND v.domain_uuid = :domain_uuid
AND m.message_status = 'saved'
) as saved_messages
]]
function message_count_by_id(voicemail_id, domain_uuid)
local new_messages, saved_messages = "0", "0"
local params = {voicemail_id = voicemail_id, domain_uuid = domain_uuid};
if debug["sql"] then
log.noticef("SQL: %s; params: %s", message_count_by_id_sql, json.encode(params))
end
dbh:query(message_count_by_id_sql, params, function(row)
new_messages, saved_messages = row.new_messages, row.saved_messages
end)
if debug["info"] then
log.noticef("mailbox: %s messages: %s/%s", voicemail_id, new_messages, saved_messages)
end
return new_messages, saved_messages
end
@@ -0,0 +1,58 @@
-- Part of FusionPBX
-- Copyright (C) 2013 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--save the message
function message_saved(voicemail_id, uuid)
--clear the dtmf
dtmf_digits = '';
--flush dtmf digits from the input buffer
session:flushDigits();
--get the voicemail_uuid
local sql = [[SELECT * FROM v_voicemails
WHERE domain_uuid = :domain_uuid
AND voicemail_id = :voicemail_id]];
local params = {domain_uuid = domain_uuid, voicemail_id = voicemail_id};
dbh:query(sql, params, function(row)
db_voicemail_uuid = row["voicemail_uuid"];
end);
--delete from the database
sql = [[UPDATE v_voicemail_messages SET message_status = 'saved'
WHERE domain_uuid = :domain_uuid
AND voicemail_uuid = :voicemail_uuid
AND voicemail_message_uuid = :uuid]];
params = {domain_uuid = domain_uuid, voicemail_uuid = db_voicemail_uuid, uuid = uuid};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params);
--log to console
if (debug["info"]) then
freeswitch.consoleLog("notice", "[voicemail][saved] id: " .. voicemail_id .. " message: "..uuid.."\n");
end
--check the message waiting status
message_waiting(voicemail_id, domain_uuid);
--clear the variable
db_voicemail_uuid = '';
end
@@ -0,0 +1,71 @@
-- Part of FusionPBX
-- Copyright (C) 2013-2016 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--voicemail count if zero new messages set the mwi to no
function message_waiting(voicemail_id, domain_uuid)
--initialize the array and add the voicemail_id
local accounts = {}
--add the current voicemail id to the accounts array
table.insert(accounts, voicemail_id);
--get the voicemail id and all related mwi accounts
local sql = [[SELECT extension, number_alias from v_extensions
WHERE domain_uuid = :domain_uuid
AND (
mwi_account = :voicemail_id
or mwi_account = :mwi_account
or number_alias = :voicemail_id
)]];
local params = {domain_uuid = domain_uuid, voicemail_id = voicemail_id,
mwi_account = voicemail_id .. "@" .. domain_name};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
table.insert(accounts, row["extension"]);
end);
--get new and saved message counts
local new_messages, saved_messages = message_count_by_id(voicemail_id, domain_uuid);
--send the message waiting event
for _,value in ipairs(accounts) do
--add the domain to voicemail id
local account = value.."@"..domain_name;
--send the message waiting notifications
mwi_notify(account, new_messages, saved_messages);
blf_notify(tonumber(new_messages) > 0, "voicemail+" .. voicemail_id .. "@" .. domain_name)
--send information to the console
if (debug["info"]) then
if new_messages == "0" then
freeswitch.consoleLog("notice", "[voicemail] mailbox: "..account.." messages: no new messages\n");
else
freeswitch.consoleLog("notice", "[voicemail] mailbox: "..account.." messages: " .. new_messages .. " new message(s)\n");
end
end
end
end
@@ -0,0 +1,56 @@
--define a function to send mwi notify
function mwi_notify(account, new_messages, saved_messages)
--includes
require "resources.functions.explode"
require "resources.functions.trim"
--create the api object
api = freeswitch.API();
local sofia_contacts = trim(api:executeString("sofia_contact */"..account));
local sofia_contact_table = explode(",", sofia_contacts);
local sip_profile_table = {};
for key,value in pairs(sofia_contact_table) do
f = explode("/", value);
sip_profile = f[2];
--check to see if a notify has already been sent to this profile
new = "true";
for profile_index, profile_table_value in pairs(sip_profile_table) do
if profile_table_value == sip_profile then
new = "false";
end
end
if new == "true" then
--debug info
--freeswitch.consoleLog("NOTICE", "sofia_contact */"..account.."\n");
--freeswitch.consoleLog("NOTICE", "sip_profile="..sip_profile.."\n");
--freeswitch.consoleLog("NOTICE", "sofia_contacts="..sofia_contacts.."\n");
--set the variables
new_messages = tonumber(new_messages) or 0
saved_messages = tonumber(saved_messages) or 0
--set the event and send it
local event = freeswitch.Event("message_waiting")
event:addHeader("MWI-Messages-Waiting", (new_messages == 0) and "no" or "yes")
event:addHeader("MWI-Message-Account", "sip:" .. account)
event:addHeader("MWI-Voice-Message", string.format("%d/%d (0/0)", new_messages, saved_messages))
event:addHeader("sofia-profile", sip_profile)
event:fire()
table.insert(sip_profile_table,sip_profile);
end
end
end
--return module value
return mwi_notify
@@ -0,0 +1,60 @@
-- Part of FusionPBX
-- Copyright (C) 2013 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--define on_dtmf call back function
function on_dtmf(s, type, obj, arg)
if (type == "dtmf") then
freeswitch.console_log("info", "[voicemail] dtmf digit: " .. obj['digit'] .. ", duration: " .. obj['duration'] .. "\n");
if (obj['digit'] == "#") then
return 0;
else
dtmf_digits = dtmf_digits .. obj['digit'];
if (debug["info"]) then
freeswitch.console_log("info", "[voicemail] dtmf digits: " .. dtmf_digits .. ", length: ".. string.len(dtmf_digits) .." max_digits: " .. max_digits .. "\n");
end
if (stream_seek == true) then
if (dtmf_digits == "4") then
dtmf_digits = "";
return("seek:-5000");
end
if (dtmf_digits == "5") then
dtmf_digits = "";
return("pause");
end
if (dtmf_digits == "6") then
dtmf_digits = "";
return("seek:+5000");
end
end
if (string.len(dtmf_digits) >= max_digits) then
if (debug["info"]) then
freeswitch.console_log("info", "[voicemail] max_digits reached\n");
end
return 0;
end
end
end
return 0;
end
@@ -0,0 +1,126 @@
-- Part of FusionPBX
-- Copyright (C) 2013 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
local Database = require"resources.functions.database"
--play the greeting
function play_greeting()
timeout = 100;
tries = 1;
max_timeout = 200;
--voicemail prompt
if (skip_greeting == "true") then
--skip the greeting
else
if (session:ready()) then
--set the greeting based on the voicemail_greeting_number variable
if (voicemail_greeting_number ~= nil) then
if (string.len(voicemail_greeting_number) > 0) then
greeting_id = voicemail_greeting_number;
end
end
--play the greeting
dtmf_digits = '';
if (string.len(greeting_id) > 0 and greeting_id ~= "default") then
--greeting set to none, return without a greeting
if (greeting_id == "0") then
return true;
end
--sleep
session:execute("playback","silence_stream://200");
--get the greeting from the database
if (storage_type == "base64") then
local dbh = Database.new('system', 'base64/read')
local sql = [[SELECT * FROM v_voicemail_greetings
WHERE domain_uuid = :domain_uuid
AND voicemail_id = :voicemail_id
AND greeting_id = :greeting_id ]];
local params = {domain_uuid = domain_uuid, voicemail_id = voicemail_id,
greeting_id = greeting_id};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
local saved
dbh:query(sql, params, function(row)
--set the voicemail message path
mkdir(voicemail_dir.."/"..voicemail_id);
greeting_location = voicemail_dir.."/"..voicemail_id.."/greeting_"..greeting_id..".wav"; --vm_message_ext;
--if not found, save greeting to local file system
--saved = file_exists(greeting_location)
--if not saved then
if (string.len(row["greeting_base64"]) > 32) then
--include the file io
local file = require "resources.functions.file"
--write decoded string to file
saved = file.write_base64(greeting_location, row["greeting_base64"]);
end
--end
end);
dbh:release();
if saved then
--play the greeting
dtmf_digits = session:playAndGetDigits(0, max_digits, tries, timeout, "#", voicemail_dir.."/"..voicemail_id.."/greeting_"..greeting_id..".wav", "", ".*", max_timeout);
--session:execute("playback",voicemail_dir.."/"..voicemail_id.."/greeting_"..greeting_id..".wav");
--delete the greeting (retain local for better responsiveness)
--os.remove(voicemail_dir.."/"..voicemail_id.."/greeting_"..greeting_id..".wav");
end
elseif (storage_type == "http_cache") then
dtmf_digits = session:playAndGetDigits(0, max_digits, tries, timeout, "#", voicemail_dir.."/"..voicemail_id.."/greeting_"..greeting_id..".wav", "", ".*", max_timeout);
--session:execute("playback",storage_path.."/"..voicemail_id.."/greeting_"..greeting_id..".wav");
else
greeting_ext = 'wav';
if (file_exists(voicemail_dir.."/"..voicemail_id.."/greeting_"..greeting_id..".mp3")) then
greeting_ext = "mp3";
end
dtmf_digits = session:playAndGetDigits(0, max_digits, tries, timeout, "#", voicemail_dir.."/"..voicemail_id.."/greeting_"..greeting_id.."."..greeting_ext, "",".*", max_timeout);
--session:execute("playback",voicemail_dir.."/"..voicemail_id.."/greeting_"..greeting_id..".wav");
end
else
--default greeting
session:execute("playback","silence_stream://200");
--determine the voicemail_id to say
if (voicemail_alternate_greet_id and string.len(voicemail_alternate_greet_id) > 0) then
voicemail_id_say = voicemail_alternate_greet_id;
elseif (voicemail_greet_id and string.len(voicemail_greet_id) > 0) then
voicemail_id_say = voicemail_greet_id;
else
voicemail_id_say = voicemail_id;
end
dtmf_digits = session:playAndGetDigits(0, 1, 1, 200, "#", "phrase:voicemail_play_greeting:" .. voicemail_id_say, "", ".*");
end
end
end
end
@@ -0,0 +1,139 @@
-- Part of FusionPBX
-- Copyright (C) 2013 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--load libraries
local Database = require "resources.functions.database"
local Settings = require "resources.functions.lazy_settings"
--define a function to record the greeting
function record_greeting(greeting_id, menu)
--setup the database connection
local db = dbh or Database.new('system')
--get the voicemail settings
local settings = Settings.new(db, domain_name, domain_uuid)
--set the maximum greeting length
local greeting_max_length = settings:get('voicemail', 'greeting_max_length', 'numeric') or 90;
local greeting_silence_threshold = settings:get('voicemail', 'greeting_silence_threshold', 'numeric') or 200;
local greeting_silence_seconds = settings:get('voicemail', 'greeting_silence_seconds', 'numeric') or 3;
--flush dtmf digits from the input buffer
session:flushDigits();
--disable appending to the recording
session:setVariable("record_append", "false");
--choose a greeting between 1 and 9
if (greeting_id == nil) then
if (session:ready()) then
dtmf_digits = '';
greeting_id = session:playAndGetDigits(1, 1, max_tries, 5000, "#", "phrase:voicemail_choose_greeting", "", "\\d+");
freeswitch.consoleLog("notice", "[voicemail] greeting_id: " .. greeting_id .. "\n");
end
end
--validate the greeting_id
if (greeting_id == "1"
or greeting_id == "2"
or greeting_id == "3"
or greeting_id == "4"
or greeting_id == "5"
or greeting_id == "6"
or greeting_id == "7"
or greeting_id == "8"
or greeting_id == "9") then
--record your greeting at the tone press any key or stop talking to end the recording
if (session:ready()) then
dtmf_digits = '';
session:execute("playback", "phrase:voicemail_record_greeting");
session:execute("sleep", "1000");
session:streamFile("tone_stream://L=1;%(1000, 0, 640)");
end
--store the voicemail greeting
if (storage_type == "http_cache") then
freeswitch.consoleLog("notice", "[voicemail] ".. storage_type .. " ".. storage_path .."\n");
storage_path = storage_path:gsub("${domain_name}", domain_name);
session:execute("record", storage_path .."/"..recording_name);
else
--prepare to record the greeting
if (session:ready()) then
silence_seconds = 5;
mkdir(voicemail_dir.."/"..voicemail_id);
-- syntax is session:recordFile(file_name, max_len_secs, silence_threshold, silence_seconds)
result = session:recordFile(voicemail_dir.."/"..voicemail_id.."/greeting_"..greeting_id..".tmp.wav", greeting_max_length, greeting_silence_threshold, greeting_silence_seconds);
--session:execute("record", voicemail_dir.."/"..uuid.." 180 200");
end
end
--play the greeting
--if (session:ready()) then
-- if (file_exists(voicemail_dir.."/"..voicemail_id.."/greeting_"..greeting_id..".wav")) then
-- session:streamFile(voicemail_dir.."/"..voicemail_id.."/greeting_"..greeting_id..".wav");
-- end
--end
--option to play, save, and re-record the greeting
if (session:ready()) then
timeouts = 0;
record_menu("greeting", voicemail_dir.."/"..voicemail_id.."/greeting_"..greeting_id..".tmp.wav", greeting_id, menu);
end
else
--invalid greeting_id
if (session:ready()) then
dtmf_digits = '';
session:execute("playback", "phrase:voicemail_choose_greeting_fail");
session:execute("sleep", "500");
end
--send back to choose the greeting
if (session:ready()) then
timeouts = timeouts + 1;
if (timeouts < max_timeouts) then
record_greeting(nil, menu);
else
timeouts = 0;
if (menu == "tutorial") then
tutorial("finish")
end
if (menu == "advanced") then
advanced();
else
advanced();
end
end
end
end
--clean up any tmp greeting files
for gid = 1, 9, 1 do
if (file_exists(voicemail_dir.."/"..voicemail_id.."/greeting_"..gid..".tmp.wav")) then
os.remove(voicemail_dir.."/"..voicemail_id.."/greeting_"..gid..".tmp.wav");
end
end
end
@@ -0,0 +1,239 @@
-- Part of FusionPBX
-- Copyright (C) 2013-2015 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--record message menu
function record_menu(type, tmp_file, greeting_id, menu)
if (session:ready()) then
--clear the dtmf digits variable
dtmf_digits = '';
--flush dtmf digits from the input buffer
session:flushDigits();
--to listen to the recording press 1, to save the recording press 2, to re-record press 3
if (session:ready()) then
if (string.len(dtmf_digits) == 0) then
dtmf_digits = session:playAndGetDigits(0, 1, 1, 3000, "#", "phrase:voicemail_record_file_options:1:2:3", "", "\\d+");
end
end
--process the dtmf
if (session:ready()) then
if (dtmf_digits == "1") then
--use sleep for a small pause
session:sleep('1000');
--listen to the recording
session:streamFile(tmp_file);
--session:streamFile(voicemail_dir.."/"..voicemail_id.."/msg_"..uuid.."."..vm_message_ext);
--record menu (1=listen, 2=save, 3=re-record)
record_menu(type, tmp_file, greeting_id, menu);
elseif (dtmf_digits == "2") then
--save the message
dtmf_digits = '';
session:execute("playback", "phrase:voicemail_ack:saved");
session:execute("sleep", "500");
if (type == "message") then
--goodbye
session:execute("playback", "phrase:voicemail_goodbye");
--hangup the call
session:hangup();
end
if (type == "greeting") then
--remove old greeting file, and rename tmp file
local real_file = string.gsub(tmp_file, ".tmp", "");
if (file_exists(real_file)) then
os.remove(real_file);
end
if (file_exists(tmp_file)) then
os.rename(tmp_file, real_file);
end
if (storage_type == "base64") then
--delete the greeting (retain local for better responsiveness)
--os.remove(real_file);
end
--if base64, encode file
if (storage_type == "base64") then
--include the file io
local file = require "resources.functions.file"
--read file content as base64 string
greeting_base64 = assert(file.read_base64(real_file));
end
--delete the previous recording
local sql = "delete from v_voicemail_greetings ";
sql = sql .. "where domain_uuid = :domain_uuid ";
sql = sql .. "and voicemail_id = :voicemail_id ";
sql = sql .. "and greeting_id = :greeting_id ";
local params = {domain_uuid = domain_uuid,
voicemail_id = voicemail_id, greeting_id = greeting_id};
--freeswitch.consoleLog("notice", "[SQL] DELETING: " .. greeting_id .. "\n");
dbh:query(sql, params);
--get a new uuid
voicemail_greeting_uuid = api:execute("create_uuid");
--save the message to the voicemail messages
local array = {}
table.insert(array, "INSERT INTO v_voicemail_greetings ");
table.insert(array, "(");
table.insert(array, "voicemail_greeting_uuid, ");
table.insert(array, "domain_uuid, ");
table.insert(array, "voicemail_id, ");
table.insert(array, "greeting_id, ");
if (storage_type == "base64") then
table.insert(array, "greeting_base64, ");
end
table.insert(array, "greeting_name, ");
table.insert(array, "greeting_filename ");
table.insert(array, ") ");
table.insert(array, "VALUES ");
table.insert(array, "( ");
table.insert(array, ":greeting_uuid, ");
table.insert(array, ":domain_uuid, ");
table.insert(array, ":voicemail_id, ");
table.insert(array, ":greeting_id, ");
if (storage_type == "base64") then
table.insert(array, ":greeting_base64, ");
end
table.insert(array, ":greeting_name, ");
table.insert(array, ":greeting_filename ");
table.insert(array, ") ");
sql = table.concat(array, "\n");
params = {
greeting_uuid = voicemail_greeting_uuid;
domain_uuid = domain_uuid;
voicemail_id = voicemail_id;
greeting_id = greeting_id;
greeting_base64 = greeting_base64;
greeting_name = "Greeting "..greeting_id;
greeting_filename = "greeting_"..greeting_id..".wav"
};
--freeswitch.consoleLog("notice", "[SQL] INSERTING: " .. greeting_id .. "\n");
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
if (storage_type == "base64") then
local dbh = Database.new('system', 'base64');
dbh:query(sql, params);
dbh:release();
else
dbh:query(sql, params);
end
--use the new greeting
sql = {}
table.insert(sql, "update v_voicemails ");
table.insert(sql, "set greeting_id = :greeting_id ");
table.insert(sql, "where domain_uuid = :domain_uuid ");
table.insert(sql, "and voicemail_id = :voicemail_id ");
sql = table.concat(sql, "\n");
params = {domain_uuid = domain_uuid, greeting_id = greeting_id,
voicemail_id = voicemail_id};
dbh:query(sql, params);
if (menu == "advanced") then
advanced();
end
if (menu == "tutorial") then
tutorial("finish")
end
end
if (type == "name") then
if (menu == "advanced") then
advanced();
end
if (menu == "tutorial") then
tutorial("change_password")
end
end
elseif (dtmf_digits == "3") then
--re-record the message
timeouts = 0;
dtmf_digits = '';
if (type == "message") then
record_message();
end
if (type == "greeting") then
--remove temporary greeting file, if any
if (file_exists(tmp_file)) then
os.remove(tmp_file);
end
record_greeting(greeting_id, menu);
end
if (type == "name") then
record_name(menu);
end
elseif (dtmf_digits == "*") then
if (type == "greeting") then
--remove temporary greeting file, if any
if (file_exists(tmp_file)) then
os.remove(tmp_file);
end
end
--hangup
if (session:ready()) then
dtmf_digits = '';
session:execute("playback", "phrase:voicemail_goodbye");
session:hangup();
end
else
if (session:ready()) then
timeouts = timeouts + 1;
if (timeouts < max_timeouts) then
record_menu(type, tmp_file, greeting_id, menu);
else
if (type == "message") then
dtmf_digits = '';
session:execute("playback", "phrase:voicemail_ack:saved");
session:execute("sleep", "300");
session:execute("playback", "phrase:voicemail_goodbye");
session:hangup();
end
if (type == "greeting") then
--remove temporary greeting file, if any
if (file_exists(tmp_file)) then
os.remove(tmp_file);
end
if (menu == "advanced") then
advanced();
end
if (menu == "tutorial") then
tutorial("finish")
end
end
if (type == "name") then
if (menu == "advanced") then
advanced();
end
if (menu == "tutorial") then
tutorial("change_password");
end
end
end
end
end
end
end
end
@@ -0,0 +1,276 @@
-- Part of FusionPBX
-- Copyright (C) 2013-2022 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--load libraries
local Database = require "resources.functions.database"
local Settings = require "resources.functions.lazy_settings"
local JSON = require "resources.functions.lunajson"
--define uuid function
local random = math.random;
local function gen_uuid()
local template ='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
return string.gsub(template, '[xy]', function (c)
local v = (c == 'x') and random(0, 0xf) or random(8, 0xb);
return string.format('%x', v);
end)
end
--define escape function (prevents lua injection attacks)
local function esc(x)
return (x:gsub('%%', '%%%%')
:gsub('^%^', '%%^')
:gsub('%$$', '%%$')
:gsub('%(', '%%(')
:gsub('%)', '%%)')
:gsub('%.', '%%.')
:gsub('%[', '%%[')
:gsub('%]', '%%]')
:gsub('%*', '%%*')
:gsub('%+', '%%+')
:gsub('%-', '%%-')
:gsub('%?', '%%?'))
end
--save the recording
function record_message()
--set the variables
local db = dbh or Database.new('system')
local settings = Settings.new(db, domain_name, domain_uuid)
local message_max_length = settings:get('voicemail', 'message_max_length', 'numeric') or 300;
local message_silence_threshold = settings:get('voicemail', 'message_silence_threshold', 'numeric') or 200;
local message_silence_seconds = settings:get('voicemail', 'message_silence_seconds', 'numeric') or 3;
local email_queue_enabled = settings:get('email_queue', 'enabled', 'boolean') or "false";
--record your message at the tone press any key or stop talking to end the recording
if (skip_instructions == "true") then
--skip the instructions
else
if (dtmf_digits and string.len(dtmf_digits) == 0) then
dtmf_digits = session:playAndGetDigits(0, 1, 1, 500, "#", "phrase:voicemail_record_message", "", "\\d+");
end
end
--voicemail ivr options
if (session:ready()) then
if (dtmf_digits == nil) then
dtmf_digits = session:getDigits(max_digits, "#", 1000);
else
dtmf_digits = dtmf_digits .. session:getDigits(max_digits, "#", 1000);
end
end
if (dtmf_digits) then
if (string.len(dtmf_digits) > 0) then
if (session:ready()) then
if (direct_dial["enabled"] == "true") then
if (string.len(dtmf_digits) < max_digits) then
dtmf_digits = dtmf_digits .. session:getDigits(direct_dial["max_digits"], "#", 3000);
end
end
end
if (session:ready()) then
freeswitch.consoleLog("notice", "[voicemail] dtmf_digits: " .. string.sub(dtmf_digits, 0, 1) .. "\n");
if (dtmf_digits == "*") then
if (remote_access == "true") then
--check the voicemail password
check_password(voicemail_id, password_tries);
--send to the main menu
timeouts = 0;
main_menu();
else
--remote access is false
freeswitch.consoleLog("notice", "[voicemail] remote access is disabled.\n");
session:hangup();
end
elseif (string.sub(dtmf_digits, 0, 1) == "*") then
--do not allow dialing numbers prefixed with *
session:hangup();
else
--get the voicemail options
local sql = [[SELECT * FROM v_voicemail_options WHERE voicemail_uuid = :voicemail_uuid ORDER BY voicemail_option_order asc ]];
local params = {voicemail_uuid = voicemail_uuid};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
count = 0;
dbh:query(sql, params, function(row)
--check for matching options
if (tonumber(row.voicemail_option_digits) ~= nil) then
row.voicemail_option_digits = "^"..row.voicemail_option_digits.."$";
end
if (api:execute("regex", "m:~"..dtmf_digits.."~"..row.voicemail_option_digits) == "true") then
if (row.voicemail_option_action == "menu-exec-app") then
--get the action and data
pos = string.find(row.voicemail_option_param, " ", 0, true);
action = string.sub( row.voicemail_option_param, 0, pos-1);
data = string.sub( row.voicemail_option_param, pos+1);
--check if the option uses a regex
regex = string.find(row.voicemail_option_digits, "(", 0, true);
if (regex) then
--get the regex result
result = trim(api:execute("regex", "m:~"..digits.."~"..row.voicemail_option_digits.."~$1"));
if (debug["regex"]) then
freeswitch.consoleLog("notice", "[voicemail] regex m:~"..digits.."~"..row.voicemail_option_digits.."~$1\n");
freeswitch.consoleLog("notice", "[voicemail] result: "..result.."\n");
end
--replace the $1 and the domain name
data = data:gsub("$1", result);
data = data:gsub("${domain_name}", domain_name);
end --if regex
end --if menu-exex-app
end --if regex match
--execute
if (action) then
if (string.len(action) > 0) then
--send to the log
if (debug["action"]) then
freeswitch.consoleLog("notice", "[voicemail] action: " .. action .. " data: ".. data .. "\n");
end
--run the action
session:execute(action, data);
end
end
--clear the variables
action = "";
data = "";
--inrement the option count
count = count + 1;
end); --end results
--direct dial
if (session:ready()) then
if (direct_dial["enabled"] == "true" and count == 0) then
if (string.len(dtmf_digits) < max_digits) then
dtmf_digits = dtmf_digits .. session:getDigits(direct_dial["max_digits"], "#", 5000);
session:transfer(dtmf_digits.." XML "..context);
end
end
end
end
end
end
end
--play the beep
dtmf_digits = '';
session:execute("playback","silence_stream://200");
session:streamFile("tone_stream://L=1;%(1000, 0, 640)");
--make sure the voicemail directory exists
mkdir(voicemail_dir.."/"..voicemail_id);
--start epoch
start_epoch = os.time();
--save the recording
-- syntax is session:recordFile(file_name, max_len_secs, silence_threshold, silence_secs)
if (storage_path == "http_cache") then
result = session:recordFile(storage_path.."/"..voicemail_id.."/msg_"..uuid.."."..vm_message_ext, message_max_length, message_silence_threshold, message_silence_seconds);
else
if (vm_message_ext == "mp3") then
shout_exists = trim(api:execute("module_exists", "mod_shout"));
if (shout_exists == "true") then
--send information to the console
freeswitch.consoleLog("notice", "using mod_shout for mp3 encoding\n");
--record in mp3 directly
result = session:recordFile(voicemail_dir.."/"..voicemail_id.."/msg_"..uuid..".mp3", message_max_length, message_silence_threshold, message_silence_seconds);
else
--create initial wav recording
result = session:recordFile(voicemail_dir.."/"..voicemail_id.."/msg_"..uuid..".wav", message_max_length, message_silence_threshold, message_silence_seconds);
--use lame to encode, if available
if (file_exists("/usr/bin/lame")) then
freeswitch.consoleLog("notice", "using lame for mp3 encoding\n");
--convert the wav to an mp3 (lame required)
resample = "/usr/bin/lame -b 32 --resample 8 -m s "..voicemail_dir.."/"..voicemail_id.."/msg_"..uuid..".wav "..voicemail_dir.."/"..voicemail_id.."/msg_"..uuid..".mp3";
session:execute("system", resample);
--delete the wav file, if mp3 exists
if (file_exists(voicemail_dir.."/"..voicemail_id.."/msg_"..uuid..".mp3")) then
os.remove(voicemail_dir.."/"..voicemail_id.."/msg_"..uuid..".wav");
else
vm_message_ext = "wav";
end
else
--send information to the console
freeswitch.consoleLog("notice", "neither mod_shout or lame found, defaulting to wav\n");
--use wav format
vm_message_ext = "wav";
end
end
else
result = session:recordFile(voicemail_dir.."/"..voicemail_id.."/msg_"..uuid.."."..vm_message_ext, message_max_length, message_silence_threshold, message_silence_seconds);
end
end
--stop epoch
stop_epoch = os.time();
--calculate the message length
message_length = stop_epoch - start_epoch;
message_length_formatted = format_seconds(message_length);
--if the recording is below the minimal length then re-record the message
if (message_length > 2) then
session:setVariable("voicemail_message_seconds", message_length);
else
if (session:ready()) then
--your recording is below the minimal acceptable length, please try again
dtmf_digits = '';
session:execute("playback", "phrase:voicemail_ack:too-small");
session:execute("sleep", "500");
--record your message at the tone
timeouts = timeouts + 1;
if (timeouts < max_timeouts) then
record_message();
else
timeouts = 0;
record_menu("message", voicemail_dir.."/"..voicemail_id.."/msg_"..uuid.."."..vm_message_ext);
end
end
end
--instructions press 1 to listen to the recording, press 2 to save the recording, press 3 to re-record
if (session:ready()) then
if (skip_instructions == "true") then
--save the message
dtmf_digits = '';
session:execute("playback", "phrase:voicemail_ack:saved");
session:execute("sleep", "300");
session:execute("playback", "phrase:voicemail_goodbye");
--hangup the call
session:hangup();
else
timeouts = 0;
record_menu("message", voicemail_dir.."/"..voicemail_id.."/msg_"..uuid.."."..vm_message_ext);
end
end
end
@@ -0,0 +1,106 @@
-- Part of FusionPBX
-- Copyright (C) 2013 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--define a function to record the name
function record_name(menu)
if (session:ready()) then
--flush dtmf digits from the input buffer
session:flushDigits();
--play the name record
dtmf_digits = '';
session:execute("playback", "phrase:voicemail_record_name");
session:execute("sleep", "1000");
session:streamFile("tone_stream://L=1;%(1000, 0, 640)");
--prepate to record
-- syntax is session:recordFile(file_name, max_len_secs, silence_threshold, silence_secs)
max_len_seconds = 30;
silence_threshold = 30;
silence_seconds = 5;
--make sure the voicemail directory exists
mkdir(voicemail_dir.."/"..voicemail_id);
--record and save the file
if (storage_type == "base64") then
--set the location
voicemail_name_location = voicemail_dir.."/"..voicemail_id.."/recorded_name.wav";
--record the file to the file system
-- syntax is session:recordFile(file_name, max_len_secs, silence_threshold, silence_secs);
result = session:recordFile(voicemail_name_location, max_len_seconds, silence_threshold, silence_seconds);
--session:execute("record", voicemail_dir.."/"..uuid.." 180 200");
--show the storage type
freeswitch.consoleLog("notice", "[recordings] ".. storage_type .. "\n");
--base64 encode the file
--include the file io
local file = require "resources.functions.file"
--read file content as base64 string
voicemail_name_base64 = assert(file.read_base64(voicemail_name_location));
--update the voicemail name
local sql = "UPDATE v_voicemails ";
sql = sql .. "set voicemail_name_base64 = :voicemail_name_base64 ";
sql = sql .. "where domain_uuid = :domain_uuid ";
sql = sql .. "and voicemail_id = :voicemail_id";
local params = {voicemail_name_base64 = voicemail_name_base64,
domain_uuid = domain_uuid, voicemail_id = voicemail_id};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[recording] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
if (storage_type == "base64") then
local dbh = Database.new('system', 'base64');
dbh:query(sql, params);
dbh:release();
else
dbh:query(sql, params);
end
elseif (storage_type == "http_cache") then
freeswitch.consoleLog("notice", "[voicemail] ".. storage_type .. " ".. storage_path .."\n");
session:execute("record", storage_path .."/"..recording_name);
else
-- syntax is session:recordFile(file_name, max_len_secs, silence_threshold, silence_secs);
result = session:recordFile(voicemail_dir.."/"..voicemail_id.."/recorded_name.wav", max_len_seconds, silence_threshold, silence_seconds);
end
--play the name
--session:streamFile(voicemail_dir.."/"..voicemail_id.."/recorded_name.wav");
--option to play, save, and re-record the name
if (session:ready()) then
timeouts = 0;
record_menu("name", voicemail_dir.."/"..voicemail_id.."/recorded_name.wav",nil, menu);
if (storage_type == "base64") then
--delete the greeting
os.remove(voicemail_dir.."/"..voicemail_id.."/recorded_name.wav");
end
end
end
end
@@ -0,0 +1,36 @@
-- Part of FusionPBX
-- Copyright (C) 2013 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--define a function to return the call
function return_call(destination)
if (session:ready()) then
--clear the dtmf
dtmf_digits = '';
--flush dtmf digits from the input buffer
session:flushDigits();
--transfer the call
session:transfer(destination, "XML", context);
end
end
@@ -0,0 +1,305 @@
-- Part of FusionPBX
-- Copyright (C) 2013 - 2020 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--load libraries
local send_mail = require 'resources.functions.send_mail'
local Database = require "resources.functions.database"
local Settings = require "resources.functions.lazy_settings"
--define a function to send email
function send_email(id, uuid)
local db = dbh or Database.new('system')
local settings = Settings.new(db, domain_name, domain_uuid)
local transcribe_enabled = settings:get('voicemail', 'transcribe_enabled', 'boolean');
local http_protocol = settings:get('domain', 'http_protocol', 'text') or "https";
local email_queue_enabled = "true";
--get voicemail message details
local sql = [[SELECT * FROM v_voicemails
WHERE domain_uuid = :domain_uuid
AND voicemail_id = :voicemail_id]]
local params = {domain_uuid = domain_uuid, voicemail_id = id};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
db_voicemail_uuid = string.lower(row["voicemail_uuid"]);
--voicemail_password = row["voicemail_password"];
--greeting_id = row["greeting_id"];
voicemail_mail_to = row["voicemail_mail_to"];
voicemail_transcription_enabled = row["voicemail_transcription_enabled"];
voicemail_file = row["voicemail_file"];
voicemail_local_after_email = row["voicemail_local_after_email"];
voicemail_description = row["voicemail_description"];
end);
--set default values
if (voicemail_local_after_email == nil) then
voicemail_local_after_email = "true";
end
if (voicemail_file == nil) then
voicemail_file = "listen";
end
--require the email address to send the email
if (string.len(voicemail_mail_to) > 2) then
--include languages file
local Text = require "resources.functions.text"
local text = Text.new("app.voicemail.app_languages")
local dbh = dbh
--user setting time zone, if set
local sql = [[
select
us.user_setting_value as time_zone
from
v_user_settings as us,
v_extension_users as eu,
v_extensions as e,
v_voicemails as v
where
v.voicemail_id = :voicemail_id and
v.domain_uuid = :domain_uuid and
v.voicemail_id = e.extension and
e.domain_uuid = :domain_uuid and
e.extension_uuid = eu.extension_uuid and
eu.domain_uuid = :domain_uuid and
eu.user_uuid = us.user_uuid and
us.domain_uuid = :domain_uuid and
us.user_setting_category = 'domain' and
us.user_setting_subcategory = 'time_zone' and
us.user_setting_name = 'name' and
us.user_setting_enabled = 'true'
order by
eu.insert_date asc
limit 1
]]
local params = {domain_uuid = domain_uuid, voicemail_id = id};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
time_zone = row["time_zone"];
end);
--default/domain setting time zone
if (time_zone == nil or time_zone == '') then
time_zone = settings:get('domain', 'time_zone', 'name');
end
--default time zone
if (time_zone == nil or time_zone == '') then
time_zone = 'UTC';
end
--connect using other backend if needed
if storage_type == "base64" then
dbh = Database.new('system', 'base64/read')
end
--get voicemail message details
local sql = [[SELECT to_char(timezone(:time_zone, to_timestamp(created_epoch)), 'Day DD Mon YYYY HH:MI:SS PM') as message_date, *
FROM v_voicemail_messages
WHERE domain_uuid = :domain_uuid
AND voicemail_message_uuid = :uuid]]
local params = {domain_uuid = domain_uuid, uuid = uuid, time_zone = time_zone};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
--get the values from the database
--uuid = row["voicemail_message_uuid"];
created_epoch = row["created_epoch"];
caller_id_name = row["caller_id_name"];
caller_id_number = row["caller_id_number"];
message_date = row["message_date"];
message_length = row["message_length"];
--message_status = row["message_status"];
--message_priority = row["message_priority"];
--get the recordings from the database
if (storage_type == "base64") then
--set the voicemail message path
message_location = voicemail_dir.."/"..id.."/msg_"..uuid.."."..vm_message_ext;
--save the recording to the file system
if (string.len(row["message_base64"]) > 32) then
--include the file io
local file = require "resources.functions.file"
--write decoded string to file
file.write_base64(message_location, row["message_base64"]);
end
end
end);
--close temporary connection
if storage_type == "base64" then
dbh:release()
end
--format the message length and date
message_length_formatted = format_seconds(message_length);
if (debug["info"]) then
freeswitch.consoleLog("notice", "[voicemail] message date: " .. message_date .. "\n");
freeswitch.consoleLog("notice", "[voicemail] message length: " .. message_length .. "\n");
end
--local message_date = os.date("%A, %d %b %Y %I:%M %p", created_epoch);
--connect to the database
local dbh = Database.new('system');
--get the templates
local sql = "SELECT * FROM v_email_templates ";
sql = sql .. "WHERE (domain_uuid = :domain_uuid or domain_uuid is null) ";
sql = sql .. "AND template_language = :template_language ";
sql = sql .. "AND template_category = 'voicemail' "
if (transcribe_enabled == 'true' and voicemail_transcription_enabled == 'true') then
sql = sql .. "AND template_subcategory = 'transcription' "
else
sql = sql .. "AND template_subcategory = 'default' "
end
sql = sql .. "AND template_enabled = 'true' "
sql = sql .. "ORDER BY domain_uuid DESC "
local params = {domain_uuid = domain_uuid, template_language = default_language.."-"..default_dialect};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
subject = row["template_subject"];
body = row["template_body"];
end);
--get the link_address
link_address = http_protocol.."://"..domain_name..project_path;
--prepare the headers
local headers = {
["X-FusionPBX-Domain-UUID"] = domain_uuid;
["X-FusionPBX-Domain-Name"] = domain_name;
["X-FusionPBX-Call-UUID"] = uuid;
["X-FusionPBX-Email-Type"] = 'voicemail';
["X-FusionPBX-local_after_email"] = voicemail_local_after_email;
}
--prepare the voicemail_name_formatted
voicemail_name_formatted = id;
local display_domain_name = settings:get('voicemail', 'display_domain_name', 'boolean');
if (display_domain_name == 'true') then
voicemail_name_formatted = id.."@"..domain_name;
end
if (voicemail_description ~= nil and voicemail_description ~= "" and voicemail_description ~= id) then
voicemail_name_formatted = voicemail_name_formatted.." ("..voicemail_description..")";
end
--prepare the subject
subject = subject:gsub("${caller_id_name}", caller_id_name);
subject = subject:gsub("${caller_id_number}", caller_id_number);
subject = subject:gsub("${message_date}", message_date);
subject = subject:gsub("${message_duration}", message_length_formatted);
subject = subject:gsub("${account}", voicemail_name_formatted);
subject = subject:gsub("${voicemail_id}", id);
subject = subject:gsub("${voicemail_description}", voicemail_description);
subject = subject:gsub("${voicemail_name_formatted}", voicemail_name_formatted);
subject = subject:gsub("${domain_name}", domain_name);
subject = subject:gsub("${new_messages}", new_messages);
subject = trim(subject);
subject = '=?utf-8?B?'..base64.encode(subject)..'?=';
--prepare the body
body = body:gsub("${caller_id_name}", caller_id_name);
body = body:gsub("${caller_id_number}", caller_id_number);
body = body:gsub("${message_date}", message_date);
if (transcription ~= nil) then
transcription = transcription:gsub("%%", "*");
body = body:gsub("${message_text}", transcription);
end
body = body:gsub("${message_duration}", message_length_formatted);
body = body:gsub("${account}", voicemail_name_formatted);
body = body:gsub("${voicemail_id}", id);
body = body:gsub("${voicemail_description}", voicemail_description);
body = body:gsub("${voicemail_name_formatted}", voicemail_name_formatted);
body = body:gsub("${domain_name}", domain_name);
body = body:gsub("${sip_to_user}", id);
if (origination_callee_id_name ~= nil) then
body = body:gsub("${origination_callee_id_name}", origination_callee_id_name);
end
body = body:gsub("${dialed_user}", id);
if (voicemail_file == "attach") then
body = body:gsub("${message}", text['label-attached']);
elseif (voicemail_file == "link") then
body = body:gsub("${message}", "<a href='"..link_address.."/app/voicemails/voicemail_messages.php?action=download&id="..id.."&voicemail_uuid="..db_voicemail_uuid.."&uuid="..uuid.."&t=bin'>"..text['label-download'].."</a>");
else
body = body:gsub("${message}", "<a href='"..link_address.."/app/voicemails/voicemail_messages.php?action=autoplay&id="..db_voicemail_uuid.."&uuid="..uuid.."'>"..text['label-listen'].."</a>");
end
--body = body:gsub(" ", "&nbsp;");
--body = body:gsub("%s+", "");
--body = body:gsub("&nbsp;", " ");
body = trim(body);
--prepare file
file = voicemail_dir.."/"..id.."/msg_"..uuid.."."..vm_message_ext;
--send the email
send_mail(headers,
nil,
voicemail_mail_to,
{subject, body},
(voicemail_file == "attach") and file
);
end
--whether to keep the voicemail message and details local after email
if (string.len(voicemail_mail_to) > 2 and email_queue_enabled == 'false') then
if (voicemail_local_after_email == "false") then
--delete the voicemail message details
local sql = [[DELETE FROM v_voicemail_messages
WHERE domain_uuid = :domain_uuid
AND voicemail_uuid = :voicemail_uuid
AND voicemail_message_uuid = :uuid]]
local params = {domain_uuid = domain_uuid,
voicemail_uuid = db_voicemail_uuid, uuid = uuid};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params);
--delete voicemail recording file
if (file_exists(file)) then
os.remove(file);
end
--set message waiting indicator
message_waiting(id, domain_uuid);
--clear the variable
db_voicemail_uuid = '';
elseif (storage_type == "base64") then
--delete voicemail recording file
if (file_exists(file)) then
os.remove(file);
end
end
end
end
@@ -0,0 +1,109 @@
-- Part of FusionPBX
-- Copyright (C) 2013 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--define a function to send sms
function send_sms(id, uuid)
debug["info"] = true;
api = freeswitch.API();
--get voicemail message details
sql = [[SELECT * FROM v_voicemails
WHERE domain_uuid = ']] .. domain_uuid ..[['
AND voicemail_id = ']] .. id ..[[']]
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "\n");
end
status = dbh:query(sql, function(row)
db_voicemail_uuid = string.lower(row["voicemail_uuid"]);
voicemail_sms_to = row["voicemail_sms_to"];
voicemail_file = row["voicemail_file"];
end);
--get the sms_body template
if (settings['voicemail']['voicemail_sms_body'] ~= nil) then
if (settings['voicemail']['voicemail_sms_body']['text'] ~= nil) then
sms_body = settings['voicemail']['voicemail_sms_body']['text'];
end
else
sms_body = 'You have a new voicemail from: ${caller_id_name} - ${caller_id_number} length ${message_length_formatted}';
end
--require the sms address to send to
if (string.len(voicemail_sms_to) > 2) then
--include languages file
local Text = require "resources.functions.text"
local text = Text.new("app.voicemail.app_languages")
--get voicemail message details
sql = [[SELECT * FROM v_voicemail_messages
WHERE domain_uuid = ']] .. domain_uuid ..[['
AND voicemail_message_uuid = ']] .. uuid ..[[']]
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "\n");
end
status = dbh:query(sql, function(row)
--get the values from the database
--uuid = row["voicemail_message_uuid"];
created_epoch = row["created_epoch"];
caller_id_name = row["caller_id_name"];
caller_id_number = row["caller_id_number"];
message_length = row["message_length"];
end);
--format the message length and date
message_length_formatted = format_seconds(message_length);
if (debug["info"]) then
freeswitch.consoleLog("notice", "[voicemail-sms] message length: " .. message_length .. "\n");
if (transcription ~= nil) then
freeswitch.consoleLog("notice", "[voicemail-sms] transcription: " .. transcription .. "\n");
end
freeswitch.consoleLog("notice", "[voicemail-sms] domain_name: " .. domain_name .. "\n");
end
local message_date = os.date("%A, %d %b %Y %I:%M %p", created_epoch)
sms_body = sms_body:gsub("${caller_id_name}", caller_id_name);
sms_body = sms_body:gsub("${caller_id_number}", caller_id_number);
sms_body = sms_body:gsub("${message_date}", message_date);
sms_body = sms_body:gsub("${message_duration}", message_length_formatted);
sms_body = sms_body:gsub("${account}", id);
sms_body = sms_body:gsub("${domain_name}", domain_name);
sms_body = sms_body:gsub("${sip_to_user}", id);
sms_body = sms_body:gsub("${dialed_user}", id);
if (transcription ~= nil) then
sms_body = sms_body:gsub("${message_text}", transcription);
end
if (debug["info"]) then
freeswitch.consoleLog("notice", "[voicemail-sms] sms_body: " .. sms_body .. "\n");
end
-- sms_body = "hello";
cmd = "luarun app.lua sms outbound " .. voicemail_sms_to .. "@" .. domain_name .. " " .. voicemail_to_sms_did .. " '" .. sms_body .. "'";
api:executeString(cmd);
end
end
@@ -0,0 +1,197 @@
-- Part of FusionPBX
-- Copyright (C) 2013 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--define function main menu
function tutorial (menu)
if (voicemail_uuid) then
--intro menu
if (menu == "intro") then
--clear the value
dtmf_digits = '';
--flush dtmf digits from the input buffer
session:flushDigits();
--play the tutorial press 1, to skip 2
if (session:ready()) then
if (string.len(dtmf_digits) == 0) then
dtmf_digits = session:playAndGetDigits(0, 1, 1, 3000, "#", "phrase:tutorial_intro", "", "\\d+");
end
end
--process the dtmf
if (session:ready()) then
if (dtmf_digits == "1") then
timeouts = 0;
tutorial("record_name");
elseif (dtmf_digits == "2") then
timeouts = 0;
tutorial("finish");
else
if (session:ready()) then
timeouts = timeouts + 1;
if (timeouts < max_timeouts) then
tutorial("intro");
else
timeouts = 0;
tutorial("finish");
end
end
end
end
end
--record name menu
if (menu == "record_name") then
--clear the value
dtmf_digits = '';
--flush dtmf digits from the input buffer
session:flushDigits();
--play the record name press 1
if (session:ready()) then
if (string.len(dtmf_digits) == 0) then
dtmf_digits = session:playAndGetDigits(0, 1, 1, 200, "#", "phrase:tutorial_record_name:1", "", "\\d+");
end
end
--skip the name and go to password press 2
if (session:ready()) then
if (string.len(dtmf_digits) == 0) then
dtmf_digits = session:playAndGetDigits(0, 1, 1, 3000, "#", "phrase:tutorial_skip:2", "", "\\d+");
end
end
--process the dtmf
if (session:ready()) then
if (dtmf_digits == "1") then
timeouts = 0;
record_name("tutorial");
elseif (dtmf_digits == "2") then
timeouts = 0;
tutorial("change_password");
else
if (session:ready()) then
timeouts = timeouts + 1;
if (timeouts < max_timeouts) then
tutorial("record_name");
else
tutorial("change_password");
end
end
end
end
end
--change password menu
if (menu == "change_password") then
--clear the value
dtmf_digits = '';
--flush dtmf digits from the input buffer
session:flushDigits();
--to change your password press 1
if (session:ready()) then
if (string.len(dtmf_digits) == 0) then
dtmf_digits = session:playAndGetDigits(0, 1, 1, 200, "#", "phrase:tutorial_change_password:1", "", "\\d+");
end
end
--skip the password and go to greeting press 2
if (session:ready()) then
if (string.len(dtmf_digits) == 0) then
dtmf_digits = session:playAndGetDigits(0, 1, 1, 3000, "#", "phrase:tutorial_skip:2", "", "\\d+");
end
end
--process the dtmf
if (session:ready()) then
if (dtmf_digits == "1") then
timeouts = 0;
change_password(voicemail_id, "tutorial");
elseif (dtmf_digits == "2") then
timeouts = 0;
tutorial("record_greeting");
else
if (session:ready()) then
timeouts = timeouts + 1;
if (timeouts < max_timeouts) then
tutorial("change_password");
else
tutorial("record_greeting");
end
end
end
end
end
--change greeting menu
if (menu == "record_greeting") then
--clear the value
dtmf_digits = '';
--flush dtmf digits from the input buffer
session:flushDigits();
--to record a greeting press 1
if (session:ready()) then
if (string.len(dtmf_digits) == 0) then
dtmf_digits = session:playAndGetDigits(0, 1, 1, 200, "#", "phrase:tutorial_record_greeting:1", "", "\\d+");
end
end
--skip the record greeting press 2. finishes the tutorial and routes to main menu
if (session:ready()) then
if (string.len(dtmf_digits) == 0) then
dtmf_digits = session:playAndGetDigits(0, 1, 1, 3000, "#", "phrase:tutorial_skip:2", "", "\\d+");
end
end
--process the dtmf
if (session:ready()) then
if (dtmf_digits == "1") then
timeouts = 0;
record_greeting(nil, "tutorial");
elseif (dtmf_digits == "2") then
timeouts = 0;
tutorial("finish");
else
if (session:ready()) then
timeouts = timeouts + 1;
if (timeouts < max_timeouts) then
tutorial("record_greeting");
else
tutorial("finish");
end
end
end
end
end
if (menu == "finish") then
--clear the value
dtmf_digits = '';
--flush dtmf digits from the input buffer
session:flushDigits();
--update play tutorial in the datebase
local sql = [[UPDATE v_voicemails
set voicemail_tutorial = 'false'
WHERE domain_uuid = :domain_uuid
AND voicemail_id = :voicemail_id
AND voicemail_enabled = 'true' ]];
local params = {domain_uuid = domain_uuid,
voicemail_id = voicemail_id};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params);
--go to main menu
main_menu();
end
end
end
@@ -0,0 +1,109 @@
-- Part of FusionPBX
-- Copyright (C) 2013 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--include the lua script
require "resources.functions.config";
--define general settings
sleep = 300;
--define the run file
run_file = scripts_dir .. "/run/voicemail-mwi.tmp";
--debug
debug["sql"] = false;
debug["info"] = false;
--only run the script a single time
runonce = false;
--connect to the database
local Database = require "resources.functions.database";
dbh = Database.new('system');
--used to stop the lua service
local file = assert(io.open(run_file, "w"));
file:write("remove this file to stop the script");
--define the trim function
require "resources.functions.trim";
--check if a file exists
require "resources.functions.file_exists";
--send MWI NOTIFY message
require "app.voicemail.resources.functions.mwi_notify";
--get message count for mailbox
require "app.voicemail.resources.functions.message_count";
--create the api object
api = freeswitch.API();
--run lua as a service
while true do
--exit the loop when the file does not exist
if (not file_exists(run_file)) then
freeswitch.consoleLog("NOTICE", run_file.." not found\n");
break;
end
--Send MWI events for voicemail boxes with messages
local sql = [[SELECT v.voicemail_id, v.voicemail_uuid, v.domain_uuid, d.domain_name, COUNT(*) AS message_count
FROM v_voicemail_messages as m, v_voicemails as v, v_domains as d
WHERE v.voicemail_uuid = m.voicemail_uuid
AND v.domain_uuid = d.domain_uuid
AND v.voicemail_enabled = 'true'
GROUP BY v.voicemail_id, v.voicemail_uuid, v.domain_uuid, d.domain_name;]];
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "\n");
end
dbh:query(sql, function(row)
--get saved and new message counts
local new_messages, saved_messages = message_count_by_uuid(
row["voicemail_uuid"], row["domain_uuid"]
)
--send the message waiting event
local account = row["voicemail_id"].."@"..row["domain_name"]
mwi_notify(account, new_messages, saved_messages)
--log to console
if (debug["info"]) then
freeswitch.consoleLog("notice", "[voicemail] mailbox: "..account.." messages: " .. (new_messages or "0") .. "/" .. (saved_messages or "0") .. " \n");
end
end);
if (runonce) then
freeswitch.consoleLog("notice", "mwi.lua has ended\n");
break;
else
--slow the loop down
os.execute("sleep "..sleep);
end
end
@@ -0,0 +1,22 @@
--get the argv values
voicemail_id = argv[1];
domain_name = argv[2];
new_messages = argv[3] or '0';
saved_messages = argv[4] or '0';
--include the lua script
require "resources.functions.config";
--send MWI NOTIFY message
require "app.voicemail.resources.functions.mwi_notify";
--debug info
--freeswitch.consoleLog("NOTICE", "voicemail_id="..voicemail_id.."\n");
--freeswitch.consoleLog("NOTICE", "domain_name="..domain_name.."\n");
--freeswitch.consoleLog("NOTICE", "new_messages="..new_messages.."\n");
--freeswitch.consoleLog("NOTICE", "saved_messages="..saved_messages.."\n");
--send the message waiting event
mwi_notify(voicemail_id..'@'..domain_name, domain_name, new_messages, saved_messages);
@@ -0,0 +1,106 @@
require "resources.functions.config"
require "resources.functions.split"
local service_name = "mwi"
local log = require "resources.functions.log"[service_name]
local BasicEventService = require "resources.functions.basic_event_service"
local Database = require "resources.functions.database"
local cache = require "resources.functions.cache"
local mwi_notify = require "app.voicemail.resources.functions.mwi_notify"
local vm_message_count do
local vm_to_uuid_sql = [[SELECT v.voicemail_uuid
FROM v_voicemails as v inner join v_domains as d on v.domain_uuid = d.domain_uuid
WHERE v.voicemail_enabled = 'true' and v.voicemail_id = :voicemail_id and d.domain_name = :domain_name]]
local vm_messages_sql = [[SELECT
( SELECT count(*)
FROM v_voicemail_messages
WHERE voicemail_uuid = :voicemail_uuid
AND (message_status is null or message_status = '')
) as new_messages,
( SELECT count(*)
FROM v_voicemail_messages
WHERE voicemail_uuid = :voicemail_uuid
AND message_status = 'saved'
) as saved_messages
]]
function vm_message_count(account, use_cache)
local id, domain_name = split_first(account, '@', true)
if not domain_name then return end
-- FusionPBX support only numeric voicemail id
if not tonumber(id) then
log.warningf('non numeric voicemail id: %s', id)
return
end
local dbh = Database.new('system')
if not dbh then return end
-- Get the UUID from the cache if enabled/supported
local uuid
if use_cache and cache.support() then
uuid = cache.get('voicemail_uuid:' .. account)
end
-- If no UUID cached or if cache is not enabled/supported get the UUID from the database
if not uuid then
uuid = dbh:first_value(vm_to_uuid_sql, {
voicemail_id = id, domain_name = domain_name
})
if uuid and #uuid > 0 and use_cache and cache.support() then
cache.set('voicemail_uuid:' .. account, uuid, 3600)
end
end
-- Get the count of unread and read messages from the database if there is a valid voicemail account
local row
if uuid and #uuid > 0 then
row = dbh:first_row(vm_messages_sql, {voicemail_uuid = uuid})
end
dbh:release()
-- This condition can be hit if the voicemail box is either disabled or non-existent
if not row then return end
return row.new_messages, row.saved_messages
end
end
local service = BasicEventService.new(log, service_name)
-- MWI SUBSCRIBE
service:bind("MESSAGE_QUERY", function(self, name, event)
local account_header = event:getHeader('Message-Account')
if not account_header then
return log.warningf("MWI message without `Message-Account` header")
end
local proto, account = split_first(account_header, ':', true)
if (not account) or (proto ~= 'sip' and proto ~= 'sips') then
return log.warningf("invalid format for voicemail id: %s", account_header)
end
local new_messages, saved_messages = vm_message_count(account)
if not new_messages then
return log.warningf('can not find voicemail: %s', account)
end
log.noticef('voicemail %s has %s/%s message(s)', account, new_messages, saved_messages)
mwi_notify(account, new_messages, saved_messages)
end)
log.notice("start")
service:run()
log.notice("stop")
@@ -0,0 +1,61 @@
<html>
<table width="400" border="0" cellspacing="0" cellpadding="0" align="center"
style="border: 1px solid #cbcfd5;-moz-border-radius: 4px;
-webkit-border-radius: 4px; border-radius: 4px;">
<tr>
<td valign="middle" align="center" bgcolor="#e5e9f0" style="background-color: #e5e9f0;
color: #000; font-family: Arial; font-size: 14px; padding: 7px;-moz-border-radius: 4px;
-webkit-border-radius: 4px; border-radius: 4px;">
<strong>Neue Sprachnachricht</strong>
</td>
</tr>
<tr>
<td valign="top" style="padding: 15px;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td style="color: #333; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
<strong>Nebenstelle</strong>
</td>
<td style="color: #666; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
${voicemail_name_formatted}
</td>
</tr>
<tr>
<td style="color: #333; font-family: Arial; font-size: 12px; padding-bottom: 11px;" width="20%">
<strong>Anrufer</strong>
</td>
<td style="color: #666; font-family: Arial; font-size: 12px; padding-bottom: 11px;" width="80%">
${caller_id_number}
</td>
</tr>
<!--
<tr>
<td style="color: #333; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
<strong>Received</strong>
</td>
<td style="color: #666; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
${message_date}
</td>
</tr>
-->
<tr>
<td style="color: #333; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
<strong>Nachricht</strong>
</td>
<td style="color: #666; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
${message}
</td>
</tr>
<tr>
<td style="color: #333; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
<strong>L&auml;nge</strong>
</td>
<td style="color: #666; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
${message_duration}
</td>
</tr>
</table>
</td>
</tr>
</table>
</html>
@@ -0,0 +1 @@
Sprachnachricht von ${caller_id_name} <${caller_id_number}> ${message_duration}
@@ -0,0 +1,61 @@
<html>
<table width="400" border="0" cellspacing="0" cellpadding="0" align="center"
style="border: 1px solid #cbcfd5;-moz-border-radius: 4px;
-webkit-border-radius: 4px; border-radius: 4px;">
<tr>
<td valign="middle" align="center" bgcolor="#e5e9f0" style="background-color: #e5e9f0;
color: #000; font-family: Arial; font-size: 14px; padding: 7px;-moz-border-radius: 4px;
-webkit-border-radius: 4px; border-radius: 4px;">
<strong>Neue Sprachnachricht</strong>
</td>
</tr>
<tr>
<td valign="top" style="padding: 15px;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td style="color: #333; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
<strong>Nebenstelle</strong>
</td>
<td style="color: #666; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
${voicemail_name_formatted}
</td>
</tr>
<tr>
<td style="color: #333; font-family: Arial; font-size: 12px; padding-bottom: 11px;" width="20%">
<strong>Anrufer</strong>
</td>
<td style="color: #666; font-family: Arial; font-size: 12px; padding-bottom: 11px;" width="80%">
${caller_id_number}
</td>
</tr>
<!--
<tr>
<td style="color: #333; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
<strong>Received</strong>
</td>
<td style="color: #666; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
${message_date}
</td>
</tr>
-->
<tr>
<td style="color: #333; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
<strong>Nachricht</strong>
</td>
<td style="color: #666; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
${message}
</td>
</tr>
<tr>
<td style="color: #333; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
<strong>L&auml;nge</strong>
</td>
<td style="color: #666; font-family: Arial; font-size: 12px; padding-bottom: 11px;">
${message_duration}
</td>
</tr>
</table>
</td>
</tr>
</table>
</html>
@@ -0,0 +1 @@
Sprachnachricht von ${caller_id_name} <${caller_id_number}> ${message_duration}
@@ -0,0 +1,6 @@
To ${voicemail_name_formatted}
Received ${message_date}
Message ${message}
Length ${message_duration}
@@ -0,0 +1 @@
Voicemail from ${caller_id_name} <${caller_id_number}> ${message_duration}
@@ -0,0 +1,6 @@
To ${voicemail_name_formatted}
Received ${message_date}
Message ${message}
Length ${message_duration}
@@ -0,0 +1,9 @@
To ${voicemail_name_formatted}
Received ${message_date}
Message ${message}
Length ${message_duration}
Message Text
${message_text}
@@ -0,0 +1 @@
Voicemail from ${caller_id_name} <${caller_id_number}> ${message_duration}
@@ -0,0 +1,108 @@
-- Part of FusionPBX
-- Copyright (C) 2013 - 2022 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--general functions
require "resources.functions.trim";
require "resources.functions.file_exists";
require "resources.functions.explode";
--if the params class and methods do not exist then add them to prevent errors
if (not params) then
params = {}
function params:getHeader(name)
self.name = name;
end
function params:serialize(name)
self.name = name;
end
end
--show the params in the console
if (debug["params"]) then
if (params:serialize() ~= nil) then
freeswitch.consoleLog("notice", "[xml_handler] Params:\n" .. params:serialize() .. "\n");
end
end
--show the xml request in the console
if (debug["xml_request"]) then
freeswitch.consoleLog("notice", "[xml_handler] Section: " .. XML_REQUEST["section"] .. "\n");
freeswitch.consoleLog("notice", "[xml_handler] Tag Name: " .. XML_REQUEST["tag_name"] .. "\n");
freeswitch.consoleLog("notice", "[xml_handler] Key Name: " .. XML_REQUEST["key_name"] .. "\n");
freeswitch.consoleLog("notice", "[xml_handler] Key Value: " .. XML_REQUEST["key_value"] .. "\n");
end
--get the params and set them as variables
domain_name = params:getHeader("sip_from_host");
if (domain_uuid == nil) then
domain_uuid = params:getHeader("domain_uuid");
end
domain_name = params:getHeader("domain");
if (domain_name == nil) then
domain_name = params:getHeader("domain_name");
end
if (domain_name == nil) then
domain_name = params:getHeader("variable_domain_name");
end
if (domain_name == nil) then
domain_name = params:getHeader("variable_sip_from_host");
end
purpose = params:getHeader("purpose");
profile = params:getHeader("profile");
key = params:getHeader("key");
user = params:getHeader("user");
user_context = params:getHeader("variable_user_context");
call_context = params:getHeader("Caller-Context");
destination_number = params:getHeader("Caller-Destination-Number");
sip_to_user = params:getHeader("variable_sip_to_user");
sip_req_user = params:getHeader("variable_sip_req_user");
caller_id_number = params:getHeader("Caller-Caller-ID-Number");
hunt_context = params:getHeader("Hunt-Context");
if (hunt_context ~= nil) then
call_context = hunt_context;
end
--prepare the api object
api = freeswitch.API();
--process the sections
if (XML_REQUEST["section"] == "configuration") then
configuration = scripts_dir.."/app/xml_handler/resources/scripts/configuration/"..XML_REQUEST["key_value"]..".lua";
if (debug["xml_request"]) then
freeswitch.consoleLog("notice", "[xml_handler] " .. configuration .. "\n");
end
if (file_exists(configuration)) then
dofile(configuration);
end
end
if (XML_REQUEST["section"] == "directory") then
dofile(scripts_dir.."/app/xml_handler/resources/scripts/directory/directory.lua");
end
if (XML_REQUEST["section"] == "dialplan") then
dofile(scripts_dir.."/app/xml_handler/resources/scripts/dialplan/dialplan.lua");
end
if (XML_REQUEST["section"] == "languages") then
dofile(scripts_dir.."/app/xml_handler/resources/scripts/languages/languages.lua");
end
@@ -0,0 +1,162 @@
-- xml_handler.lua
-- Part of FusionPBX
-- Copyright (C) 2015-2018 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--[[
These ACL's are automatically created on startup.
rfc1918.auto - RFC1918 Space
nat.auto - RFC1918 Excluding your local lan.
localnet.auto - ACL for your local lan.
loopback.auto - ACL for your local lan.
]]
--include xml library
local Xml = require "resources.functions.xml";
--get the cache
local cache = require "resources.functions.cache"
local acl_cache_key = "configuration:acl.conf"
XML_STRING, err = cache.get(acl_cache_key)
--set the cache
if not XML_STRING then
--log cache error
if (debug["cache"]) then
freeswitch.consoleLog("warning", "[xml_handler] configuration:acl.conf can not be get from the cache: " .. tostring(err) .. "\n");
end
--set a default value
if (expire["acl"] == nil) then
expire["acl"]= "3600";
end
--connect to the database
local Database = require "resources.functions.database";
dbh = Database.new('system');
--include json library
local json
if (debug["sql"]) then
json = require "resources.functions.lunajson"
end
--exits the script if we didn't connect properly
assert(dbh:connected());
--start the xml array
local xml = Xml:new();
xml:append([[<?xml version="1.0" encoding="UTF-8" standalone="no"?>]]);
xml:append([[<document type="freeswitch/xml">]]);
xml:append([[ <section name="configuration">]]);
xml:append([[ <configuration name="acl.conf" description="Network Lists">]]);
xml:append([[ <network-lists>]]);
--run the query
sql = "select * from v_access_controls ";
sql = sql .. "order by access_control_name asc ";
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[xml_handler] SQL: " .. sql .. "\n");
end
x = 0;
dbh:query(sql, function(row)
--list open tag
xml:append([[ <list name="]] .. xml.sanitize(row.access_control_name) .. [[" default="]] .. xml.sanitize(row.access_control_default) .. [[">]]);
--get the nodes
sql = "select * from v_access_control_nodes ";
sql = sql .. "where access_control_uuid = :access_control_uuid ";
sql = sql .. "and length(node_cidr) > 0 ";
local params = {access_control_uuid = row.access_control_uuid}
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[xml_handler] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
x = 0;
dbh:query(sql, params, function(field)
xml:append([[ <node type="]] .. xml.sanitize(field.node_type) .. [[" cidr="]] .. xml.sanitize(field.node_cidr) .. [[" description="]] .. xml.sanitize(field.node_description) .. [["/>]]);
end)
--add the domains
if (row.access_control_name == 'providers' or row.access_control_name == 'domains') then
sql = "select domain_name, domain_description from v_domains ";
sql = sql .. "where domain_uuid in (select distinct(domain_uuid) ";
sql = sql .. "from v_extensions where enabled = 'true') ";
sql = sql .. "and domain_enabled = 'true' ";
local params = {}
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[xml_handler] SQL: " .. sql .. ";\n");
end
x = 0;
dbh:query(sql, params, function(field)
xml:append([[ <node type="allow" domain="]] .. xml.sanitize(field.domain_name) .. [[" description="]] .. xml.sanitize(field.domain_description) .. [["/>]]);
end)
end
--list close tag
xml:append([[ </list>]]);
end)
--close the extension tag if it was left open
xml:append([[ </network-lists>]]);
xml:append([[ </configuration>]]);
xml:append([[ </section>]]);
xml:append([[</document>]]);
XML_STRING = xml:build();
if (debug["xml_string"]) then
freeswitch.consoleLog("notice", "[xml_handler] XML_STRING: " .. XML_STRING .. "\n");
end
--close the database connection
dbh:release();
--set the cache
local ok, err = cache.set(acl_cache_key, XML_STRING, expire["acl"]);
if debug["cache"] then
if ok then
freeswitch.consoleLog("notice", "[xml_handler] " .. acl_cache_key .. " stored in the cache\n");
else
freeswitch.consoleLog("warning", "[xml_handler] " .. acl_cache_key .. " can not be stored in the cache: " .. tostring(err) .. "\n");
end
end
--send to the console
if (debug["cache"]) then
freeswitch.consoleLog("notice", "[xml_handler] " .. acl_cache_key .. " source: database\n");
end
else
--send to the console
if (debug["cache"]) then
freeswitch.consoleLog("notice", "[xml_handler] " .. acl_cache_key .. " source: cache\n");
end
end --if XML_STRING
--send the xml to the console
if (debug["xml_string"]) then
local file = assert(io.open(temp_dir .. "/acl.conf.xml", "w"));
file:write(XML_STRING);
file:close();
end
@@ -0,0 +1,347 @@
-- xml_handler.lua
-- Part of FusionPBX
-- Copyright (C) 2015-2022 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--include functions
require "resources.functions.format_ringback"
--include xml library
local Xml = require "resources.functions.xml";
--get the cache
local cache = require "resources.functions.cache"
hostname = trim(api:execute("switchname", ""));
local cc_cache_key = "configuration:callcenter.conf:" .. hostname
XML_STRING, err = cache.get(cc_cache_key)
--set the cache
if not XML_STRING then
--log cache error
if (debug["cache"]) then
freeswitch.consoleLog("warning", "[xml_handler] " .. cc_cache_key .. " can not be get from the cache: " .. tostring(err) .. "\n");
end
--connect to the database
local Database = require "resources.functions.database";
dbh = Database.new('system');
--exits the script if we didn't connect properly
assert(dbh:connected());
--get the variables
dsn = freeswitch.getGlobalVariable("dsn") or ''
dsn_callcenter = freeswitch.getGlobalVariable("dsn_callcenter") or ''
--start the xml array
local xml = Xml:new();
xml:append([[<?xml version="1.0" encoding="UTF-8" standalone="no"?>]]);
xml:append([[<document type="freeswitch/xml">]]);
xml:append([[ <section name="configuration">]]);
xml:append([[ <configuration name="callcenter.conf" description="Call Center">]]);
xml:append([[ <settings>]]);
if #dsn_callcenter > 0 then
xml:append([[ <param name="odbc-dsn" value="]] .. xml.sanitize(dsn_callcenter) .. [["/>]]);
elseif #dsn > 0 then
xml:append([[ <param name="odbc-dsn" value="]] .. xml.sanitize(database["switch"]) .. [["/>]]);
end
xml:append([[ <param name="cc-instance-id" value="]] .. xml.sanitize(hostname) .. [["/>]]);
-- xml:append([[ <param name="dbname" value="]] .. xml.sanitize(database_dir) .. [[/call_center.db"/>]]);
xml:append([[ </settings>]]);
--write the queues
xml:append([[ <queues>]]);
sql = "select * from v_call_center_queues as q, v_domains as d ";
sql = sql .. "where d.domain_uuid = q.domain_uuid; ";
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[xml_handler] SQL: " .. sql .. "\n");
end
x = 0;
dbh:query(sql, function(row)
--set as variables
queue_uuid = row.call_center_queue_uuid;
domain_uuid = row.domain_uuid;
domain_name = row.domain_name;
queue_name = row.queue_name;
queue_extension = row.queue_extension;
queue_strategy = row.queue_strategy;
queue_moh_sound = row.queue_moh_sound;
queue_record_template = row.queue_record_template;
queue_time_base_score = row.queue_time_base_score;
queue_max_wait_time = row.queue_max_wait_time;
queue_max_wait_time_with_no_agent = row.queue_max_wait_time_with_no_agent;
queue_max_wait_time_with_no_agent_time_reached = row.queue_max_wait_time_with_no_agent_time_reached;
queue_tier_rules_apply = row.queue_tier_rules_apply;
queue_tier_rule_wait_second = row.queue_tier_rule_wait_second;
queue_tier_rule_wait_multiply_level = row.queue_tier_rule_wait_multiply_level;
queue_tier_rule_no_agent_no_wait = row.queue_tier_rule_no_agent_no_wait;
queue_discard_abandoned_after = row.queue_discard_abandoned_after;
queue_abandoned_resume_allowed = row.queue_abandoned_resume_allowed;
queue_announce_sound = row.queue_announce_sound;
queue_announce_frequency = row.queue_announce_frequency;
queue_description = row.queue_description;
--replace the space with a dash
queue_name = queue_name:gsub(" ", "-");
--sanitize the queue_record_template and allow specific variables
queue_record_template = xml.sanitize(queue_record_template);
queue_record_template = string.gsub(queue_record_template, "{strftime", "${strftime");
queue_record_template = string.gsub(queue_record_template, "{uuid}", "${uuid}");
queue_record_template = string.gsub(queue_record_template, "{record_ext}", "${record_ext}");
--start the xml
xml:append([[ <queue name="]] .. xml.sanitize(queue_extension) .. [[@]] .. xml.sanitize(domain_name) .. [[" label="]] .. xml.sanitize(queue_name) .. [[@]] .. xml.sanitize(domain_name) .. [[">]]);
xml:append([[ <param name="strategy" value="]] .. xml.sanitize(queue_strategy) .. [["/>]]);
--set ringback
queue_ringback = format_ringback(queue_moh_sound);
xml:append([[ <param name="moh-sound" value="]] .. xml.sanitize(queue_ringback) .. [["/>]]);
if (queue_record_template ~= nil) then
xml:append([[ <param name="record-template" value="]] .. queue_record_template .. [["/>]]);
end
if (queue_time_base_score ~= nil) then
xml:append([[ <param name="time-base-score" value="]] .. xml.sanitize(queue_time_base_score) .. [["/>]]);
end
if (queue_max_wait_time_with_no_agent ~= nil) then
xml:append([[ <param name="max-wait-time" value="]] .. xml.sanitize(queue_max_wait_time) .. [["/>]]);
end
if (queue_max_wait_time_with_no_agent ~= nil) then
xml:append([[ <param name="max-wait-time-with-no-agent" value="]] .. xml.sanitize(queue_max_wait_time_with_no_agent) .. [["/>]]);
end
if (queue_max_wait_time_with_no_agent_time_reached ~= nil) then
xml:append([[ <param name="max-wait-time-with-no-agent-time-reached" value="]] .. xml.sanitize(queue_max_wait_time_with_no_agent_time_reached) .. [["/>]]);
end
if (queue_tier_rules_apply ~= nil) then
xml:append([[ <param name="tier-rules-apply" value="]] .. xml.sanitize(queue_tier_rules_apply) .. [["/>]]);
end
if (queue_tier_rule_wait_second ~= nil) then
xml:append([[ <param name="tier-rule-wait-second" value="]] .. xml.sanitize(queue_tier_rule_wait_second) .. [["/>]]);
end
if (queue_tier_rule_wait_multiply_level ~= nil) then
xml:append([[ <param name="tier-rule-wait-multiply-level" value="]] .. xml.sanitize(queue_tier_rule_wait_multiply_level) .. [["/>]]);
end
if (queue_tier_rule_no_agent_no_wait ~= nil) then
xml:append([[ <param name="tier-rule-no-agent-no-wait" value="]] .. xml.sanitize(queue_tier_rule_no_agent_no_wait) .. [["/>]]);
end
if (queue_discard_abandoned_after ~= nil) then
xml:append([[ <param name="discard-abandoned-after" value="]] .. xml.sanitize(queue_discard_abandoned_after) .. [["/>]]);
end
if (queue_abandoned_resume_allowed ~= nil) then
xml:append([[ <param name="abandoned-resume-allowed" value="]] .. xml.sanitize(queue_abandoned_resume_allowed) .. [["/>]]);
end
if (queue_announce_sound ~= nil) then
xml:append([[ <param name="announce-sound" value="]] .. xml.sanitize(queue_announce_sound) .. [["/>]]);
end
if (queue_announce_frequency ~= nil) then
xml:append([[ <param name="announce-frequency" value="]] .. xml.sanitize(queue_announce_frequency) .. [["/>]]);
end
xml:append([[ </queue>]]);
--increment the value of x
x = x + 1;
end)
xml:append([[ </queues>]]);
--get the agents
xml:append([[ <agents>]]);
sql = "select SPLIT_PART(SPLIT_PART(a.agent_contact, '/', 2), '@', 1) as extension, ";
sql = sql .. "(select extension_uuid from v_extensions where domain_uuid = a.domain_uuid ";
sql = sql .. "and extension = SPLIT_PART(SPLIT_PART(a.agent_contact, '/', 2), '@', 1) limit 1) as extension_uuid, ";
sql = sql .. "a.*, d.domain_name ";
sql = sql .. "from v_call_center_agents as a, v_domains as d ";
sql = sql .. "where d.domain_uuid = a.domain_uuid; ";
--sql = "select * from v_call_center_agents as a, v_domains as d ";
--sql = sql .. "where d.domain_uuid = a.domain_uuid; ";
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[xml_handler] SQL: " .. sql .. "\n");
end
x = 0;
dbh:query(sql, function(row)
--get the values from the database and set as variables
agent_uuid = row.call_center_agent_uuid;
domain_uuid = row.domain_uuid;
domain_name = row.domain_name;
extension_uuid = row.extension_uuid;
agent_name = row.agent_name;
agent_type = row.agent_type;
agent_call_timeout = row.agent_call_timeout;
agent_contact = row.agent_contact;
agent_status = row.agent_status;
agent_no_answer_delay_time = row.agent_no_answer_delay_time;
agent_max_no_answer = row.agent_max_no_answer;
agent_wrap_up_time = row.agent_wrap_up_time;
agent_reject_delay_time = row.agent_reject_delay_time;
agent_busy_delay_time = row.agent_busy_delay_time;
agent_record = row.agent_record;
--get and then set the complete agent_contact with the call_timeout and when necessary confirm
--confirm = "group_confirm_file=custom/press_1_to_accept_this_call.wav,group_confirm_key=1";
--if you change this variable also change app/call_center/call_center_agent_edit.php
confirm = "group_confirm_file=ivr/ivr-accept_reject_voicemail.wav,group_confirm_key=1,group_confirm_read_timeout=2000,leg_timeout="..agent_call_timeout;
local record = "";
if (agent_record == "true") then
record = string.format(",execute_on_pre_bridge='record_session %s/%s/archive/${strftime(%%Y)}/${strftime(%%b)}/${strftime(%%d)}/${uuid}.${record_ext}'", recordings_dir, domain_name)
end
if (string.find(agent_contact, '}') == nil) then
--not found
if (string.find(agent_contact, 'sofia/gateway') == nil) then
--add the call_timeout
agent_contact = "{call_timeout="..agent_call_timeout..",domain_name="..domain_name..",domain_uuid="..domain_uuid..",extension_uuid="..extension_uuid..",sip_h_caller_destination=${caller_destination}"..record.."}"..agent_contact;
else
--add the call_timeout and confirm
agent_contact = "{"..confirm..",call_timeout="..agent_call_timeout..",domain_name="..domain_name..",domain_uuid="..domain_uuid..",sip_h_caller_destination=${caller_destination}}"..agent_contact;
end
else
--found
if (string.find(agent_contact, 'sofia/gateway') == nil) then
--not found
if (string.find(agent_contact, 'call_timeout') == nil) then
--add the call_timeout
pos = string.find(agent_contact, "}");
first = string.sub(agent_contact, 0, pos -1);
last = string.sub(agent_contact, pos);
agent_contact = first..[[,domain_name=]]..domain_name..[[,domain_uuid=]]..domain_uuid..[[,sip_h_caller_destination=${caller_destination},call_timeout=]]..agent_call_timeout..last;
else
--add the call_timeout
pos = string.find(agent_contact, "}");
first = string.sub(agent_contact, 0, pos - 1);
last = string.sub(agent_contact, pos);
agent_contact = first..[[,sip_h_caller_destination=${caller_destination},call_timeout=]]..agent_call_timeout..last;
end
else
--found
pos = string.find(agent_contact, "}");
first = string.sub(agent_contact, 0, pos - 1);
last = string.sub(agent_contact, pos);
if (string.find(agent_contact, 'call_timeout') == nil) then
--add the call_timeout and confirm
agent_contact = first..','..confirm..',sip_h_caller_destination=${caller_destination},domain_name="..domain_name..",domain_uuid="..domain_uuid..",sip_h_caller_destination=${caller_destination},call_timeout='..agent_call_timeout..last;
else
--add confirm
agent_contact = tmp_first..',domain_name="..domain_name..",domain_uuid="..domain_uuid..",sip_h_caller_destination=${caller_destination},'..confirm..tmp_last;
end
end
end
--sanitize the agent_contact and allow specific variables
agent_contact = xml.sanitize(agent_contact);
agent_contact = string.gsub(agent_contact, "{caller_destination}", "${caller_destination}");
--build the xml string
xml:append([[ <agent ]]);
xml:append([[ name="]] .. xml.sanitize(agent_uuid) .. [[" ]]);
xml:append([[ label="]] .. xml.sanitize(agent_name) .. [[@]] .. xml.sanitize(domain_name) .. [[" ]]);
xml:append([[ type="]] .. xml.sanitize(agent_type) .. [[" ]]);
xml:append([[ contact="]] .. agent_contact .. [[" ]]);
xml:append([[ status="]] .. xml.sanitize(agent_status) .. [[" ]]);
if (agent_no_answer_delay_time ~= nil) then
xml:append([[ no-answer-delay-time="]] .. xml.sanitize(agent_no_answer_delay_time) .. [[" ]]);
end
if (agent_max_no_answer ~= nil) then
xml:append([[ max-no-answer="]] .. xml.sanitize(agent_max_no_answer) .. [[" ]]);
end
if (agent_wrap_up_time ~= nil) then
xml:append([[ wrap-up-time="]] .. xml.sanitize(agent_wrap_up_time) .. [[" ]]);
end
if (agent_reject_delay_time ~= nil) then
xml:append([[ reject-delay-time="]] .. xml.sanitize(agent_reject_delay_time) .. [[" ]]);
end
if (agent_busy_delay_time ~= nil) then
xml:append([[ busy-delay-time="]] .. xml.sanitize(agent_busy_delay_time) .. [[" ]]);
end
xml:append([[ />]]);
end)
xml:append([[ </agents>]]);
--get the tiers
sql = "select t.domain_uuid, d.domain_name, t.call_center_agent_uuid, t.call_center_queue_uuid, q.queue_extension, t.tier_level, t.tier_position ";
sql = sql .. "from v_call_center_tiers as t, v_domains as d, v_call_center_queues as q ";
sql = sql .. "where d.domain_uuid = t.domain_uuid ";
sql = sql .. "and t.call_center_queue_uuid = q.call_center_queue_uuid; ";
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[xml_handler] SQL: " .. sql .. "\n");
end
xml:append([[ <tiers>]]);
dbh:query(sql, function(row)
--get the values from the database and set as variables
domain_uuid = row.domain_uuid;
domain_name = row.domain_name;
agent_uuid = row.call_center_agent_uuid;
queue_uuid = row.call_center_queue_uuid;
queue_extension = row.queue_extension;
tier_level = row.tier_level;
tier_position = row.tier_position;
--build the xml
xml:append([[ <tier ]]);
xml:append([[ agent="]] .. xml.sanitize(agent_uuid) .. [[" ]]);
xml:append([[ queue="]] .. xml.sanitize(queue_extension) .. [[@]] .. xml.sanitize(domain_name) .. [[" ]]);
xml:append([[ domain_name="]] .. xml.sanitize(domain_name) .. [[" ]]);
--xml:append([[ agent_name="]] .. xml.sanitize(agent_name) .. [[" ]]);
--xml:append([[ queue_name="]] .. xml.sanitize(queue_name) .. [[" ]]);
xml:append([[ level="]] .. xml.sanitize(tier_level) .. [[" ]]);
xml:append([[ position="]] .. xml.sanitize(tier_position) .. [[" ]]);
xml:append([[ />]]);
end)
xml:append([[ </tiers>]]);
--close the extension tag if it was left open
xml:append([[ </configuration>]]);
xml:append([[ </section>]]);
xml:append([[</document>]]);
XML_STRING = xml:build();
if (debug["xml_string"]) then
freeswitch.consoleLog("notice", "[xml_handler] XML_STRING: " .. XML_STRING .. "\n");
end
--close the database connection
dbh:release();
--freeswitch.consoleLog("notice", "[xml_handler]"..api:execute("eval ${dsn}"));
--set the cache
local ok, err = cache.set(cc_cache_key, XML_STRING, expire["callcenter"]);
if debug["cache"] then
if ok then
freeswitch.consoleLog("notice", "[xml_handler] " .. cc_cache_key .. " stored in the cache\n");
else
freeswitch.consoleLog("warning", "[xml_handler] " .. cc_cache_key .. " can not be stored in the cache: " .. tostring(err) .. "\n");
end
end
--send to the console
if (debug["cache"]) then
freeswitch.consoleLog("notice", "[xml_handler] " .. cc_cache_key .. " source: database\n");
end
else
--send to the console
if (debug["cache"]) then
freeswitch.consoleLog("notice", "[xml_handler] " .. cc_cache_key .. " source: cache\n");
end
end --if XML_STRING
--send the xml to the console
if (debug["xml_string"]) then
local file = assert(io.open(temp_dir .. "/callcenter.conf.xml", "w"));
file:write(XML_STRING);
file:close();
end
@@ -0,0 +1,124 @@
-- xml_handler.lua
-- Part of FusionPBX
-- Copyright (C) 2016 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--connect to the database
local Database = require "resources.functions.database";
dbh = Database.new('system');
--include json library
local json
if (debug["sql"]) then
json = require "resources.functions.lunajson"
end
--exits the script if we didn't connect properly
assert(dbh:connected());
--include xml library
local Xml = require "resources.functions.xml";
--set the xml array
local xml = Xml:new();
xml:append([[<?xml version="1.0" encoding="UTF-8" standalone="no"?>]]);
xml:append([[<document type="freeswitch/xml">]]);
xml:append([[ <section name="configuration">]]);
xml:append([[ <configuration name="conference.conf" description="Audio Conference">]]);
--start the conference controls
xml:append([[ <caller-controls>]]);
sql = [[SELECT * FROM v_conference_controls
WHERE control_enabled = 'true' ]];
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[conference_control] SQL: " .. sql .. "\n");
end
dbh:query(sql, function(field)
conference_control_uuid = field["conference_control_uuid"];
xml:append([[ <group name="]] .. xml.sanitize(field["control_name"]) .. [[">]]);
--get the conference control details from the database
sql = [[SELECT * FROM v_conference_control_details
WHERE conference_control_uuid = :conference_control_uuid
AND control_enabled = 'true' ]];
local params = {conference_control_uuid = conference_control_uuid};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[conference_control] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
--conference_control_uuid = row["conference_control_uuid"];
--conference_control_detail_uuid = row["conference_control_detail_uuid"];
xml:append([[ <control digits="]] .. xml.sanitize(row["control_digits"]) .. [[" action="]] .. xml.sanitize(row["control_action"]) .. [[" data="]] .. xml.sanitize(row["control_data"]) .. [["/>]]);
end);
xml:append([[ </group>]]);
end);
xml:append([[ </caller-controls>]]);
--start the conference profiles
xml:append([[ <profiles>]]);
sql = [[SELECT * FROM v_conference_profiles
WHERE profile_enabled = 'true' ]];
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[conference_profiles] SQL: " .. sql .. "\n");
end
dbh:query(sql, function(field)
conference_profile_uuid = field["conference_profile_uuid"];
xml:append([[ <profile name="]] .. xml.sanitize(field["profile_name"]) .. [[">]]);
--get the conference profile parameters from the database
sql = [[SELECT * FROM v_conference_profile_params
WHERE conference_profile_uuid = :conference_profile_uuid
AND profile_param_enabled = 'true' ]];
local params = {conference_profile_uuid = conference_profile_uuid};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[conference_profiles] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
--conference_profile_uuid = row["conference_profile_uuid"];
--conference_profile_param_uuid = row["conference_profile_param_uuid"];
--profile_param_description = row["profile_param_description"];
xml:append([[ <param name="]] .. xml.sanitize(row["profile_param_name"]) .. [[" value="]] .. xml.sanitize(row["profile_param_value"]) .. [["/>]]);
end);
xml:append([[ </profile>]]);
end);
xml:append([[ </profiles>]]);
--set the xml array and then concatenate the array to a string
xml:append([[ </configuration>]]);
xml:append([[ </section>]]);
xml:append([[</document>]]);
XML_STRING = xml:build();
if (debug["xml_string"]) then
freeswitch.consoleLog("notice", "[xml_handler] XML_STRING: " .. XML_STRING .. "\n");
end
--send the xml to the console
if (debug["xml_string"]) then
local file = assert(io.open(temp_dir .."/conference.conf.xml", "w"));
file:write(XML_STRING);
file:close();
end
@@ -0,0 +1,358 @@
-- xml_handler.lua
-- Part of FusionPBX
-- Copyright (C) 2016-2022 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--get the ivr name
ivr_menu_uuid = params:getHeader("Menu-Name");
original_ivr_menu_uuid = ivr_menu_uuid
local log = require "resources.functions.log".ivr_menu
--include xml library
local Xml = require "resources.functions.xml";
--get the cache
local cache = require "resources.functions.cache"
local ivr_menu_cache_key = "configuration:ivr.conf:" .. ivr_menu_uuid
XML_STRING, err = cache.get(ivr_menu_cache_key)
--set the cache
if not XML_STRING then
--log cache error
if (debug["cache"]) then
freeswitch.consoleLog("warning", "[xml_handler] " .. ivr_menu_cache_key .. " can not be get from the cache: " .. tostring(err) .. "\n");
end
--required includes
local Database = require "resources.functions.database"
local Settings = require "resources.functions.lazy_settings"
local json
if (debug["sql"]) then
json = require "resources.functions.lunajson"
end
--start the xml array
local xml = Xml:new();
xml:append([[<?xml version="1.0" encoding="UTF-8" standalone="no"?>]]);
xml:append([[<document type="freeswitch/xml">]]);
xml:append([[ <section name="configuration">]]);
xml:append([[ <configuration name="ivr.conf" description="IVR Menus">]]);
xml:append([[ <menus>]]);
--set the sound prefix
sound_prefix = sounds_dir.."/${default_language}/${default_dialect}/${default_voice}/";
--connect to the database
local dbh = Database.new('system');
--exits the script if we didn't connect properly
assert(dbh:connected());
--get the ivr menu from the database
local sql = [[
with recursive ivr_menus as (
select *
from v_ivr_menus
where ivr_menu_uuid = :ivr_menu_uuid
and ivr_menu_enabled = 'true'
union all
select child.*
from v_ivr_menus as child, ivr_menus as parent
where child.ivr_menu_parent_uuid = parent.ivr_menu_uuid
and child.ivr_menu_enabled = 'true'
)
select * from ivr_menus
]];
local params = {ivr_menu_uuid = ivr_menu_uuid};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[ivr_menu] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
--set the variables
domain_uuid = row["domain_uuid"];
ivr_menu_uuid = row["ivr_menu_uuid"];
ivr_menu_name = row["ivr_menu_name"];
ivr_menu_extension = row["ivr_menu_extension"];
ivr_menu_greet_long = row["ivr_menu_greet_long"];
ivr_menu_greet_short = row["ivr_menu_greet_short"];
ivr_menu_invalid_sound = row["ivr_menu_invalid_sound"];
ivr_menu_exit_sound = row["ivr_menu_exit_sound"];
ivr_menu_pin_number = row["ivr_menu_pin_number"];
ivr_menu_confirm_macro = row["ivr_menu_confirm_macro"];
ivr_menu_confirm_key = row["ivr_menu_confirm_key"];
ivr_menu_tts_engine = row["ivr_menu_tts_engine"];
ivr_menu_tts_voice = row["ivr_menu_tts_voice"];
ivr_menu_confirm_attempts = row["ivr_menu_confirm_attempts"];
ivr_menu_timeout = row["ivr_menu_timeout"];
ivr_menu_exit_app = row["ivr_menu_exit_app"];
ivr_menu_exit_data = row["ivr_menu_exit_data"];
ivr_menu_inter_digit_timeout = row["ivr_menu_inter_digit_timeout"];
ivr_menu_max_failures = row["ivr_menu_max_failures"];
ivr_menu_max_timeouts = row["ivr_menu_max_timeouts"];
ivr_menu_digit_len = row["ivr_menu_digit_len"];
ivr_menu_direct_dial = row["ivr_menu_direct_dial"];
ivr_menu_ringback = row["ivr_menu_ringback"];
ivr_menu_cid_prefix = row["ivr_menu_cid_prefix"];
ivr_menu_description = row["ivr_menu_description"];
--set variables from settings
local settings = Settings.new(dbh, domain_name, domain_uuid)
--direct dial length
local direct_dial_digits_min = settings:get('ivr_menu', 'direct_dial_digits_min', 'numeric') or 2;
local direct_dial_digits_max = settings:get('ivr_menu', 'direct_dial_digits_max', 'numeric') or 11;
--storage path
local storage_type = settings:get('recordings', 'storage_type', 'text')
local storage_path = settings:get('recordings', 'storage_path', 'text')
if (storage_path ~= nil) then
storage_path = storage_path:gsub("${domain_name}", domain_name)
storage_path = storage_path:gsub("${domain_uuid}", domain_uuid)
end
--get the recordings from the database
ivr_menu_greet_long_is_base64 = false;
ivr_menu_greet_short_is_base64 = false;
ivr_menu_invalid_sound_is_base64 = false;
ivr_menu_exit_sound_is_base64 = false;
if (storage_type == "base64") then
--include the file io
local file = require "resources.functions.file"
--connect to db
local dbh = Database.new('system', 'base64/read');
--base path for recordings
local base_path = recordings_dir.."/"..domain_name
--function to get recording to local fs
local function load_record(name)
local path = base_path .. "/" .. name;
local is_base64 = false;
if not file_exists(path) then
local sql = "SELECT recording_base64 FROM v_recordings " ..
"WHERE domain_uuid = :domain_uuid " ..
"AND recording_filename = :name "
local params = {domain_uuid = domain_uuid, name = name};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[ivr_menu] SQL: "..sql.."; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
--save the recording to the file system
if #row.recording_base64 > 32 then
is_base64 = true;
file.write_base64(path, row.recording_base64);
--add the full path and file name
name = path;
end
end);
end
return name, is_base64
end
--greet long
if #ivr_menu_greet_long > 1 then
ivr_menu_greet_long, ivr_menu_greet_long_is_base64 = load_record(ivr_menu_greet_long)
end
--greet short
if #ivr_menu_greet_short > 1 then
ivr_menu_greet_short, ivr_menu_greet_short_is_base64 = load_record(ivr_menu_greet_short)
end
--invalid sound
if #ivr_menu_invalid_sound > 1 then
ivr_menu_invalid_sound, ivr_menu_invalid_sound_is_base64 = load_record(ivr_menu_invalid_sound)
end
--exit sound
if #ivr_menu_exit_sound > 1 then
ivr_menu_exit_sound, ivr_menu_exit_sound_is_base64 = load_record(ivr_menu_exit_sound)
end
dbh:release()
elseif (storage_type == "http_cache") then
--add the path to file name
ivr_menu_greet_long = storage_path.."/"..ivr_menu_greet_long;
ivr_menu_greet_short = storage_path.."/"..ivr_menu_greet_short;
ivr_menu_invalid_sound = storage_path.."/"..ivr_menu_invalid_sound;
ivr_menu_exit_sound = storage_path.."/"..ivr_menu_exit_sound;
end
--greet long
if (not ivr_menu_greet_long_is_base64 and not file_exists(ivr_menu_greet_long)) then
if (file_exists(recordings_dir.."/"..domain_name.."/"..ivr_menu_greet_long)) then
ivr_menu_greet_long = recordings_dir.."/"..domain_name.."/"..ivr_menu_greet_long;
--elseif (file_exists(sounds_dir.."/en/us/callie/8000/"..ivr_menu_greet_long)) then
-- ivr_menu_greet_long = sounds_dir.."/${default_language}/${default_dialect}/${default_voice}/"..ivr_menu_greet_long;
end
end
--greet short
if (string.len(ivr_menu_greet_short) > 1) then
if (not ivr_menu_greet_short_is_base64 and not file_exists(ivr_menu_greet_short)) then
if (file_exists(recordings_dir.."/"..domain_name.."/"..ivr_menu_greet_short)) then
ivr_menu_greet_short = recordings_dir.."/"..domain_name.."/"..ivr_menu_greet_short;
--elseif (file_exists(sounds_dir.."/en/us/callie/8000/"..ivr_menu_greet_short)) then
-- ivr_menu_greet_short = sounds_dir.."/${default_language}/${default_dialect}/${default_voice}/"..ivr_menu_greet_short;
end
end
else
ivr_menu_greet_short = ivr_menu_greet_long;
end
--invalid sound
if (not ivr_menu_invalid_sound_is_base64 and not file_exists(ivr_menu_invalid_sound)) then
if (file_exists(recordings_dir.."/"..domain_name.. "/"..ivr_menu_invalid_sound)) then
ivr_menu_invalid_sound = recordings_dir.."/"..domain_name.."/"..ivr_menu_invalid_sound;
elseif (file_exists(sounds_dir.."/en/us/callie/8000/"..ivr_menu_invalid_sound)) then
ivr_menu_invalid_sound = sounds_dir.."/${default_language}/${default_dialect}/${default_voice}/"..ivr_menu_invalid_sound;
end
end
--exit sound
if (not ivr_menu_exit_sound_is_base64 and not file_exists(ivr_menu_exit_sound)) then
if (file_exists(recordings_dir.."/"..ivr_menu_exit_sound)) then
if (ivr_menu_exit_sound ~= nil and ivr_menu_exit_sound ~= "") then
ivr_menu_exit_sound = recordings_dir.."/"..domain_name.."/"..ivr_menu_exit_sound;
end
elseif (file_exists(sounds_dir.."/en/us/callie/8000/"..ivr_menu_exit_sound)) then
ivr_menu_exit_sound = sounds_dir.."/${default_language}/${default_dialect}/${default_voice}/"..ivr_menu_exit_sound;
end
end
--add xml to the array
xml:append([[ <menu name="]] .. xml.sanitize(ivr_menu_uuid) .. [[" description="]] .. xml.sanitize(ivr_menu_name) .. [[" ]]);
xml:append([[ greet-long="]] .. xml.sanitize(ivr_menu_greet_long) .. [[" ]]);
xml:append([[ greet-short="]] .. xml.sanitize(ivr_menu_greet_short) .. [[" ]]);
xml:append([[ invalid-sound="]] .. xml.sanitize(ivr_menu_invalid_sound) .. [[" ]]);
xml:append([[ exit-sound="]] .. xml.sanitize(ivr_menu_exit_sound) .. [[" ]]);
xml:append([[ pin="]] .. xml.sanitize(ivr_menu_pin_number) .. [[" ]]);
xml:append([[ confirm-macro="]] .. xml.sanitize(ivr_menu_confirm_macro) .. [[" ]]);
xml:append([[ confirm-key="]] .. xml.sanitize(ivr_menu_confirm_key) .. [[" ]]);
xml:append([[ tts-engine="]] .. xml.sanitize(ivr_menu_tts_engine) .. [[" ]]);
xml:append([[ tts-voice="]] .. xml.sanitize(ivr_menu_tts_voice) .. [[" ]]);
xml:append([[ confirm-attempts="]] .. xml.sanitize(ivr_menu_confirm_attempts) .. [[" ]]);
xml:append([[ timeout="]] .. xml.sanitize(ivr_menu_timeout) .. [[" ]]);
xml:append([[ inter-digit-timeout="]] .. xml.sanitize(ivr_menu_inter_digit_timeout) .. [[" ]]);
xml:append([[ max-failures="]] .. xml.sanitize(ivr_menu_max_failures) .. [[" ]]);
xml:append([[ max-timeouts="]] .. xml.sanitize(ivr_menu_max_timeouts) .. [[" ]]);
xml:append([[ digit-len="]] .. xml.sanitize(ivr_menu_digit_len) .. [[" ]]);
xml:append([[ ivr_menu_exit_app="]] .. xml.sanitize(ivr_menu_exit_app) .. [[" ]]);
xml:append([[ ivr_menu_exit_data="]] .. xml.sanitize(ivr_menu_exit_data) .. [[" ]]);
xml:append([[ >]]);
--get the ivr menu options
local sql = [[ SELECT * FROM v_ivr_menu_options WHERE ivr_menu_uuid = :ivr_menu_uuid AND ivr_menu_option_enabled = 'true' ORDER BY ivr_menu_option_order asc ]];
local params = {ivr_menu_uuid = ivr_menu_uuid};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[ivr_menu] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
local direct_dial_exclude = {};
dbh:query(sql, params, function(r)
ivr_menu_option_digits = r.ivr_menu_option_digits
ivr_menu_option_action = r.ivr_menu_option_action
ivr_menu_option_param = r.ivr_menu_option_param
ivr_menu_option_description = r.ivr_menu_option_description
if (#ivr_menu_option_action > 0) then
ivr_menu_option_param = xml.sanitize(ivr_menu_option_param);
ivr_menu_option_param = string.gsub(ivr_menu_option_param, "{accountcode}", "${accountcode}");
xml:append([[ <entry action="]] .. xml.sanitize(ivr_menu_option_action) .. [[" digits="]] .. ivr_menu_option_digits .. [[" param="]] .. ivr_menu_option_param .. [[" description="]] .. xml.sanitize(ivr_menu_option_description) .. [["/>]]);
if (tonumber(ivr_menu_option_digits) and #ivr_menu_option_digits >= tonumber(direct_dial_digits_min)) then
table.insert(direct_dial_exclude, ivr_menu_option_digits);
end
end
end);
--direct dial
if (ivr_menu_direct_dial == "true") then
local negative_lookahead = "";
if (#direct_dial_exclude > 0) then
negative_lookahead = "(?!^("..table.concat(direct_dial_exclude, "|")..")$)";
end
local direct_dial_regex = string.format("/^(%s\\d{%s,%s})$/", negative_lookahead, direct_dial_digits_min, direct_dial_digits_max);
xml:append([[ <entry action="menu-exec-app" digits="]] .. direct_dial_regex .. [[" param="set ${cond(${user_exists id $1 ]] .. xml.sanitize(domain_name) .. [[} == true ? user_exists=true : user_exists=false)}" description="direct dial"/>\n]]);
--xml:append([[ <entry action="menu-exec-app" digits="]] .. xml.sanitize(direct_dial_regex) .. [[" param="set ${cond(${user_exists} == true ? user_exists=true : ivr_max_failures=${system(expr ${ivr_max_failures} + 1)})}" description="increment max failures"/>\n]]);
xml:append([[ <entry action="menu-exec-app" digits="]] .. direct_dial_regex .. [[" param="playback ${cond(${user_exists} == true ? ]] .. xml.sanitize(sound_prefix) .. [[ivr/ivr-call_being_transferred.wav : ]] .. xml.sanitize(sound_prefix) .. [[ivr/ivr-that_was_an_invalid_entry.wav)}" description="play sound"/>\n]]);
--xml:append([[ <entry action="menu-exec-app" digits="]] .. xml.sanitize(direct_dial_regex) .. [[" param="transfer ${cond(${ivr_max_failures} == ]] .. xml.sanitize(ivr_menu_max_failures) .. [[ ? ]] .. xml.sanitize(ivr_menu_exit_data) .. [[)}" description="max fail transfer"/>\n]]);
xml:append([[ <entry action="menu-exec-app" digits="]] .. direct_dial_regex .. [[" param="transfer ${cond(${user_exists} == true ? $1 XML ]] .. xml.sanitize(domain_name) .. [[)}" description="direct dial transfer"/>\n]]);
end
--close the extension tag if it was left open
xml:append([[ </menu>]]);
end);
--add the xml closing tags
xml:append([[ </menus>]]);
xml:append([[ </configuration>]]);
xml:append([[ </section>]]);
xml:append([[</document>]]);
--save the xml into a string
XML_STRING = xml:build();
--optinonal debug message
if (debug["xml_string"]) then
freeswitch.consoleLog("notice", "[xml_handler] XML_STRING: " .. XML_STRING .. "\n");
end
--close the database connection
dbh:release();
--freeswitch.consoleLog("notice", "[xml_handler]"..api:execute("eval ${dsn}"));
--set the cache
local ok, err = cache.set("configuration:ivr.conf:" .. original_ivr_menu_uuid, XML_STRING, expire["ivr"]);
if debug["cache"] then
if ok then
freeswitch.consoleLog("notice", "[xml_handler] " .. ivr_menu_uuid .. " stored in the cache\n");
else
freeswitch.consoleLog("warning", "[xml_handler] " .. ivr_menu_uuid .. " can not be stored in the cache: " .. tostring(err) .. "\n");
end
end
--send the xml to the console
if (debug["xml_string"]) then
local file = assert(io.open(temp_dir .. "/ivr-"..ivr_menu_uuid..".conf.xml", "w"));
file:write(XML_STRING);
file:close();
end
--send to the console
if (debug["cache"]) then
freeswitch.consoleLog("notice", "[xml_handler] " .. ivr_menu_cache_key .. " source: database\n");
end
else
--send to the console
if (debug["cache"]) then
freeswitch.consoleLog("notice", "[xml_handler] " .. ivr_menu_cache_key .. " source: cache\n");
end
end --if XML_STRING
@@ -0,0 +1,152 @@
--include xml library
local Xml = require "resources.functions.xml";
--get the cache
local cache = require "resources.functions.cache"
local local_stream_cache_key = "configuration:local_stream.conf"
XML_STRING, err = cache.get(local_stream_cache_key)
--set the cache
if not XML_STRING then
--log the cache error
if (debug["cache"]) then
freeswitch.consoleLog("warning", "[xml_handler] configuration:local_stream.conf can not be get from cache: " .. tostring(err) .. "\n");
end
--set a default value
if (expire["default"] == nil) then
expire["default"]= "3600";
end
--connect to the database
local Database = require "resources.functions.database";
dbh = Database.new('system');
--include json library
local json
if (debug["sql"]) then
json = require "resources.functions.lunajson"
end
--exits the script if we didn't connect properly
assert(dbh:connected());
--start the xml array
local xml = Xml:new();
xml:append([[<?xml version="1.0" encoding="UTF-8" standalone="no"?>]]);
xml:append([[<document type="freeswitch/xml">]]);
xml:append([[ <section name="configuration">]]);
xml:append([[ <configuration name="local_stream.conf" description="stream files from local dir">]]);
--run the query
sql = "select d.domain_name, s.* "
sql = sql .. "from v_music_on_hold as s left outer join v_domains as d "
sql = sql .. "on d.domain_uuid = s.domain_uuid "
sql = sql .. "order by s.music_on_hold_name asc "
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[xml_handler] SQL: " .. sql .. "\n");
end
x = 0;
dbh:query(sql, function(row)
--combine the name, domain_name and the rate
name = '';
if (row.domain_uuid ~= nil and string.len(row.domain_uuid) > 0) then
name = row.domain_name..'/';
end
name = name .. row.music_on_hold_name;
if (row.music_on_hold_rate ~= nil and #row.music_on_hold_rate > 0) then
name = name .. '/' .. row.music_on_hold_rate;
end
--replace the variable with the path to the sounds directory
music_on_hold_path = row.music_on_hold_path:gsub("$${sounds_dir}", sounds_dir);
--set the rate
rate = row.music_on_hold_rate;
if rate == '' then
rate = '48000';
end
--add the full path to the chime list
chime_list = row.music_on_hold_chime_list;
if (chime_list ~= nil) then
chime_array = explode(",", chime_list);
chime_list = "";
for k,v in pairs(chime_array) do
f = explode("/", v);
if (f[1] ~= nil and f[2] ~= nil and file_exists(sounds_dir.."/en/us/callie/"..f[1].."/"..rate.."/"..f[2])) then
chime_list = chime_list .. sounds_dir.."/en/us/callie/"..v;
else
chime_list = chime_list .. v;
end
end
end
--set the default timer name to soft
if (row.music_on_hold_timer_name == nil or row.music_on_hold_timer_name == '') then
timer_name = "soft";
else
timer_name = row.music_on_hold_timer_name;
end
--build the xml ]]..row.music_on_hold_name..[["
xml:append([[ <directory name="]] .. xml.sanitize(name) .. [[" uuid="]] .. xml.sanitize(row.music_on_hold_uuid) .. [[" path="]] .. xml.sanitize(music_on_hold_path) .. [[">]]);
xml:append([[ <param name="rate" value="]] .. xml.sanitize(rate) .. [["/>]]);
xml:append([[ <param name="shuffle" value="]] .. xml.sanitize(row.music_on_hold_shuffle) .. [["/>]]);
xml:append([[ <param name="channels" value="1"/>]]);
xml:append([[ <param name="interval" value="20"/>]]);
xml:append([[ <param name="timer-name" value="]] .. xml.sanitize(timer_name) ..[["/>]]);
if (chime_list ~= nil) then
xml:append([[ <param name="chime-list" value="]] .. xml.sanitize(chime_list) .. [["/>]]);
end
if (row.music_on_hold_chime_freq ~= nil) then
xml:append([[ <param name="chime-freq" value="]] .. xml.sanitize(row.music_on_hold_chime_freq) .. [["/>]]);
end
if (row.music_on_hold_chime_max ~= nil) then
xml:append([[ <param name="chime-max" value="]] .. xml.sanitize(row.music_on_hold_chime_max) .. [["/>]]);
end
xml:append([[ </directory>]]);
end)
--close the extension tag if it was left open
xml:append([[ </configuration>]]);
xml:append([[ </section>]]);
xml:append([[</document>]]);
XML_STRING = xml:build();
if (debug["xml_string"]) then
freeswitch.consoleLog("notice", "[xml_handler] XML_STRING: " .. XML_STRING .. "\n");
end
--close the database connection
dbh:release();
--set the cache
local ok, err = cache.set(local_stream_cache_key, XML_STRING, expire["default"]);
if debug["cache"] then
if ok then
freeswitch.consoleLog("notice", "[xml_handler] " .. local_stream_cache_key .. " stored in the cache\n");
else
freeswitch.consoleLog("warning", "[xml_handler] " .. local_stream_cache_key .. " can not be stored in the cache: " .. tostring(err) .. "\n");
end
end
--send to the console
if (debug["cache"]) then
freeswitch.consoleLog("notice", "[xml_handler] " .. local_stream_cache_key .. " source: database\n");
end
else
--send to the console
if (debug["cache"]) then
freeswitch.consoleLog("notice", "[xml_handler] " .. local_stream_cache_key .. " source: cache\n");
end
end --if XML_STRING
--send the xml to the console
if (debug["xml_string"]) then
local file = assert(io.open(temp_dir .. "/local_stream.conf.xml", "w"));
file:write(XML_STRING);
file:close();
end
@@ -0,0 +1,333 @@
-- xml_handler.lua
-- Part of FusionPBX
-- Copyright (C) 2013 - 2022 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--include xml library
local Xml = require "resources.functions.xml";
--get the cache
local cache = require "resources.functions.cache"
local hostname = trim(api:execute("switchname", ""))
local sofia_cache_key = "configuration:sofia.conf:" .. hostname
XML_STRING, err = cache.get(sofia_cache_key)
--set the cache
if not XML_STRING then
--log cache error
if (debug["cache"]) then
freeswitch.consoleLog("warning", "[xml_handler] " .. sofia_cache_key .. " can not be get from the cache: " .. tostring(err) .. "\n");
end
--set a default value
if (expire["sofia"] == nil) then
expire["sofia"]= "3600";
end
--connect to the database
local Database = require "resources.functions.database";
dbh = Database.new('system');
--include json library
local json
if (debug["sql"]) then
json = require "resources.functions.lunajson"
end
--exits the script if we didn't connect properly
assert(dbh:connected());
--get the variables
vars = trim(api:execute("global_getvar", ""));
--start the xml array
local xml = Xml:new();
xml:append([[<?xml version="1.0" encoding="UTF-8" standalone="no"?>]]);
xml:append([[<document type="freeswitch/xml">]]);
xml:append([[ <section name="configuration">]]);
xml:append([[ <configuration name="sofia.conf" description="sofia Endpoint">]]);
--gt the global settings
sql = "select * from v_sofia_global_settings ";
sql = sql .. "where global_setting_enabled = 'true' ";
sql = sql .. "order by global_setting_name asc ";
local params = {};
x = 0;
xml:append([[ <global_settings>]]);
dbh:query(sql, params, function(row)
xml:append([[ <param name="]] .. xml.sanitize(row.global_setting_name) .. [[" value="]] .. xml.sanitize(row.global_setting_value) .. [["/>]]);
end)
xml:append([[ </global_settings>]]);
--set defaults
previous_sip_profile_name = "";
profile_tag_status = "closed";
--run the query
sql = "select p.sip_profile_uuid, p.sip_profile_name, p.sip_profile_description, s.sip_profile_setting_name, s.sip_profile_setting_value ";
sql = sql .. "from v_sip_profiles as p, v_sip_profile_settings as s ";
sql = sql .. "where s.sip_profile_setting_enabled = 'true' ";
sql = sql .. "and p.sip_profile_enabled = 'true' ";
sql = sql .. "and (p.sip_profile_hostname = :hostname or p.sip_profile_hostname is null or p.sip_profile_hostname = '') ";
sql = sql .. "and p.sip_profile_uuid = s.sip_profile_uuid ";
sql = sql .. "order by p.sip_profile_name asc ";
local params = {hostname = hostname};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[xml_handler] SQL: " .. sql .. "; params: " .. json.encode(params) .. "\n");
end
x = 0;
xml:append([[ <profiles>]]);
dbh:query(sql, params, function(row)
--set as variables
sip_profile_uuid = row.sip_profile_uuid;
sip_profile_name = row.sip_profile_name;
--sip_profile_description = row.sip_profile_description;
sip_profile_setting_name = row.sip_profile_setting_name;
sip_profile_setting_value = row.sip_profile_setting_value;
--open xml tag
if (sip_profile_name ~= previous_sip_profile_name) then
if (x > 1) then
xml:append([[ </settings>]]);
xml:append([[ </profile>]]);
end
xml:append([[ <profile name="]] .. xml.sanitize(sip_profile_name) .. [[">]]);
xml:append([[ <aliases>]]);
xml:append([[ </aliases>]]);
xml:append([[ <gateways>]]);
--xml:append([[ <X-PRE-PROCESS cmd="include" data="]] .. xml.sanitize(sip_profile_name) .. [[/*.xml"/>]]);
--get the gateways
sql = "select * from v_gateways ";
sql = sql .. "where profile = :profile ";
sql = sql .. "and enabled = 'true' ";
sql = sql .. "and (hostname = :hostname or hostname is null or hostname = '') ";
local params = {profile = sip_profile_name, hostname = hostname};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[xml_handler] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
x = 0;
dbh:query(sql, params, function(field)
xml:append([[ <gateway name="]] .. xml.sanitize(string.lower(field.gateway_uuid)) .. [[">]]);
if (string.len(field.username) > 0) then
xml:append([[ <param name="username" value="]] .. xml.sanitize(field.username) .. [["/>]]);
end
if (string.len(field.distinct_to) > 0) then
xml:append([[ <param name="distinct-to" value="]] .. xml.sanitize(field.distinct_to) .. [["/>]]);
end
if (string.len(field.auth_username) > 0) then
xml:append([[ <param name="auth-username" value="]] .. xml.sanitize(field.auth_username) .. [["/>]]);
end
if (string.len(field.password) > 0) then
xml:append([[ <param name="password" value="]] .. xml.sanitize(field.password) .. [["/>]]);
end
if (string.len(field.realm) > 0) then
xml:append([[ <param name="realm" value="]] .. xml.sanitize(field.realm) .. [["/>]]);
end
if (string.len(field.from_user) > 0) then
xml:append([[ <param name="from-user" value="]] .. xml.sanitize(field.from_user) .. [["/>]]);
end
if (string.len(field.from_domain) > 0) then
xml:append([[ <param name="from-domain" value="]] .. xml.sanitize(field.from_domain) .. [["/>]]);
end
if (string.len(field.proxy) > 0) then
xml:append([[ <param name="proxy" value="]] .. xml.sanitize(field.proxy) .. [["/>]]);
end
if (string.len(field.register_proxy) > 0) then
xml:append([[ <param name="register-proxy" value="]] .. xml.sanitize(field.register_proxy) .. [["/>]]);
end
if (string.len(field.outbound_proxy) > 0) then
xml:append([[ <param name="outbound-proxy" value="]] .. xml.sanitize(field.outbound_proxy) .. [["/>]]);
end
if (string.len(field.expire_seconds) > 0) then
xml:append([[ <param name="expire-seconds" value="]] .. xml.sanitize(field.expire_seconds) .. [["/>]]);
end
if (string.len(field.register) > 0) then
xml:append([[ <param name="register" value="]] .. xml.sanitize(field.register) .. [["/>]]);
end
if (field.register_transport) then
if (field.register_transport == "udp") then
xml:append([[ <param name="register-transport" value="udp"/>]]);
elseif (field.register_transport == "tcp") then
xml:append([[ <param name="register-transport" value="tcp"/>]]);
elseif (field.register_transport == "tls") then
xml:append([[ <param name="register-transport" value="tls"/>]]);
else
xml:append([[ <param name="register-transport" value="udp"/>]]);
end
end
if (field.contact_params) then
xml:append([[ <param name="contact-params" value="]] .. xml.sanitize(field.contact_params) .. [["/>]]);
end
if (string.len(field.retry_seconds) > 0) then
xml:append([[ <param name="retry-seconds" value="]] .. xml.sanitize(field.retry_seconds) .. [["/>]]);
end
if (string.len(field.extension) > 0) then
xml:append([[ <param name="extension" value="]] .. xml.sanitize(field.extension) .. [["/>]]);
end
if (string.len(field.ping) > 0) then
xml:append([[ <param name="ping" value="]] .. xml.sanitize(field.ping) .. [["/>]]);
end
if (string.len(field.ping_min) > 0) then
xml:append([[ <param name="ping-min" value="]] .. xml.sanitize(field.ping_min) .. [["/>]]);
end
if (string.len(field.ping_max) > 0) then
xml:append([[ <param name="ping-max" value="]] .. xml.sanitize(field.ping_max) .. [["/>]]);
end
if (string.len(field.contact_in_ping) > 0) then
xml:append([[ <param name="contact-in-ping" value="]] .. xml.sanitize(field.contact_in_ping) .. [["/>]]);
end
if (string.len(field.context) > 0) then
xml:append([[ <param name="context" value="]] .. xml.sanitize(field.context) .. [["/>]]);
end
if (string.len(field.caller_id_in_from) > 0) then
xml:append([[ <param name="caller-id-in-from" value="]] .. xml.sanitize(field.caller_id_in_from) .. [["/>]]);
end
if (string.len(field.supress_cng) > 0) then
xml:append([[ <param name="supress-cng" value="]] .. xml.sanitize(field.supress_cng) .. [["/>]]);
end
if (string.len(field.extension_in_contact) > 0) then
xml:append([[ <param name="extension-in-contact" value="]] .. xml.sanitize(field.extension_in_contact) .. [["/>]]);
end
xml:append([[ <variables>]]);
if (string.len(field.sip_cid_type) > 0) then
xml:append([[ <variable name="sip_cid_type" value="]] .. xml.sanitize(field.sip_cid_type) .. [["/>]]);
end
xml:append([[ </variables>]]);
xml:append([[ </gateway>]]);
end)
xml:append([[ </gateways>]]);
xml:append([[ <domains>]]);
--add sip profile domain: name, alias, and parse
xml:append([[ <!-- indicator to parse the directory for domains with parse="true" to get gateways-->]]);
xml:append([[ <!--<domain name="$${domain}" parse="true"/>-->]]);
xml:append([[ <!-- indicator to parse the directory for domains with parse="true" to get gateways and alias every domain to this profile -->]]);
xml:append([[ <!--<domain name="all" alias="true" parse="true"/>-->]]);
sql = "SELECT sip_profile_domain_name, sip_profile_domain_alias, sip_profile_domain_parse FROM v_sip_profile_domains ";
sql = sql .. "WHERE sip_profile_uuid = :sip_profile_uuid";
local params = {sip_profile_uuid = sip_profile_uuid};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[xml_handler] SQL: " .. sql .. "; sip_profile_uuid:" .. sip_profile_uuid .. "\n");
end
dbh:query(sql, params, function(row)
name = row.sip_profile_domain_name;
alias = row.sip_profile_domain_alias;
parse = row.sip_profile_domain_parse;
if (name == nil or name == '') then name = 'false'; end
if (alias == nil or alias == '') then alias = 'false'; end
if (parse == nil or parse == '') then parse = 'false'; end
xml:append([[ <domain name="]] .. xml.sanitize(name) .. [[" alias="]] .. xml.sanitize(alias) .. [[" parse="]] .. xml.sanitize(parse) .. [["/>]]);
end);
xml:append([[ </domains>]]);
xml:append([[ <settings>]]);
profile_tag_status = "open";
end
--loop through the var array
for line in (vars.."\n"):gmatch"(.-)\n" do
if (line) then
pos = string.find(line, "=", 0, true);
--name = string.sub( line, 0, pos-1);
--value = string.sub( line, pos+1);
sip_profile_setting_value = sip_profile_setting_value:gsub("%$%${"..string.sub( line, 0, pos-1).."}", string.sub( line, pos+1));
end
end
--remove $ and replace with ""
--if (sip_profile_setting_value) then
-- sip_profile_setting_value = sip_profile_setting_value:gsub("%$", "");
--end
--sanitize the sip profile setting value, allow specific safe variables
sip_profile_setting_value = xml.sanitize(sip_profile_setting_value);
sip_profile_setting_value = string.gsub(sip_profile_setting_value, "{domain_name}", "${domain_name}");
sip_profile_setting_value = string.gsub(sip_profile_setting_value, "{strftime", "${strftime");
sip_profile_setting_value = string.gsub(sip_profile_setting_value, "{uuid}", "${uuid}");
sip_profile_setting_value = string.gsub(sip_profile_setting_value, "{record_ext}", "${record_ext}");
--set the parameters
if (sip_profile_setting_name) then
xml:append([[ <param name="]] .. xml.sanitize(sip_profile_setting_name) .. [[" value="]] .. sip_profile_setting_value .. [["/>]]);
end
--set the previous value
previous_sip_profile_name = sip_profile_name;
--increment the value of x
x = x + 1;
end)
--close the extension tag if it was left open
if (profile_tag_status == "open") then
xml:append([[ </settings>]]);
xml:append([[ </profile>]]);
profile_tag_status = "close";
end
xml:append([[ </profiles>]]);
xml:append([[ </configuration>]]);
xml:append([[ </section>]]);
xml:append([[</document>]]);
XML_STRING = xml:build();
if (debug["xml_string"]) then
freeswitch.consoleLog("notice", "[xml_handler] XML_STRING: " .. XML_STRING .. "\n");
end
--close the database connection
dbh:release();
--set the cache
local ok, err = cache.set(sofia_cache_key, XML_STRING, expire["sofia"])
if debug["cache"] then
if ok then
freeswitch.consoleLog("notice", "[xml_handler] " .. sofia_cache_key .. " stored in the cache\n");
else
freeswitch.consoleLog("warning", "[xml_handler] " .. sofia_cache_key .. " can not be stored in the cache: " .. tostring(err) .. "\n");
end
end
--send to the console
if (debug["cache"]) then
freeswitch.consoleLog("notice", "[xml_handler] " .. sofia_cache_key .. " source: database\n");
end
else
--send to the console
if (debug["cache"]) then
freeswitch.consoleLog("notice", "[xml_handler] " .. sofia_cache_key .. " source: cache\n");
end
end --if XML_STRING
--send the xml to the console
if (debug["xml_string"]) then
local file = assert(io.open(temp_dir .. "/sofia.conf.xml", "w"));
file:write(XML_STRING);
file:close();
end
@@ -0,0 +1,144 @@
-- xml_handler.lua
-- Part of FusionPBX
-- Copyright (C) 2018 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--include xml library
local Xml = require "resources.functions.xml";
--get the cache
local cache = require "resources.functions.cache"
local translate_cache_key = "configuration:translate.conf"
XML_STRING, err = cache.get(translate_cache_key)
--set the cache
if not XML_STRING then
--log cache error
if (debug["cache"]) then
freeswitch.consoleLog("warning", "[xml_handler] " .. translate_cache_key .. " can not be get from the cache: " .. tostring(err) .. "\n");
end
--log cache error
if (debug["cache"]) then
freeswitch.consoleLog("warning", "[xml_handler] configuration:translate.conf can not be get from the cache: " .. tostring(err) .. "\n");
end
--set a default value
if (expire["translate"] == nil) then
expire["translate"]= "3600";
end
--connect to the database
local Database = require "resources.functions.database";
dbh = Database.new('system');
--include json library
local json
if (debug["sql"]) then
json = require "resources.functions.lunajson"
end
--exits the script if we didn't connect properly
assert(dbh:connected());
--start the xml array
local xml = Xml:new();
xml:append([[<?xml version="1.0" encoding="UTF-8" standalone="no"?>]]);
xml:append([[<document type="freeswitch/xml">]]);
xml:append([[ <section name="configuration">]]);
xml:append([[ <configuration name="translate.conf" description="Number Translation Rules" autogenerated="true">]]);
xml:append([[ <profiles>]]);
--run the query
sql = "select * from v_number_translations ";
sql = sql .. "order by number_translation_name asc ";
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[xml_handler] SQL: " .. sql .. "\n");
end
x = 0;
dbh:query(sql, function(row)
--list open tag
xml:append([[ <profile name="]] .. xml.sanitize(row.number_translation_name) .. [[" description="]] .. xml.sanitize(row.number_translation_description) .. [[">]]);
--get the nodes
sql = "select * from v_number_translation_details ";
sql = sql .. "where number_translation_uuid = :number_translation_uuid ";
sql = sql .. "order by number_translation_detail_order asc ";
local params = {number_translation_uuid = row.number_translation_uuid}
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[xml_handler] SQL: " .. sql .. "\n");
end
x = 0;
dbh:query(sql, params, function(field)
if (string.len(field.number_translation_detail_regex) > 0) then
xml:append([[ <rule regex="]] .. xml.sanitize(field.number_translation_detail_regex) .. [[" replace="]] .. xml.sanitize(field.number_translation_detail_replace) .. [[" />]]);
end
end)
--list close tag
xml:append([[ </profile>]]);
end)
--close the extension tag if it was left open
xml:append([[ </profiles>]]);
xml:append([[ </configuration>]]);
xml:append([[ </section>]]);
xml:append([[</document>]]);
XML_STRING = xml:build();
if (debug["xml_string"]) then
freeswitch.consoleLog("notice", "[xml_handler] XML_STRING: " .. XML_STRING .. "\n");
end
--close the database connection
dbh:release();
--set the cache
local ok, err = cache.set(translate_cache_key, XML_STRING, expire["translate"]);
if debug["cache"] then
if ok then
freeswitch.consoleLog("notice", "[xml_handler] " .. translate_cache_key .. " stored in the cache\n");
else
freeswitch.consoleLog("warning", "[xml_handler] " .. translate_cache_key .. " can not be stored in the cache: " .. tostring(err) .. "\n");
end
end
--send to the console
if (debug["cache"]) then
freeswitch.consoleLog("notice", "[xml_handler] " .. translate_cache_key .. " source: database\n");
end
else
--send to the console
if (debug["cache"]) then
freeswitch.consoleLog("notice", "[xml_handler] " .. translate_cache_key .. " source: cache\n");
end
end --if XML_STRING
--send the xml to the console
if (debug["xml_string"]) then
local file = assert(io.open(temp_dir .. "/translate.conf.xml", "w"));
file:write(XML_STRING);
file:close();
end
@@ -0,0 +1,267 @@
-- xml_handler.lua
-- Part of FusionPBX
-- Copyright (C) 2013-2022 Mark J Crane <markjcrane@fusionpbx.com>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
--
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-- POSSIBILITY OF SUCH DAMAGE.
--includes
local cache = require"resources.functions.cache"
local log = require"resources.functions.log"["xml_handler"]
--include xml library
local Xml = require "resources.functions.xml";
--connect to the database
local Database = require "resources.functions.database";
dbh = Database.new('system');
--needed for cli-command xml_locate dialplan
if (call_context == nil) then
call_context = "public";
end
--get the dialplan mode from the cache
dialplan_destination_key = "dialplan:destination";
dialplan_destination, err = cache.get(dialplan_destination_key);
--if not found in the cache then get it from the database
if (err == 'NOT FOUND') then
--get the mode from default settings
sql = "select default_setting_value from v_default_settings "
sql = sql .. "where default_setting_category = 'dialplan' ";
sql = sql .. "and default_setting_subcategory = 'destination' ";
dialplan_destination = dbh:first_value(sql, nil);
if (dialplan_destination) then
local ok, err = cache.set(dialplan_destination_key, dialplan_destination, expire["dialplan"]);
end
--set the default
if (dialplan_destination == nil or dialplan_destination == '') then
dialplan_destination = "destination_number";
end
--send a message to the log
if (debug['cache']) then
log.notice(dialplan_destination_key.." source: database destination: "..dialplan_destination);
end
else
--send a message to the log
if (debug['cache']) then
log.notice(dialplan_destination_key.." source: cache destination: "..dialplan_destination);
end
end
--get the dialplan mode from the cache
dialplan_mode_key = "dialplan:mode";
dialplan_mode, err = cache.get(dialplan_mode_key);
--if not found in the cache then get it from the database
if (err == 'NOT FOUND') then
--get the mode from default settings
sql = "select default_setting_value from v_default_settings "
sql = sql .. "where default_setting_category = 'destinations' ";
sql = sql .. "and default_setting_subcategory = 'dialplan_mode' ";
dialplan_mode = dbh:first_value(sql, nil);
if (dialplan_mode) then
local ok, err = cache.set(dialplan_mode_key, dialplan_mode, expire["dialplan"]);
end
--set the default
if (dialplan_mode == nil or dialplan_mode == '') then
dialplan_mode = "multiple";
end
--send a message to the log
if (debug['cache']) then
log.notice(dialplan_mode_key.." source: database mode: "..dialplan_mode);
end
else
--send a message to the log
if (debug['cache']) then
log.notice(dialplan_mode_key.." source: cache mode: "..dialplan_mode);
end
end
--set the defaults
if (dialplan_mode == nil or dialplan_mode == '') then
dialplan_mode = "multiple";
end
domain_name = 'global';
--get the context
local context_name = call_context;
if (call_context == "public" or string.sub(call_context, 0, 7) == "public@" or string.sub(call_context, -7) == ".public") then
context_name = 'public';
end
--use alternative sip_to_user instead of the default
if (dialplan_destination == '${sip_to_user}' or dialplan_destination == 'sip_to_user') then
destination_number = api:execute("url_decode", sip_to_user);
end
--use alternative sip_req_user instead of the default
if (dialplan_destination == '${sip_req_user}' or dialplan_destination == 'sip_req_user') then
destination_number = api:execute("url_decode", sip_req_user);
end
--set the dialplan cache key
local dialplan_cache_key = "dialplan:" .. call_context;
if (context_name == 'public' and dialplan_mode == "single") then
dialplan_cache_key = "dialplan:" .. call_context .. ":" .. destination_number;
end
--log the dialplan mode and dialplan cache key
freeswitch.consoleLog("notice", "[xml_handler] ".. dialplan_mode .. " key:" .. dialplan_cache_key .. "\n");
--get the cache
XML_STRING, err = cache.get(dialplan_cache_key);
if (debug['cache']) then
if XML_STRING then
log.notice(dialplan_cache_key.." source: cache");
elseif err ~= 'NOT FOUND' then
log.notice("get element from the cache: " .. err);
end
end
--set the cache
if (not XML_STRING) then
--include json library
local json
if (debug["sql"]) then
json = require "resources.functions.lunajson"
end
--exits the script if we didn't connect properly
assert(dbh:connected());
--get the hostname
hostname = trim(api:execute("hostname", ""));
--set the xml array and then concatenate the array to a string
local xml = Xml:new();
xml:append([[<?xml version="1.0" encoding="UTF-8" standalone="no"?>]]);
xml:append([[<document type="freeswitch/xml">]]);
xml:append([[ <section name="dialplan" description="">]]);
xml:append([[ <context name="]] .. xml.sanitize(call_context) .. [[" destination_number="]] .. xml.sanitize(destination_number) .. [[" hostname="]] .. xml.sanitize(hostname) .. [[">]]);
--get the dialplan xml
if (context_name == 'public' and dialplan_mode == 'single') then
sql = "SELECT (SELECT domain_name FROM v_domains WHERE domain_uuid = p.domain_uuid) as domain_name, "
sql = sql .. "(SELECT domain_enabled FROM v_domains WHERE domain_uuid = p.domain_uuid) as domain_enabled, p.dialplan_xml ";
sql = sql .. "FROM v_dialplans AS p ";
sql = sql .. "WHERE ( ";
sql = sql .. " p.dialplan_uuid IN ( ";
sql = sql .. " SELECT dialplan_uuid FROM v_destinations ";
sql = sql .. " WHERE ( ";
sql = sql .. " destination_prefix || destination_area_code || destination_number = :destination_number ";
sql = sql .. " OR destination_trunk_prefix || destination_area_code || destination_number = :destination_number ";
sql = sql .. " OR destination_prefix || destination_number = :destination_number ";
sql = sql .. " OR '+' || destination_prefix || destination_number = :destination_number ";
sql = sql .. " OR '+' || destination_prefix || destination_area_code || destination_number = :destination_number ";
sql = sql .. " OR destination_area_code || destination_number = :destination_number ";
sql = sql .. " OR destination_number = :destination_number ";
sql = sql .. " ) ";
sql = sql .. " ) ";
sql = sql .. " or (p.dialplan_context like '%public%' and p.domain_uuid IS NULL) ";
sql = sql .. ") ";
sql = sql .. "AND (p.hostname = :hostname OR p.hostname IS NULL) ";
sql = sql .. "AND p.dialplan_enabled = 'true' ";
sql = sql .. "ORDER BY p.dialplan_order ASC ";
local params = {destination_number = destination_number, hostname = hostname};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[dialplan] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
if (row.domain_uuid ~= nil) then
domain_name = row.domain_name;
else
xml:append(row.dialplan_xml);
end
if (row.domain_enabled == true) then
xml:append(row.dialplan_xml);
end
end);
if (xml == nil) then
xml:append([[ <extension name="not-found" continue="false" uuid="9913df49-0757-414b-8cf9-bcae2fd81ae7">]]);
xml:append([[ <condition field="" expression="">]]);
xml:append([[ <action application="set" data="call_direction=inbound" inline="true"/>]]);
xml:append([[ <action application="log" data="WARNING [inbound routes] 404 not found ${sip_network_ip}" inline="true"/>]]);
xml:append([[ </condition>]]);
xml:append([[ </extension>]]);
end
else
sql = "select dialplan_xml from v_dialplans as p ";
if (context_name == "public" or string.match(context_name, "@")) then
sql = sql .. "where p.dialplan_context = :call_context ";
else
sql = sql .. "where p.dialplan_context in (:call_context, '${domain_name}', 'global') ";
end
sql = sql .. "and (p.hostname = :hostname or p.hostname is null) ";
sql = sql .. "and p.dialplan_enabled = 'true' ";
sql = sql .. "order by p.dialplan_order asc ";
local params = {call_context = call_context, hostname = hostname};
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[dialplan] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
dbh:query(sql, params, function(row)
xml:append(row.dialplan_xml);
end);
end
--set the xml array and then concatenate the array to a string
xml:append([[ </context>]]);
xml:append([[ </section>]]);
xml:append([[</document>]]);
XML_STRING = xml:build();
--close the database connection
dbh:release();
--set the cache
local ok, err = cache.set(dialplan_cache_key, XML_STRING, expire["dialplan"]);
if debug["cache"] then
if ok then
freeswitch.consoleLog("notice", "[xml_handler] " .. dialplan_cache_key .. " stored in the cache\n");
else
freeswitch.consoleLog("warning", "[xml_handler] " .. dialplan_cache_key .. " can not be stored in the cache: " .. tostring(err) .. "\n");
end
end
--send to the console
if (debug["cache"]) then
freeswitch.consoleLog("notice", "[xml_handler] " .. dialplan_cache_key .. " source: database\n");
end
else
--send to the console
if (debug["cache"]) then
freeswitch.consoleLog("notice", "[xml_handler] " .. dialplan_cache_key .. " source: cache\n");
end
end --if XML_STRING
--send the xml to the console
if (debug["xml_string"]) then
local file = assert(io.open(temp_dir .. "/" .. dialplan_cache_key .. ".xml", "w"));
file:write(XML_STRING);
file:close();
end
@@ -0,0 +1,73 @@
--connect to the database
local Database = require "resources.functions.database"
local log = require "resources.functions.log"["directory_acl"]
local dbh = Database.new('system')
--include xml library
local Xml = require "resources.functions.xml";
--include json library
local json
if (debug["sql"]) then
json = require "resources.functions.lunajson"
end
--build the xml
local xml = Xml:new();
xml:append([[<?xml version="1.0" encoding="UTF-8" standalone="no"?>]])
xml:append([[<document type="freeswitch/xml">]])
xml:append([[ <section name="directory">]])
--process when the sip profile is rescanned, sofia is reloaded, or sip redirect
local sql = "SELECT * FROM v_domains as d, v_extensions as e "
sql = sql .. "where d.domain_uuid = e.domain_uuid and e.cidr is not null and e.cidr <> '' "
if domain_name then
sql = sql .. "and d.domain_name = :domain_name "
else
sql = sql .. "order by d.domain_name"
end
local params = {domain_name = domain_name}
if debug['sql'] then
log.noticef("SQL: %s; params: %s", sql, json.encode(params))
end
local prev_domain_name
dbh:query(sql, params, function(row)
if prev_domain_name ~= row.domain_name then
if prev_domain_name then
xml:append([[ </users>]])
xml:append([[ </group>]])
xml:append([[ </groups>]])
xml:append([[ </domain>]])
end
prev_domain_name = row.domain_name
xml:append([[ <domain name="]] .. xml.sanitize(row.domain_name) .. [[" alias="true">]])
xml:append([[ <groups>]])
xml:append([[ <group name="default">]])
xml:append([[ <users>]])
end
local cidr = (#row.cidr > 0) and (' cidr="' .. xml.sanitize(row.cidr) .. '"') or ''
xml:append([[ <user id="]] .. xml.sanitize(row.extension) .. [["]] .. cidr .. [[/>]])
end)
if prev_domain_name then
xml:append([[ </users>]])
xml:append([[ </group>]])
xml:append([[ </groups>]])
xml:append([[ </domain>]])
end
xml:append([[ </section>]])
xml:append([[</document>]])
XML_STRING = xml:build();
if (debug["xml_string"]) then
log.notice("XML_STRING "..XML_STRING)
end
--close the database connection
dbh:release()
@@ -0,0 +1,104 @@
--connect to the database
local Database = require "resources.functions.database"
local log = require "resources.functions.log"["directory_dir"]
local dbh = Database.new('system')
--include json library
local json
if (debug["sql"]) then
json = require "resources.functions.lunajson"
end
--build the xml
local xml = Xml:new();
xml:append([[<?xml version="1.0" encoding="UTF-8" standalone="no"?>]])
xml:append([[<document type="freeswitch/xml">]])
xml:append([[ <section name="directory">]])
--process when the sip profile is rescanned, sofia is reloaded, or sip redirect
local sql = "SELECT * FROM v_domains as d, v_extensions as e "
sql = sql .. "where d.domain_uuid = e.domain_uuid "
sql = sql .. "and (e.directory_visible = 'true' or e.directory_exten_visible='true') "
if domain_name then
sql = sql .. "and d.domain_name = :domain_name "
else
sql = sql .. "order by d.domain_name "
end
local sql_params = {domain_name = domain_name}
if debug['sql'] then
log.noticef("SQL: %s; params: %s", sql, json.encode(sql_params))
end
-- export this params
local params = {
directory_visible = "directory-visible";
directory_exten_visible = "directory-exten-visible";
}
-- export this variables
local variables = {
effective_caller_id_name = "effective_caller_id_name";
directory_full_name = "directory_full_name";
}
local prev_domain_name
dbh:query(sql, sql_params, function(row)
if prev_domain_name ~= row.domain_name then
if prev_domain_name then
xml:append([[ </users>]])
xml:append([[ </group>]])
xml:append([[ </groups>]])
xml:append([[ </domain>]])
end
prev_domain_name = row.domain_name
xml:append([[ <domain name="]] .. xml.sanitize(row.domain_name) .. [[" alias="true">]])
xml:append([[ <groups>]])
xml:append([[ <group name="default">]])
xml:append([[ <users>]])
end
row.sip_from_user = row.extension
row.sip_from_number = (#number_alias > 0) and number_alias or row.extension
local number_alias_string = ''
if #row.number_alias > 0 then
number_alias_string = ' number-alias="' .. xml.sanitize(row.number_alias) .. '"'
end
xml:append([[ <user id="]] .. xml.sanitize(row.extension) .. [["]] .. number_alias_string .. [[>]]);
xml:append([[ <params>]])
for name, param in pairs(params) do
if row[name] and #row[name] > 0 then
xml:append([[ <param name="]] .. xml.sanitize(param) .. [[" value="]] .. xml.sanitize(row[name]) .. [["/>]])
end
end
xml:append([[ </params>]])
xml:append([[ <variables>]])
for name, param in pairs(variables) do
if row[name] and #row[name] > 0 then
xml:append([[ <variable name="]] .. xml.sanitize(param) .. [[" value="]] .. xml.sanitize(row[name]) .. [["/>]])
end
end
xml:append([[ </variables>]])
xml:append([[ </user>]])
end)
if prev_domain_name then
xml:append([[ </users>]])
xml:append([[ </group>]])
xml:append([[ </groups>]])
xml:append([[ </domain>]])
end
xml:append([[ </section>]])
xml:append([[</document>]])
XML_STRING = xml:build();
if (debug["xml_string"]) then
log.notice("XML_STRING "..XML_STRING)
end
--close the database connection
dbh:release()

Some files were not shown because too many files have changed in this diff Show More