Add a voicemail deletion queue (#7221)

* Add a deleted messages option to voicemail.
Messages are only deleted after a certain amount of time instead of immediately.
The queue can be turned off or on with the default setting "use_deletion_queue" in the "voicemail" category.
Changed deleted phrase and added a deleted messages count phrase
Added a program to delete messages that are due for deletion. Also small changes to phrases.

* Added a remove_deleted_messages function that runs on voicemail main menu log in. With this method, the deletion queue is handled per mailbox vs system-wide as in the cron-triggered script. It also allows us to adjust the retention hours on a per-domain basis.
This commit is contained in:
simplecoder732 2025-01-28 18:57:50 -05:00 committed by GitHub
parent b2349060b5
commit 77f9161408
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 356 additions and 10 deletions

View File

@ -104,6 +104,25 @@
</input>
</macro>
<macro name="voicemail_deleted_message_count">
<input pattern="^(1):(.*)$" break_on_match="true">
<match>
<action function="play-file" data="voicemail/vm-you_have.wav"/>
<action function="say" data="$1" method="pronounced" type="items"/>
<action function="play-file" data="voicemail/vm-$2.wav"/>
<action function="play-file" data="voicemail/vm-message.wav"/>
</match>
</input>
<input pattern="^(\d+):(.*)$">
<match>
<action function="play-file" data="voicemail/vm-you_have.wav"/>
<action function="say" data="$1" method="pronounced" type="items"/>
<action function="play-file" data="voicemail/vm-$2.wav"/>
<action function="play-file" data="voicemail/vm-messages.wav"/>
</match>
</input>
</macro>
<macro name="voicemail_menu">
<input pattern="^([0-9#*]):([0-9#*]):([0-9#*]):([0-9#*])$">
<match>

View File

@ -65,6 +65,31 @@
<action function="say" data="$7" method="pronounced" type="name_spelled"/>
</match>
</input>
<input pattern="^deleted:([0-9#*]):([0-9#*]):([0-9#*]):([0-9#*]):([0-9#*]):([0-9#*]):([0-9#*]):(.*)$">
<match>
<action function="play-file" data="voicemail/vm-listen_to_recording.wav"/>
<action function="play-file" data="voicemail/vm-press.wav"/>
<action function="say" data="$1" method="pronounced" type="name_spelled"/>
<action function="play-file" data="voicemail/vm-undelete_message.wav"/>
<action function="play-file" data="voicemail/vm-press.wav"/>
<action function="say" data="$2" method="pronounced" type="name_spelled"/>
<action function="play-file" data="voicemail/vm-message_envelope.wav"/>
<action function="play-file" data="voicemail/vm-press.wav"/>
<action function="say" data="$3" method="pronounced" type="name_spelled"/>
<action function="play-file" data="voicemail/vm-return_call.wav"/>
<action function="play-file" data="voicemail/vm-press.wav"/>
<action function="say" data="$4" method="pronounced" type="name_spelled"/>
<action function="play-file" data="voicemail/vm-delete_recording.wav"/>
<action function="play-file" data="voicemail/vm-press.wav"/>
<action function="say" data="$5" method="pronounced" type="name_spelled"/>
<action function="play-file" data="voicemail/vm-to_forward.wav"/>
<action function="play-file" data="voicemail/vm-press.wav"/>
<action function="say" data="$6" method="pronounced" type="name_spelled"/>
<action function="play-file" data="voicemail/vm-forward_to_email.wav"/>
<action function="play-file" data="voicemail/vm-press.wav"/>
<action function="say" data="$7" method="pronounced" type="name_spelled"/>
</match>
</input>
</macro>
<!-- copied from vm/voicemail_ivr.xml, as not functioning there -->
@ -96,6 +121,17 @@
<action function="execute" data="sleep(100)"/>
</match>
</input>
<input pattern="^deleted:([0-9#*])$">
<match>
<!-- Deleted messages -->
<action function="say" data="4" method="pronounced" type="name_spelled"/>
<action function="play-file" data="voicemail/vm-deleted.wav"/>
<action function="play-file" data="voicemail/vm-messages_alt.wav"/>
<action function="play-file" data="voicemail/vm-press.wav"/>
<action function="say" data="$1" method="pronounced" type="name_spelled"/>
<action function="execute" data="sleep(100)"/>
</match>
</input>
<input pattern="^advanced:([0-9#*])$">
<match>
<!-- For advanced options -->

View File

@ -246,6 +246,22 @@
end
end
use_deletion_queue = 'false';
if (settings['voicemail']['use_deletion_queue'] ~= nil) then
if (settings['voicemail']['use_deletion_queue']['boolean'] ~= nil) then
use_deletion_queue = settings['voicemail']['use_deletion_queue']['boolean'];
end
end
deletion_queue_retention_hours = "24";
if (settings['voicemail'] ~= nil) then
if (settings['voicemail']['deletion_queue_retention_hours'] ~= nil) then
if (settings['voicemail']['deletion_queue_retention_hours']['numeric'] ~= nil) then
deletion_queue_retention_hours = settings['voicemail']['deletion_queue_retention_hours']['numeric'];
end
end
end
end
if (settings['voicemail']) then
@ -363,6 +379,7 @@
require "app.voicemail.resources.functions.mwi_notify";
require "app.voicemail.resources.functions.blf_notify";
require "app.voicemail.resources.functions.tutorial";
require "app.voicemail.resources.functions.remove_deleted_messages";
--send a message waiting event
if (voicemail_action == "mwi") then

View File

@ -233,9 +233,13 @@
--post listen options
if (session:ready()) then
if (string.len(dtmf_digits) == 0) then
if (use_deletion_queue == "true" and message_status == "deleted") then
dtmf_digits = session:playAndGetDigits(1, 1, max_tries, digit_timeout, "#", "phrase:voicemail_listen_file_options:deleted:1:2:3:5:7:8:9:0", "", "^[\\d\\*#]$");
else
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
end
--wait for more digits
--if (session:ready()) then
@ -264,7 +268,12 @@
message_saved(voicemail_id, uuid);
return_call(caller_id_number);
elseif (dtmf_digits == "7") then
if (use_deletion_queue == "true" and message_status ~= "deleted") then
message_saved(voicemail_id, uuid, "deleted");
session:execute("playback", "phrase:voicemail_ack:deleted");
else
delete_recording(voicemail_id, uuid);
end
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

View File

@ -38,6 +38,11 @@
session:execute("sleep", "1000");
end
--remove deleted messages in queue
if (use_deletion_queue == "true") then
remove_deleted_messages(voicemail_id);
end
--new voicemail count
if (session:ready()) then
local sql = [[SELECT count(*) as new_messages FROM v_voicemail_messages
@ -70,6 +75,24 @@
dtmf_digits = session:playAndGetDigits(0, 1, 1, 300, "#", "phrase:voicemail_saved_message_count:" .. saved_messages .. ":saved", "", "\\d+");
end
end
--deleted messages
if (session:ready()) then
deleted_messages = 0;
if (string.len(dtmf_digits) == 0 and use_deletion_queue == "true") then
sql = [[SELECT count(*) as deleted_messages FROM v_voicemail_messages
WHERE domain_uuid = :domain_uuid
AND voicemail_uuid = :voicemail_uuid
AND message_status = 'deleted' ]];
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)
deleted_messages = row["deleted_messages"];
end);
dtmf_digits = session:playAndGetDigits(0, 1, 1, 300, "#", "phrase:voicemail_deleted_message_count:" .. deleted_messages .. ":deleted", "", "\\d+");
end
end
--to listen to new message
if (session:ready() and new_messages ~= '0') then
@ -83,6 +106,12 @@
dtmf_digits = session:playAndGetDigits(0, 1, 1, 100, "#", "phrase:voicemail_main_menu:saved:2", "", "\\d+");
end
end
--deleted messages
if (session:ready() and deleted_messages ~= '0') then
if (string.len(dtmf_digits) == 0) then
dtmf_digits = session:playAndGetDigits(0, 1, 1, 100, "#", "phrase:voicemail_main_menu:deleted:3", "", "\\d+");
end
end
--for advanced options
if (session:ready()) then
if (string.len(dtmf_digits) == 0) then
@ -101,6 +130,8 @@
menu_messages("new");
elseif (dtmf_digits == "2") then
menu_messages("saved");
elseif (dtmf_digits == "3" and use_deletion_queue == "true") then
menu_messages("deleted");
elseif (dtmf_digits == "5") then
timeouts = 0;
advanced();

View File

@ -38,7 +38,7 @@
--session:flushDigits();
--set the message number
message_number = 0;
--message_status new,saved
--message_status new,any
if (session:ready()) then
if (voicemail_id ~= nil) then
--get the voicemail_id
@ -58,8 +58,8 @@
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' ]];
else
sql = sql .. "AND message_status = '" .. message_status .. "' ";
end
sql = sql .. [[ORDER BY created_epoch ]]..message_order;
local params = {domain_uuid = domain_uuid, voicemail_uuid = voicemail_uuid};

View File

@ -24,11 +24,15 @@
-- POSSIBILITY OF SUCH DAMAGE.
--save the message
function message_saved(voicemail_id, uuid)
function message_saved(voicemail_id, uuid, status)
--clear the dtmf
dtmf_digits = '';
--flush dtmf digits from the input buffer
session:flushDigits();
--set default status
if (status == nil) then
status = 'saved';
end
--get the voicemail_uuid
local sql = [[SELECT * FROM v_voicemails
WHERE domain_uuid = :domain_uuid
@ -38,18 +42,20 @@
db_voicemail_uuid = row["voicemail_uuid"];
end);
--delete from the database
sql = [[UPDATE v_voicemail_messages SET message_status = 'saved'
sql = [[UPDATE v_voicemail_messages
SET message_status = :status,
update_date = now()
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};
params = {status = status, 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");
freeswitch.consoleLog("notice", "[voicemail]["..status.."] id: " .. voicemail_id .. " message: "..uuid.."\n");
end
--check the message waiting status
message_waiting(voicemail_id, domain_uuid);

View File

@ -0,0 +1,92 @@
-- Part of FusionPBX
-- Copyright (C) 2013-2025
-- 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 all deleted messages for a single mailbox
function remove_deleted_messages(voicemail_id)
--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);
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
--get messages
local sql = [[SELECT * FROM v_voicemail_messages
WHERE message_status = 'deleted' ]]
sql = sql .. "AND (update_date + interval '" .. deletion_queue_retention_hours .. " hours') < now() "
sql = sql .. [[AND voicemail_uuid = :voicemail_uuid
AND domain_uuid = :domain_uuid]];
local params = {voicemail_uuid = db_voicemail_uuid, domain_uuid = domain_uuid}
messages_to_delete = {};
dbh:query(sql, params, function(row)
table.insert(messages_to_delete, row);
end);
if (debug["sql"]) then
freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n");
end
--flush dtmf digits from the input buffer
session:flushDigits();
total_messages = #messages_to_delete;
message_number = 1;
while message_number <= total_messages do
local message_row = messages_to_delete[message_number];
local uuid = message_row["voicemail_message_uuid"];
--delete the file
os.remove(voicemail_dir.."/"..voicemail_id.."/intro_msg_"..uuid.."."..vm_message_ext);
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
end
--clear the variable
db_voicemail_uuid = '';
messages_to_delete = {};
--flush dtmf digits from the input buffer
session:flushDigits();
end

View File

@ -0,0 +1,120 @@
-- Part of FusionPBX
-- Copyright (C) 2013-2025 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
Database = require "resources.functions.database";
dbh = Database.new('system');
--get settings
require "resources.functions.settings";
settings = settings();
--set deletion_queue_retention_hours
if (settings['voicemail'] ~= nil) then
if (settings['voicemail']['deletion_queue_retention_hours'] ~= nil) then
if (settings['voicemail']['deletion_queue_retention_hours']['numeric'] ~= nil) then
retention_hours = settings['voicemail']['deletion_queue_retention_hours']['numeric'];
else
retention_hours = "24";
end
end
end
--set the voicemail_dir
if (settings['switch'] ~= nil) then
if (settings['switch']['voicemail'] ~= nil) then
if (settings['switch']['voicemail']['dir'] ~= nil) then
voicemail_dir = settings['switch']['voicemail']['dir'].."/default";
end
end
end
--get the voicemail extension
sql = "SELECT * FROM v_vars WHERE var_category = 'Defaults' AND var_name = 'vm_message_ext' AND var_enabled = 'true'";
dbh:query(sql, function(row)
vm_message_ext = row["var_value"];
end);
if (vm_message_ext == nil) then
vm_message_ext = "wav";
end
--get messages
sql = "SELECT * FROM v_voicemail_messages WHERE message_status = 'deleted' AND (update_date + interval '" .. retention_hours .. " hours') < now()";
messages_to_delete = {};
dbh:query(sql, function(row)
table.insert(messages_to_delete, row);
end);
--delete the messages
total_messages = #messages_to_delete;
message_number = 1;
while message_number <= total_messages do
local message_row = messages_to_delete[message_number];
local uuid = message_row["voicemail_message_uuid"];
--get domain_name
sql = [[SELECT * from v_domains
WHERE domain_uuid = :domain_uuid
]];
local params = {domain_uuid = message_row["domain_uuid"]};
dbh:query(sql, params, function(row)
domain_name = row["domain_name"];
end);
--get voicemail_id
sql = [[SELECT * from v_voicemails
WHERE domain_uuid = :domain_uuid
AND voicemail_uuid = :voicemail_uuid
]];
local params = {domain_uuid = message_row["domain_uuid"], voicemail_uuid = message_row["voicemail_uuid"]};
dbh:query(sql, params, function(row)
voicemail_id = row["voicemail_id"];
end);
--delete the file
os.remove(voicemail_dir.."/"..domain_name.."/"..voicemail_id.."/intro_msg_"..uuid.."."..vm_message_ext);
os.remove(voicemail_dir.."/"..domain_name.."/"..voicemail_id.."/intro_"..uuid.."."..vm_message_ext);
os.remove(voicemail_dir.."/"..domain_name.."/"..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]];
local params = {
domain_uuid = message_row["domain_uuid"],
voicemail_uuid = message_row["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
end

View File

@ -401,6 +401,22 @@
$apps[$x]['default_settings'][$y]['default_setting_value'] = "90";
$apps[$x]['default_settings'][$y]['default_setting_enabled'] = "true";
$apps[$x]['default_settings'][$y]['default_setting_description'] = "Number of days maintenance application will retain files.";
$y++;
$apps[$x]['default_settings'][$y]['default_setting_uuid'] = "79d05433-a7ab-4641-ae5d-6eb7810eb560";
$apps[$x]['default_settings'][$y]['default_setting_category'] = "voicemail";
$apps[$x]['default_settings'][$y]['default_setting_subcategory'] = "use_deletion_queue";
$apps[$x]['default_settings'][$y]['default_setting_name'] = "boolean";
$apps[$x]['default_settings'][$y]['default_setting_value'] = "false";
$apps[$x]['default_settings'][$y]['default_setting_enabled'] = "false";
$apps[$x]['default_settings'][$y]['default_setting_description'] = "Instead of deleting voicemails right away when pressing 7; queue them for deletion";
$y++;
$apps[$x]['default_settings'][$y]['default_setting_uuid'] = "b06cc9df-379e-4b45-8bda-d2d431506317";
$apps[$x]['default_settings'][$y]['default_setting_category'] = "voicemail";
$apps[$x]['default_settings'][$y]['default_setting_subcategory'] = "deletion_queue_retention_hours";
$apps[$x]['default_settings'][$y]['default_setting_name'] = "numeric";
$apps[$x]['default_settings'][$y]['default_setting_value'] = "24";
$apps[$x]['default_settings'][$y]['default_setting_enabled'] = "true";
$apps[$x]['default_settings'][$y]['default_setting_description'] = "Number of hours the voicemail deletion queue will retain deleted voicemails";
//schema details
$y=0;
$apps[$x]['db'][$y]['table']['name'] = "v_voicemails";