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
+37
View File
@@ -0,0 +1,37 @@
<?php
/*
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) 2008-2023
the Initial Developer. All Rights Reserved.
Contributor(s):
Mark J Crane <markjcrane@fusionpbx.com>
*/
//process this only one time
if ($domains_processed == 1) {
//includes files
require 'app/switch/resources/classes/scripts.php';
$obj = new scripts;
$obj->copy_files();
}
?>
+126
View File
@@ -0,0 +1,126 @@
<?php
/*
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) 2008-2023
the Initial Developer. All Rights Reserved.
Contributor(s):
Mark J Crane <markjcrane@fusionpbx.com>
*/
/**
* switch class provides methods for copying scripts
*
* @method string correct_path
* @method string copy_files
* @method string write_config
*/
if (!class_exists('scripts')) {
class scripts {
/**
* Called when the object is created
*/
public function __construct() {
}
/**
* Corrects the path for specifically for windows
*/
private function correct_path($path) {
global $IS_WINDOWS;
if ($IS_WINDOWS == null) {
if (stristr(PHP_OS, 'WIN')) { $IS_WINDOWS = true; } else { $IS_WINDOWS = false; }
}
if ($IS_WINDOWS) {
return str_replace('\\', '/', $path);
}
return $path;
}
/**
* Copy the switch scripts to the switch directory
*/
public function copy_files() {
//include files
require dirname(__DIR__, 4) . "/resources/require.php";
//get the source directory
if (file_exists('/usr/share/examples/fusionpbx/scripts')) {
$source_directory = '/usr/share/examples/fusionpbx/scripts';
}
elseif (file_exists('/usr/local/www/fusionpbx/app/switch/resources/scripts')) {
$source_directory = '/usr/local/www/fusionpbx/app/switch/resources/scripts';
}
elseif (file_exists('/var/www/fusionpbx/app/switch/resources/scripts')) {
$source_directory = '/usr/local/share/freeswitch/scripts';
}
else {
$source_directory = $_SERVER["DOCUMENT_ROOT"].PROJECT_PATH.'/app/switch/resources/scripts';
}
//get the destination directory
if (file_exists($conf['switch.scripts.dir'])) {
$destination_directory = $conf['switch.scripts.dir'];
}
elseif (file_exists('/usr/share/examples/fusionpbx/scripts')) {
$destination_directory = '/usr/share/examples/fusionpbx/scripts';
}
elseif (file_exists('/usr/local/share/freeswitch/scripts')) {
$destination_directory = '/usr/local/share/freeswitch/scripts';
}
elseif (file_exists('/usr/local/freeswitch/scripts')) {
$destination_directory = '/usr/local/freeswitch/scripts';
}
//copy the scripts directory
if (!empty($source_directory) && is_readable($source_directory)) {
//copy the main scripts
recursive_copy($source_directory, $destination_directory);
unset($source_directory);
//copy the app/*/resource/install/scripts
$app_scripts = glob($_SERVER["DOCUMENT_ROOT"].PROJECT_PATH.'app/*/resource/scripts');
foreach ($app_scripts as $app_script) {
recursive_copy($app_script, $destination_directory);
}
unset($app_scripts);
}
else {
throw new Exception("Cannot read from '$source_directory' to get the scripts");
}
chmod($destination_directory, 0775);
unset($destination_directory);
}
}
}
/*
//example use
//update config.lua
$obj = new switch;
$obj->copy_files();
*/
?>
+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

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