2013-07-24 19:54:17 +00:00
-- Part of FusionPBX
2019-02-02 18:00:59 -07:00
-- Copyright (C) 2010-2019 Mark J Crane <markjcrane@fusionpbx.com>
2013-07-24 19:54:17 +00:00
-- 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,
2014-07-13 09:24:51 +00:00
-- this list of conditions and the following disclaimer.
2013-07-24 19:54:17 +00:00
--
2018-02-08 11:33:08 -07:00
-- 2. Redistributions in binary form must reproduce the above copyright
2014-07-13 09:24:51 +00:00
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
2013-07-24 19:54:17 +00:00
--
2015-09-27 00:52:47 -06:00
-- THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
2013-07-24 19:54:17 +00:00
-- 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.
2013-12-04 08:14:40 +00:00
--
-- Contributor(s):
-- Mark J Crane <markjcrane@fusionpbx.com>
2017-07-21 18:05:08 -06:00
--include the log
2018-02-10 15:48:18 -07:00
log = require "resources.functions.log" . ring_group
2015-09-22 19:33:41 +04:00
2013-07-24 19:54:17 +00:00
--connect to the database
2016-11-22 21:09:25 +03:00
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
2013-07-24 19:54:17 +00:00
--include functions
2015-08-11 05:06:33 +03:00
require "resources.functions.trim" ;
require "resources.functions.explode" ;
2015-09-15 08:49:37 -06:00
require "resources.functions.base64" ;
require "resources.functions.file_exists" ;
2015-10-20 15:15:42 +03:00
require "resources.functions.channel_utils"
2016-01-28 15:47:31 +00:00
require "resources.functions.format_ringback"
2013-07-24 19:54:17 +00:00
2018-02-11 10:22:25 -07:00
--- include libs
local route_to_bridge = require "resources.functions.route_to_bridge"
local play_file = require "resources.functions.play_file"
2018-02-10 15:48:18 -07:00
2017-07-21 18:05:08 -06:00
--define the session hangup
function session_hangup_hook ()
--send info to the log
--freeswitch.consoleLog("notice","[ring_groups] originate_disposition: " .. session:getVariable("originate_disposition") .. "\n");
2017-07-29 23:06:43 -06:00
--status
status = 'answered'
2017-07-21 18:05:08 -06:00
--run the missed called function
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"
or session : getVariable ( "originate_disposition" ) == "ORIGINATOR_CANCEL"
) then
2017-07-29 23:06:43 -06:00
--set the status
status = 'missed'
2017-07-21 18:05:08 -06:00
--send missed call notification
missed ();
end
2017-07-29 23:06:43 -06:00
--send the ring group event
event = freeswitch.Event ( "CUSTOM" , "RING_GROUPS" );
event : addHeader ( "domain_uuid" , domain_uuid );
event : addHeader ( "domain_name" , domain_name );
event : addHeader ( "ring_group_uuid" , ring_group_uuid );
event : addHeader ( "user_uuid" , user_uuid );
event : addHeader ( "ring_group_name" , ring_group_name );
event : addHeader ( "ring_group_extension" , ring_group_extension );
event : addHeader ( "status" , status );
event : addHeader ( "call_uuid" , uuid );
event : addHeader ( "caller_id_name" , caller_id_name );
event : addHeader ( "caller_id_number" , caller_id_number );
event : fire ();
2017-07-21 18:05:08 -06:00
end
2017-11-29 10:20:45 +03:00
--define iterator function to iterate over key/value pairs in string
local function split_vars_pairs ( str )
local last_pos = 1
return function ()
-- end of string
if not str then return end
-- handle case when there exists comma after kv pair
local action , next_pos = string.match ( str , "([^=]+=%b''),()" , last_pos )
if not action then
action , next_pos = string.match ( str , "([^=]+=[^'][^,]-),()" , last_pos )
if not action then
action , next_pos = string.match ( str , "([^=]+=),()" , last_pos )
end
end
if action then
last_pos = next_pos
return action
end
-- last kv pair may not have comma after it
if last_pos < # str then
action = string.match ( str , "([^=]+=%b'')$" , last_pos )
if not action then
action = string.match ( str , "([^=]+=[^,]-)$" , last_pos )
end
str = nil -- end of iteration
end
return action
end
end
2017-07-21 18:05:08 -06:00
--set the hangup hook function
if ( session : ready ()) then
session : setHangupHook ( "session_hangup_hook" );
end
2013-07-24 19:54:17 +00:00
--get the variables
2017-07-21 18:05:08 -06:00
if ( session : ready ()) then
session : setAutoHangup ( false );
ring_group_uuid = session : getVariable ( "ring_group_uuid" );
recordings_dir = session : getVariable ( "recordings_dir" );
sounds_dir = session : getVariable ( "sounds_dir" );
username = session : getVariable ( "username" );
dialplan = session : getVariable ( "dialplan" );
caller_id_name = session : getVariable ( "caller_id_name" );
caller_id_number = session : getVariable ( "caller_id_number" );
2019-06-01 15:07:49 -06:00
effective_caller_id_name = session : getVariable ( "effective_caller_id_name" );
effective_caller_id_number = session : getVariable ( "effective_caller_id_number" );
2017-07-21 18:05:08 -06:00
network_addr = session : getVariable ( "network_addr" );
ani = session : getVariable ( "ani" );
aniii = session : getVariable ( "aniii" );
rdnis = session : getVariable ( "rdnis" );
destination_number = session : getVariable ( "destination_number" );
source = session : getVariable ( "source" );
uuid = session : getVariable ( "uuid" );
context = session : getVariable ( "context" );
call_direction = session : getVariable ( "call_direction" );
2017-11-17 17:15:47 +03:00
accountcode = session : getVariable ( "accountcode" );
2019-02-20 08:34:18 -07:00
local_ip_v4 = session : getVariable ( "local_ip_v4" )
2017-07-21 18:05:08 -06:00
end
2013-07-24 19:54:17 +00:00
2019-06-01 15:07:49 -06:00
--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
2016-03-10 18:10:49 -07:00
--default to local if nil
if ( call_direction == nil ) then
call_direction = "local" ;
end
2017-02-14 15:01:59 -07:00
--set ring ready
if ( session : ready ()) then
session : execute ( "ring_ready" , "" );
end
2013-07-24 19:54:17 +00:00
--define additional variables
uuids = "" ;
external = "false" ;
--set the sounds path for the language, dialect and voice
2017-07-21 18:05:08 -06:00
if ( session : ready ()) then
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
2013-07-24 19:54:17 +00:00
2014-08-14 01:21:55 +00:00
--get record_ext
2015-08-18 15:38:41 +04:00
record_ext = session : getVariable ( "record_ext" );
if ( not record_ext ) then
2014-08-14 01:21:55 +00:00
record_ext = "wav" ;
end
2013-07-24 19:54:17 +00:00
--prepare the api object
api = freeswitch.API ();
--define the session hangup
--function on_hangup(s,status)
-- freeswitch.consoleLog("NOTICE","---- on_hangup: "..status.."\n");
-- error();
--end
2017-11-17 17:15:47 +03:00
--get current switchname
hostname = trim ( api : execute ( "switchname" , "" ))
2013-08-16 20:36:52 +00:00
--get the ring group
2019-02-20 08:34:18 -07:00
ring_group_forward_enabled = '' ;
ring_group_forward_destination = '' ;
2019-02-12 18:22:35 -07:00
sql = "SELECT d.domain_name, r.* FROM v_ring_groups as r, v_domains as d " ;
2017-07-19 23:06:41 -06:00
sql = sql .. "where r.ring_group_uuid = :ring_group_uuid " ;
2019-02-12 18:22:35 -07:00
sql = sql .. "and r.domain_uuid = d.domain_uuid " ;
local params = { ring_group_uuid = ring_group_uuid };
2016-11-22 21:09:25 +03:00
status = dbh : query ( sql , params , function ( row )
2019-02-12 18:22:35 -07:00
domain_uuid = row [ "domain_uuid" ];
domain_name = row [ "domain_name" ];
2015-06-20 03:43:38 +00:00
ring_group_name = row [ "ring_group_name" ];
2015-09-27 00:52:47 -06:00
ring_group_extension = row [ "ring_group_extension" ];
2018-02-10 15:48:18 -07:00
ring_group_greeting = row [ "ring_group_greeting" ];
2013-08-29 16:57:36 +00:00
ring_group_forward_enabled = row [ "ring_group_forward_enabled" ];
ring_group_forward_destination = row [ "ring_group_forward_destination" ];
2017-04-19 16:12:14 -04:00
ring_group_forward_toll_allow = row [ "ring_group_forward_toll_allow" ];
2018-05-07 19:59:41 -06:00
ring_group_call_timeout = row [ "ring_group_call_timeout" ];
2018-04-05 20:02:24 -06:00
ring_group_caller_id_name = row [ "ring_group_caller_id_name" ];
ring_group_caller_id_number = row [ "ring_group_caller_id_number" ];
2014-02-08 18:42:35 +00:00
ring_group_cid_name_prefix = row [ "ring_group_cid_name_prefix" ];
2014-09-06 07:45:17 +00:00
ring_group_cid_number_prefix = row [ "ring_group_cid_number_prefix" ];
2015-06-20 04:06:20 +00:00
missed_call_app = row [ "ring_group_missed_call_app" ];
missed_call_data = row [ "ring_group_missed_call_data" ];
2013-08-16 20:36:52 +00:00
end );
2017-11-17 17:15:47 +03:00
2019-02-12 18:22:35 -07:00
--set the recording path
record_path = recordings_dir .. "/" .. domain_name .. "/archive/" .. os.date ( "%Y/%b/%d" );
record_path = record_path : gsub ( " \\ " , "/" );
2019-06-03 08:24:13 -06:00
--set the recording file name
if ( session : ready ()) then
record_name = session : getVariable ( "record_name" );
if ( not record_name ) then
record_name = uuid .. "." .. record_ext ;
end
end
2019-02-12 18:22:35 -07:00
2018-05-07 19:59:41 -06:00
---set the call_timeout to a higher value to prevent the early timeout of the ring group
if ( session : ready ()) then
if ( ring_group_call_timeout and # ring_group_call_timeout == 0 ) then
ring_group_call_timeout = '300' ;
end
2019-06-03 08:24:13 -06:00
session : setVariable ( "call_timeout" , ring_group_call_timeout );
2018-05-07 19:59:41 -06:00
end
2018-02-10 15:48:18 -07:00
--play the greeting
if ( session : ready ()) then
if ( ring_group_greeting and # ring_group_greeting > 0 ) then
2019-01-03 17:20:33 -07:00
session : answer ();
2018-02-10 15:48:18 -07:00
session : sleep ( 1000 );
play_file ( dbh , domain_name , domain_uuid , ring_group_greeting )
session : sleep ( 1000 );
end
end
2017-09-05 12:57:30 -04:00
--get the ring group user
sql = "SELECT r.*, u.user_uuid FROM v_ring_groups as r, v_ring_group_users as u " ;
sql = sql .. "where r.ring_group_uuid = :ring_group_uuid " ;
sql = sql .. "and r.ring_group_uuid = u.ring_group_uuid " ;
2019-02-12 18:22:35 -07:00
local params = { ring_group_uuid = ring_group_uuid };
2017-09-05 12:57:30 -04:00
status = dbh : query ( sql , params , function ( row )
user_uuid = row [ "user_uuid" ];
end );
2013-07-24 19:54:17 +00:00
2014-02-08 18:42:35 +00:00
--set the caller id
if ( session : ready ()) then
2017-11-04 12:37:58 -06:00
if ( ring_group_cid_name_prefix ~= nil and string.len ( ring_group_cid_name_prefix ) > 0 ) then
2017-04-03 13:53:41 -04:00
session : execute ( "export" , "effective_caller_id_name=" .. ring_group_cid_name_prefix .. "#" .. caller_id_name );
2015-06-20 04:06:20 +00:00
end
2017-11-04 12:37:58 -06:00
if ( ring_group_cid_number_prefix ~= nil and string.len ( ring_group_cid_number_prefix ) > 0 ) then
2017-04-03 13:53:41 -04:00
session : execute ( "export" , "effective_caller_id_number=" .. ring_group_cid_number_prefix .. caller_id_number );
2015-06-20 04:06:20 +00:00
end
2014-02-08 18:42:35 +00:00
end
2015-06-20 03:43:38 +00:00
--check the missed calls
function missed ()
2017-07-20 03:50:10 -06:00
--send missed call email
2015-06-20 03:43:38 +00:00
if ( missed_call_app ~= nil and missed_call_data ~= nil ) then
if ( missed_call_app == "email" ) then
2015-09-15 08:49:37 -06:00
--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
2018-03-24 15:45:11 -06:00
--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 " );
2015-09-15 08:49:37 -06:00
end
2018-03-24 15:45:11 -06:00
dbh : query ( sql , params , function ( row )
subject = row [ "template_subject" ];
body = row [ "template_body" ];
end );
2015-09-15 08:49:37 -06:00
--prepare the headers
headers = '{"X-FusionPBX-Domain-UUID":"' .. domain_uuid .. '",' ;
headers = headers .. '"X-FusionPBX-Domain-Name":"' .. domain_name .. '",' ;
headers = headers .. '"X-FusionPBX-Call-UUID":"' .. uuid .. '",' ;
headers = 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 ( "${ring_group_name}" , ring_group_name );
2015-09-27 00:52:47 -06:00
subject = subject : gsub ( "${ring_group_extension}" , ring_group_extension );
subject = subject : gsub ( "${sip_to_user}" , ring_group_name );
subject = subject : gsub ( "${dialed_user}" , ring_group_extension );
2015-09-15 08:49:37 -06:00
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 ( "${ring_group_name}" , ring_group_name );
2015-09-27 00:52:47 -06:00
body = body : gsub ( "${ring_group_extension}" , ring_group_extension );
body = body : gsub ( "${sip_to_user}" , ring_group_name );
body = body : gsub ( "${dialed_user}" , ring_group_extension );
2015-09-15 08:49:37 -06:00
body = body : gsub ( " " , " " );
body = body : gsub ( "%s+" , "" );
body = body : gsub ( " " , " " );
body = body : gsub ( " \n " , "" );
body = body : gsub ( " \n " , "" );
body = body : gsub ( "'" , "'" );
body = body : gsub ( [["]] , """ );
body = trim ( body );
--send the email
cmd = "luarun email.lua " .. missed_call_data .. " " .. missed_call_data .. " " .. headers .. " '" .. subject .. "' '" .. body .. "'" ;
if ( debug [ "info" ]) then
freeswitch.consoleLog ( "notice" , "[missed call] cmd: " .. cmd .. " \n " );
end
api = freeswitch.API ();
result = api : executeString ( cmd );
2015-06-20 03:43:38 +00:00
end
end
end
2017-10-07 23:14:22 -06:00
--get the destination and follow the forward
function get_forward_all ( count , destination_number , domain_name )
cmd = "user_exists id " .. destination_number .. " " .. domain_name ;
2018-10-30 21:44:31 -06:00
--freeswitch.consoleLog("notice", "[ring groups][call forward all] " .. cmd .. "\n");
2017-10-07 23:14:22 -06:00
user_exists = api : executeString ( cmd );
if ( user_exists == "true" ) then
---check to see if the new destination is forwarded - third forward
cmd = "user_data " .. destination_number .. "@" .. domain_name .. " var forward_all_enabled" ;
if ( api : executeString ( cmd ) == "true" ) then
2018-01-09 17:08:28 -05:00
--get the toll_allow var
cmd = "user_data " .. destination_number .. "@" .. leg_domain_name .. " var toll_allow" ;
toll_allow = api : executeString ( cmd );
2018-10-30 21:44:31 -06:00
--freeswitch.consoleLog("notice", "[ring groups][call forward all] " .. destination_number .. " toll_allow is ".. toll_allow .."\n");
2017-10-07 23:14:22 -06:00
--get the new destination - third foward
cmd = "user_data " .. destination_number .. "@" .. domain_name .. " var forward_all_destination" ;
destination_number = api : executeString ( cmd );
2018-10-30 21:44:31 -06:00
--freeswitch.consoleLog("notice", "[ring groups][call forward all] " .. count .. " " .. cmd .. " ".. destination_number .."\n");
2017-10-07 23:14:22 -06:00
count = count + 1 ;
if ( count < 5 ) then
count , destination_number = get_forward_all ( count , destination_number , domain_name );
end
end
end
2018-01-09 17:08:28 -05:00
return count , destination_number , toll_allow ;
2017-10-07 23:14:22 -06:00
end
2013-08-16 20:36:52 +00:00
--process the ring group
if ( ring_group_forward_enabled == "true" and string.len ( ring_group_forward_destination ) > 0 ) then
--forward the ring group
2019-06-03 08:24:13 -06:00
session : setVariable ( "toll_allow" , ring_group_forward_toll_allow );
2013-08-16 20:36:52 +00:00
session : execute ( "transfer" , ring_group_forward_destination .. " XML " .. context );
else
2015-09-01 18:33:26 +02:00
--get the strategy of the ring group, if random, we use random() to order the destinations
2016-11-22 21:09:25 +03:00
local sql = [[
SELECT
r.ring_group_strategy
FROM
v_ring_groups as r
WHERE
ring_group_uuid = :ring_group_uuid
AND r.domain_uuid = :domain_uuid
AND r.ring_group_enabled = 'true'
]] ;
local params = { ring_group_uuid = ring_group_uuid , domain_uuid = domain_uuid };
2019-08-11 16:55:12 -06:00
dbh : query ( sql , params , function ( row )
2015-09-01 18:33:26 +02:00
if ( row.ring_group_strategy == "random" ) then
2015-09-07 11:48:10 +02:00
if ( database [ "type" ] == "mysql" ) then
2015-09-03 11:53:28 +02:00
sql_order = 'rand()'
2015-09-07 12:51:12 +02:00
else
sql_order = 'random()' --both postgresql and sqlite uses random() instead of rand()
2015-09-03 11:53:28 +02:00
end
2015-09-01 18:33:26 +02:00
else
sql_order = 'd.destination_delay, d.destination_number asc'
2016-03-11 13:21:52 +00:00
end
2019-08-11 16:55:12 -06:00
end );
2016-03-11 13:21:52 +00:00
2013-08-16 20:36:52 +00:00
--get the ring group destinations
2015-05-12 05:59:33 +00:00
sql = [[
2016-03-11 13:21:52 +00:00
SELECT
r.ring_group_strategy, r.ring_group_timeout_app, r.ring_group_distinctive_ring,
d.destination_number, d.destination_delay, d.destination_timeout, d.destination_prompt,
2018-04-05 20:02:24 -06:00
r.ring_group_caller_id_name, r.ring_group_caller_id_number,
r.ring_group_cid_name_prefix, r.ring_group_cid_number_prefix,
r.ring_group_timeout_data, r.ring_group_ringback
2016-03-11 13:21:52 +00:00
FROM
2015-06-20 04:06:20 +00:00
v_ring_groups as r, v_ring_group_destinations as d
2016-03-11 13:21:52 +00:00
WHERE
d.ring_group_uuid = r.ring_group_uuid
2016-11-22 21:09:25 +03:00
AND d.ring_group_uuid = :ring_group_uuid
AND r.domain_uuid = :domain_uuid
2016-03-11 13:21:52 +00:00
AND r.ring_group_enabled = 'true'
ORDER BY
2015-09-03 11:53:28 +02:00
]] .. sql_order .. [[
2016-11-22 21:09:25 +03:00
]] ;
if debug [ "sql" ] then
freeswitch.consoleLog ( "notice" , "[ring group] SQL:" .. sql .. "; params:" .. json.encode ( params ) .. " \n " );
end
2013-08-16 20:36:52 +00:00
destinations = {};
2017-10-27 21:50:05 -06:00
destination_count = 0 ;
2018-10-05 13:42:49 -06:00
x = 1 ;
2019-08-11 16:55:12 -06:00
dbh : query ( sql , params , function ( row )
2013-08-16 20:36:52 +00:00
if ( row.destination_prompt == "1" or row.destination_prompt == "2" ) then
prompt = "true" ;
end
2013-12-04 08:14:40 +00:00
local array = explode ( "@" , row.destination_number );
if ( array [ 2 ] == nil ) then
-- no @
leg_domain_name = domain_name ;
else
leg_domain_name = array [ 2 ];
end
2017-10-07 23:14:22 -06:00
--follow the forwards
2018-01-09 17:08:28 -05:00
count , destination_number , toll_allow = get_forward_all ( 0 , row.destination_number , leg_domain_name );
--update values
2018-01-08 14:14:08 -05:00
row [ 'destination_number' ] = destination_number
2018-01-09 17:08:28 -05:00
row [ 'toll_allow' ] = toll_allow ;
2018-02-08 11:33:08 -07:00
2017-10-18 01:50:30 -06:00
--check if the user exists
cmd = "user_exists id " .. destination_number .. " " .. domain_name ;
user_exists = api : executeString ( cmd );
--cmd = "user_exists id ".. destination_number .." "..leg_domain_name;
2013-08-16 20:36:52 +00:00
if ( user_exists == "true" ) then
2015-05-16 23:38:07 +00:00
--add user_exists true or false to the row array
row [ 'user_exists' ] = "true" ;
--handle do_not_disturb
2017-10-18 01:50:30 -06:00
cmd = "user_data " .. destination_number .. "@" .. leg_domain_name .. " var do_not_disturb" ;
2015-05-16 23:38:07 +00:00
if ( api : executeString ( cmd ) ~= "true" ) then
--add the row to the destinations array
destinations [ x ] = row ;
end
2013-08-16 20:36:52 +00:00
else
2015-05-16 23:38:07 +00:00
--set the values
external = "true" ;
row [ 'user_exists' ] = "false" ;
--add the row to the destinations array
destinations [ x ] = row ;
2013-08-16 20:36:52 +00:00
end
2015-06-07 04:06:39 +00:00
row [ 'domain_name' ] = leg_domain_name ;
2017-10-27 21:50:05 -06:00
destination_count = destination_count + 1 ;
2013-08-16 20:36:52 +00:00
x = x + 1 ;
2019-08-11 16:55:12 -06:00
end );
2013-08-16 20:36:52 +00:00
--freeswitch.consoleLog("NOTICE", "[ring_group] external "..external.."\n");
2013-09-09 23:57:10 +00:00
2013-08-16 20:36:52 +00:00
--get the dialplan data and save it to a table
2018-05-09 11:54:41 -04:00
if ( external == "true" ) then
2018-02-11 10:22:25 -07:00
dialplans = route_to_bridge.preload_dialplan (
dbh , domain_uuid , { hostname = hostname , context = context }
)
2013-07-24 19:54:17 +00:00
end
2013-09-09 23:57:10 +00:00
2018-10-05 13:42:49 -06:00
--prepare the array of destinations
x = 1 ;
for key , row in pairs ( destinations ) do
--set the values from the database as variables
user_exists = row.user_exists ;
ring_group_strategy = row.ring_group_strategy ;
ring_group_timeout_app = row.ring_group_timeout_app ;
ring_group_timeout_data = row.ring_group_timeout_data ;
ring_group_caller_id_name = row.ring_group_caller_id_name ;
ring_group_caller_id_number = row.ring_group_caller_id_number ;
ring_group_cid_name_prefix = row.ring_group_cid_name_prefix ;
ring_group_cid_number_prefix = row.ring_group_cid_number_prefix ;
ring_group_distinctive_ring = row.ring_group_distinctive_ring ;
ring_group_ringback = row.ring_group_ringback ;
destination_number = row.destination_number ;
destination_delay = row.destination_delay ;
destination_timeout = row.destination_timeout ;
destination_prompt = row.destination_prompt ;
toll_allow = row.toll_allow ;
2019-06-28 13:12:04 -06:00
--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
2018-10-05 13:42:49 -06:00
freeswitch.consoleLog ( "NOTICE" , "[ring_group] " .. cmd .. " \n " );
2019-06-28 13:12:04 -06:00
cmd = "user_data " .. destination_number .. "@" .. domain_name .. " var forward_user_not_registered_enabled" ;
freeswitch.consoleLog ( "NOTICE" , "[ring_group] " .. 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" , "[ring_group] " .. cmd .. " \n " );
not_registered_destination_number = api : executeString ( cmd );
freeswitch.consoleLog ( "NOTICE" , "[ring_group] " .. 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
2018-10-05 13:42:49 -06:00
end
end
end
end
2018-10-30 21:44:31 -06:00
--process the destinations
--x = 1;
--for key, row in pairs(destinations) do
2019-02-02 18:00:59 -07:00
-- freeswitch.consoleLog("NOTICE", "[ring group] destination_number: "..row.destination_number.."\n");
2018-10-30 21:44:31 -06:00
--end
2013-08-16 20:36:52 +00:00
--process the destinations
2018-10-05 13:42:49 -06:00
x = 1 ;
2013-08-16 20:36:52 +00:00
for key , row in pairs ( destinations ) do
--set the values from the database as variables
2014-02-25 04:59:02 +00:00
ring_group_strategy = row.ring_group_strategy ;
2013-08-16 20:36:52 +00:00
ring_group_timeout_app = row.ring_group_timeout_app ;
ring_group_timeout_data = row.ring_group_timeout_data ;
2018-04-05 20:02:24 -06:00
ring_group_caller_id_name = row.ring_group_caller_id_name ;
ring_group_caller_id_number = row.ring_group_caller_id_number ;
2013-08-16 20:36:52 +00:00
ring_group_cid_name_prefix = row.ring_group_cid_name_prefix ;
2014-09-06 07:45:17 +00:00
ring_group_cid_number_prefix = row.ring_group_cid_number_prefix ;
2015-06-07 04:06:39 +00:00
ring_group_distinctive_ring = row.ring_group_distinctive_ring ;
2013-08-16 20:36:52 +00:00
ring_group_ringback = row.ring_group_ringback ;
destination_number = row.destination_number ;
destination_delay = row.destination_delay ;
destination_timeout = row.destination_timeout ;
destination_prompt = row.destination_prompt ;
2018-10-05 13:42:49 -06:00
group_confirm_key = row.group_confirm_key ;
group_confirm_file = row.group_confirm_file ;
2018-01-09 17:08:28 -05:00
toll_allow = row.toll_allow ;
2018-10-05 13:42:49 -06:00
user_exists = row.user_exists ;
2018-04-25 21:19:31 -06:00
2017-10-18 01:50:30 -06:00
--follow the forwards
count , destination_number = get_forward_all ( 0 , destination_number , leg_domain_name );
--check if the user exists
cmd = "user_exists id " .. destination_number .. " " .. domain_name ;
user_exists = api : executeString ( cmd );
2013-08-16 20:36:52 +00:00
--set ringback
2016-01-28 15:47:31 +00:00
ring_group_ringback = format_ringback ( ring_group_ringback );
2013-08-16 20:36:52 +00:00
session : setVariable ( "ringback" , ring_group_ringback );
session : setVariable ( "transfer_ringback" , ring_group_ringback );
2013-09-09 23:57:10 +00:00
2017-10-27 21:50:05 -06:00
--set the timeout if there is only one destination
if ( destination_count == 1 ) then
session : execute ( "set" , "call_timeout=" .. row.destination_timeout );
end
2013-08-16 20:36:52 +00:00
--setup the delimiter
delimiter = "," ;
2014-02-25 04:59:02 +00:00
if ( ring_group_strategy == "rollover" ) then
2013-08-16 20:36:52 +00:00
delimiter = "|" ;
end
2014-02-25 04:59:02 +00:00
if ( ring_group_strategy == "sequence" ) then
delimiter = "|" ;
end
2015-09-01 18:33:26 +02:00
if ( ring_group_strategy == "random" ) then
delimiter = "|" ;
end
2014-02-25 04:59:02 +00:00
if ( ring_group_strategy == "simultaneous" ) then
2013-08-16 20:36:52 +00:00
delimiter = "," ;
end
2014-02-25 04:59:02 +00:00
if ( ring_group_strategy == "enterprise" ) then
2013-08-16 20:36:52 +00:00
delimiter = ":_:" ;
end
2013-09-09 23:57:10 +00:00
2015-07-03 09:51:02 -06:00
--leg delay settings
if ( ring_group_strategy == "enterprise" ) then
delay_name = "originate_delay_start" ;
2019-06-22 00:04:14 -04:00
destination_delay = destination_delay * 500 ;
2015-07-03 09:51:02 -06:00
else
delay_name = "leg_delay_start" ;
end
2013-08-16 20:36:52 +00:00
--create a new uuid and add it to the uuid list
new_uuid = api : executeString ( "create_uuid" );
if ( string.len ( uuids ) == 0 ) then
uuids = new_uuid ;
else
uuids = uuids .. "," .. new_uuid ;
end
session : execute ( "set" , "uuids=" .. uuids );
2014-07-13 03:25:05 +00:00
2015-06-07 04:06:39 +00:00
--export the ringback
if ( ring_group_distinctive_ring ~= nil ) then
2019-02-20 08:34:18 -07:00
if ( local_ip_v4 ~= nil ) then
ring_group_distinctive_ring = ring_group_distinctive_ring : gsub ( "${local_ip_v4}" , local_ip_v4 );
end
if ( domain_name ~= nil ) then
ring_group_distinctive_ring = ring_group_distinctive_ring : gsub ( "${domain_name}" , domain_name );
end
2015-06-07 04:10:41 +00:00
session : execute ( "export" , "sip_h_Alert-Info=" .. ring_group_distinctive_ring );
2015-06-07 04:06:39 +00:00
end
2014-08-14 03:06:00 +00:00
--set confirm
2016-03-11 13:21:52 +00:00
if ( ring_group_strategy == "simultaneous"
2014-08-14 03:06:00 +00:00
or ring_group_strategy == "sequence"
or ring_group_strategy == "rollover" ) then
session : execute ( "set" , "group_confirm_key=exec" );
2015-08-11 05:06:33 +03:00
session : execute ( "set" , "group_confirm_file=lua " .. scripts_dir : gsub ( ' \\ ' , '/' ) .. "/confirm.lua" );
2014-08-14 03:06:00 +00:00
end
2014-07-13 03:25:05 +00:00
--determine confirm prompt
if ( destination_prompt == nil ) then
2014-08-14 03:06:00 +00:00
group_confirm = "confirm=false," ;
2014-07-13 03:25:05 +00:00
elseif ( destination_prompt == "1" ) then
2015-08-11 05:06:33 +03:00
group_confirm = "group_confirm_key=exec,group_confirm_file=lua " .. scripts_dir : gsub ( ' \\ ' , '/' ) .. "/confirm.lua,confirm=true," ;
2014-07-13 03:25:05 +00:00
elseif ( destination_prompt == "2" ) then
2015-08-11 05:06:33 +03:00
group_confirm = "group_confirm_key=exec,group_confirm_file=lua " .. scripts_dir : gsub ( ' \\ ' , '/' ) .. "/confirm.lua,confirm=true," ;
2013-09-11 02:37:57 +00:00
else
2014-08-14 03:06:00 +00:00
group_confirm = "confirm=false," ;
2013-09-11 02:37:57 +00:00
end
2013-09-09 23:57:10 +00:00
2014-08-14 01:21:55 +00:00
--get user_record value and determine whether to record the session
cmd = "user_data " .. destination_number .. "@" .. domain_name .. " var user_record" ;
user_record = trim ( api : executeString ( cmd ));
--set the record_session variable
2015-08-31 15:47:06 +04:00
record_session = false ;
2014-08-14 01:21:55 +00:00
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
--record the session
if ( record_session ) then
2018-03-16 00:44:11 -06:00
record_session = ",api_on_answer='uuid_record " .. uuid .. " start " .. record_path .. "/" .. record_name .. "',record_path='" .. record_path .. "',record_name=" .. record_name ;
2016-03-11 13:21:52 +00:00
else
2015-08-31 15:47:06 +04:00
record_session = ""
2014-08-14 01:21:55 +00:00
end
2015-08-31 15:47:06 +04:00
row.record_session = record_session
2014-08-14 01:21:55 +00:00
2013-08-16 20:36:52 +00:00
--process according to user_exists, sip_uri, external number
if ( user_exists == "true" ) then
2014-08-03 12:06:49 +00:00
--get the extension_uuid
2014-08-13 10:51:51 +00:00
cmd = "user_data " .. destination_number .. "@" .. domain_name .. " var extension_uuid" ;
extension_uuid = trim ( api : executeString ( cmd ));
2013-08-16 20:36:52 +00:00
--send to user
2016-03-10 18:10:49 -07:00
local dial_string_to_user = "[sip_invite_domain=" .. domain_name .. ",call_direction=" .. call_direction .. "," .. group_confirm .. "leg_timeout=" .. destination_timeout .. "," .. delay_name .. "=" .. destination_delay .. ",dialed_extension=" .. row.destination_number .. ",extension_uuid=" .. extension_uuid .. row.record_session .. "]user/" .. row.destination_number .. "@" .. domain_name ;
2017-11-17 17:15:47 +03:00
dial_string = dial_string_to_user ;
2018-02-08 11:33:08 -07:00
elseif ( tonumber ( destination_number ) == nil ) then
--sip uri
dial_string = "[sip_invite_domain=" .. domain_name .. ",call_direction=" .. call_direction .. "," .. group_confirm .. "leg_timeout=" .. destination_timeout .. "," .. delay_name .. "=" .. destination_delay .. "]" .. row.destination_number ;
2013-08-16 20:36:52 +00:00
else
2018-08-26 11:30:47 -06:00
--external number
route_bridge = 'loopback/' .. destination_number ;
--set the toll allow to an empty string
if ( toll_allow == nil ) then
toll_allow = '' ;
2017-11-29 10:20:45 +03:00
end
2018-02-11 10:22:25 -07:00
2018-08-26 11:30:47 -06:00
--set the caller id
caller_id = '' ;
if ( ring_group_caller_id_name ~= nil ) then
caller_id = "origination_caller_id_name='" .. ring_group_caller_id_name .. "'"
end
if ( ring_group_caller_id_number ~= nil ) then
caller_id = caller_id .. ",origination_caller_id_number=" .. ring_group_caller_id_number .. "," ;
end
2018-02-11 10:22:25 -07:00
2018-08-26 11:30:47 -06:00
--set the destination dial string
dial_string = "[ignore_early_media=true,toll_allow=" .. toll_allow .. "," .. caller_id .. "sip_invite_domain=" .. domain_name .. ",call_direction=" .. call_direction .. "," .. group_confirm .. "leg_timeout=" .. destination_timeout .. "," .. delay_name .. "=" .. destination_delay .. "]" .. route_bridge
2013-07-24 19:54:17 +00:00
end
2013-09-09 23:57:10 +00:00
2014-08-30 18:22:06 +00:00
--add a delimiter between destinations
2014-07-13 03:25:05 +00:00
if ( dial_string ~= nil ) then
2014-08-13 10:51:51 +00:00
--freeswitch.consoleLog("notice", "[ring group] dial_string: " .. dial_string .. "\n");
2018-10-05 13:42:49 -06:00
if ( x == 1 ) then
2014-08-30 18:22:06 +00:00
if ( ring_group_strategy == "enterprise" ) then
app_data = dial_string ;
else
app_data = "{ignore_early_media=true}" .. dial_string ;
end
2014-07-13 03:25:05 +00:00
else
2014-08-13 10:51:51 +00:00
if ( app_data == nil ) then
2014-08-30 18:22:06 +00:00
if ( ring_group_strategy == "enterprise" ) then
app_data = dial_string ;
else
app_data = "{ignore_early_media=true}" .. dial_string ;
end
2014-08-13 10:51:51 +00:00
else
app_data = app_data .. delimiter .. dial_string ;
end
2014-07-13 03:25:05 +00:00
end
2013-07-24 19:54:17 +00:00
end
2013-09-09 23:57:10 +00:00
2013-08-16 20:36:52 +00:00
--increment the value of x
x = x + 1 ;
2013-07-24 19:54:17 +00:00
end
2013-09-09 23:57:10 +00:00
2013-08-16 20:36:52 +00:00
--session execute
if ( session : ready ()) then
--set the variables
session : execute ( "set" , "hangup_after_bridge=true" );
session : execute ( "set" , "continue_on_fail=true" );
2013-09-09 23:57:10 +00:00
2016-08-30 10:10:08 +03:00
-- support conf-xfer feature
2018-06-06 21:10:23 -06:00
-- do
-- local uuid = api:executeString("create_uuid")
-- session:execute("export", "conf_xfer_number=xfer-" .. uuid .. "-" .. domain_name)
-- end
2015-08-18 15:38:41 +04:00
--set bind digit action
2016-08-20 20:31:04 -06:00
local bind_target = 'peer'
2016-08-20 23:59:10 -06:00
if session : getVariable ( "sip_authorized" ) == "true" then
bind_target = 'both' ;
end
2015-08-18 15:38:41 +04:00
local bindings = {
2018-03-16 00:44:11 -06:00
"local,*2,exec:record_session," .. record_path .. "/" .. record_name ,
2016-08-30 10:10:08 +03:00
-- "local,*0,exec:execute_extension,conf_xfer_from_dialplan XML conf-xfer@" .. context
2015-08-18 15:38:41 +04:00
}
for _ , str in ipairs ( bindings ) do
session : execute ( "bind_digit_action" , str .. "," .. bind_target )
end
2016-08-20 23:59:10 -06:00
session : execute ( "digit_action_set_realm" , "local" );
2013-09-09 23:57:10 +00:00
2018-06-06 21:10:23 -06:00
--if the user is busy rollover to the next destination
if ( ring_group_strategy == "rollover" ) then
2018-06-06 23:08:34 -06:00
timeout = 0 ;
2018-06-06 21:10:23 -06:00
x = 0 ;
for key , row in pairs ( destinations ) do
2014-08-03 10:35:01 +00:00
2018-06-06 21:10:23 -06:00
--set the app data
app_data = '{ignore_early_media=true}' ;
2015-08-14 15:27:48 -06:00
2018-06-06 21:10:23 -06:00
--set the values from the database as variables
user_exists = row.user_exists ;
destination_number = row.destination_number ;
domain_name = row.domain_name ;
2018-05-06 13:10:08 -06:00
2018-06-06 21:10:23 -06:00
--get the extension_uuid
if ( user_exists == "true" ) then
cmd = "user_data " .. destination_number .. "@" .. domain_name .. " var extension_uuid" ;
extension_uuid = trim ( api : executeString ( cmd ));
end
2015-08-14 15:27:48 -06:00
2018-06-06 23:08:34 -06:00
--if the timeout was reached exit the loop and go to the timeout action
if ( tonumber ( ring_group_call_timeout ) == timeout ) then
break ;
end
timeout = timeout + destination_timeout ;
2018-06-06 21:10:23 -06:00
--send the call to the destination
if ( user_exists == "true" ) then
dial_string = "[" .. group_confirm .. "sip_invite_domain=" .. domain_name .. ",originate_timeout=" .. destination_timeout .. ",call_direction=" .. call_direction .. ",dialed_extension=" .. destination_number .. ",extension_uuid=" .. extension_uuid .. ",domain_name=" .. domain_name .. ",domain_uuid=" .. domain_uuid .. row.record_session .. "]user/" .. destination_number .. "@" .. domain_name ;
elseif ( tonumber ( destination_number ) == nil ) then
dial_string = "[" .. group_confirm .. "sip_invite_domain=" .. domain_name .. ",originate_timeout=" .. destination_timeout .. ",call_direction=outbound,domain_name=" .. domain_name .. ",domain_uuid=" .. domain_uuid .. "]" .. destination_number ;
else
dial_string = "[" .. group_confirm .. "sip_invite_domain=" .. domain_name .. ",originate_timeout=" .. destination_timeout .. ",domain_name=" .. domain_name .. ",domain_uuid=" .. domain_uuid .. ",call_direction=outbound]loopback/" .. destination_number ;
end
--add the delimiter
app_data = app_data .. dial_string ;
freeswitch.consoleLog ( "NOTICE" , "[ring group] app_data: " .. app_data .. " \n " );
session : execute ( "bridge" , app_data );
--increment the value of x
x = x + 1 ;
2014-07-13 03:25:05 +00:00
end
2018-06-06 21:10:23 -06:00
end
2014-02-25 04:59:02 +00:00
2018-06-06 21:10:23 -06:00
--execute the bridge
if ( app_data ~= nil ) then
if ( ring_group_strategy == "enterprise" ) then
app_data = app_data : gsub ( "%[" , "{" );
app_data = app_data : gsub ( "%]" , "}" );
end
freeswitch.consoleLog ( "NOTICE" , "[ring group] app_data: " .. app_data .. " \n " );
-- log.noticef("bridge begin: originate_disposition:%s answered:%s ready:%s bridged:%s", session:getVariable("originate_disposition"), session:answered() and "true" or "false", session:ready() and "true" or "false", session:bridged() and "true" or "false")
if ( ring_group_strategy ~= "rollover" ) then
2014-07-13 03:25:05 +00:00
session : execute ( "bridge" , app_data );
2014-08-30 18:22:06 +00:00
end
2018-06-06 21:10:23 -06:00
-- log.noticef("bridge done: originate_disposition:%s answered:%s ready:%s bridged:%s", session:getVariable("originate_disposition"), session:answered() and "true" or "false", session:ready() and "true" or "false", session:bridged() and "true" or "false")
end
2014-08-30 18:22:06 +00:00
2018-06-06 21:10:23 -06:00
--timeout destination
if ( app_data ~= nil ) then
if session : ready () and (
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
--execute the time out action
if ring_group_timeout_app and # ring_group_timeout_app > 0 then
session : execute ( ring_group_timeout_app , ring_group_timeout_data );
end
2018-12-24 00:06:40 +05:00
--check and report missed call
2018-12-23 12:07:24 -07:00
missed ();
2018-06-06 21:10:23 -06:00
end
else
if ( ring_group_timeout_app ~= nil ) then
--execute the time out action
if ring_group_timeout_app and # ring_group_timeout_app > 0 then
session : execute ( ring_group_timeout_app , ring_group_timeout_data );
end
2014-07-13 03:25:05 +00:00
else
2018-06-06 21:10:23 -06:00
local sql = "SELECT ring_group_timeout_app, ring_group_timeout_data FROM v_ring_groups " ;
sql = sql .. "where ring_group_uuid = :ring_group_uuid" ;
local params = { ring_group_uuid = ring_group_uuid };
if debug [ "sql" ] then
freeswitch.consoleLog ( "notice" , "[ring group] SQL:" .. sql .. "; params:" .. json.encode ( params ) .. " \n " );
end
dbh : query ( sql , params , function ( row )
2015-06-20 03:43:38 +00:00
--execute the time out action
2018-06-06 21:10:23 -06:00
if row.ring_group_timeout_app and # row.ring_group_timeout_app > 0 then
session : execute ( row.ring_group_timeout_app , row.ring_group_timeout_data );
2017-11-17 17:15:47 +03:00
end
2018-06-06 21:10:23 -06:00
end );
2014-07-13 03:25:05 +00:00
end
2018-06-06 21:10:23 -06:00
end
end
end
2014-07-13 03:25:05 +00:00
2013-07-24 19:54:17 +00:00
--actions
--ACTIONS = {}
--table.insert(ACTIONS, {"set", "hangup_after_bridge=true"});
--table.insert(ACTIONS, {"set", "continue_on_fail=true"});
--table.insert(ACTIONS, {"bridge", app_data});
2015-05-05 06:35:43 +00:00
--table.insert(ACTIONS, {ring_group_timeout_app, ring_group_timeout_data});