1-- Part of FusionPBX 2-- Copyright (C) 2010-2018 Mark J Crane <markjcrane@fusionpbx.com> 3-- All rights reserved. 4-- 5-- Redistribution and use in source and binary forms, with or without 6-- modification, are permitted provided that the following conditions are met: 7-- 8-- 1. Redistributions of source code must retain the above copyright notice, 9-- this list of conditions and the following disclaimer. 10-- 11-- 2. Redistributions in binary form must reproduce the above copyright 12-- notice, this list of conditions and the following disclaimer in the 13-- documentation and/or other materials provided with the distribution. 14-- 15-- THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 16-- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 17-- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 18-- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 19-- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20-- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22-- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23-- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24-- POSSIBILITY OF SUCH DAMAGE. 25-- 26-- Contributor(s): 27-- Mark J Crane <markjcrane@fusionpbx.com> 28-- Luis Daniel Lucio Qurioz <dlucio@okay.com.mx> 29 30--include the log 31 log = require "resources.functions.log".ring_group 32 33--connect to the database 34 local Database = require "resources.functions.database"; 35 dbh = Database.new('system'); 36 37--include json library 38 local json 39 if (debug["sql"]) then 40 json = require "resources.functions.lunajson" 41 end 42 43--include functions 44 require "resources.functions.trim"; 45 require "resources.functions.explode"; 46 require "resources.functions.base64"; 47 require "resources.functions.file_exists"; 48 require "resources.functions.channel_utils" 49 require "resources.functions.format_ringback" 50 51--- include libs 52 local route_to_bridge = require "resources.functions.route_to_bridge" 53 local play_file = require "resources.functions.play_file" 54 55--define the session hangup 56 function session_hangup_hook() 57 58 --send info to the log 59 --freeswitch.consoleLog("notice","[ring_groups] originate_disposition: " .. session:getVariable("originate_disposition") .. "\n"); 60 61 --status 62 status = 'answered' 63 64 --run the missed called function 65 if ( 66 session:getVariable("originate_disposition") == "ALLOTTED_TIMEOUT" 67 or session:getVariable("originate_disposition") == "NO_ANSWER" 68 or session:getVariable("originate_disposition") == "NO_USER_RESPONSE" 69 or session:getVariable("originate_disposition") == "USER_NOT_REGISTERED" 70 or session:getVariable("originate_disposition") == "NORMAL_TEMPORARY_FAILURE" 71 or session:getVariable("originate_disposition") == "NO_ROUTE_DESTINATION" 72 or session:getVariable("originate_disposition") == "USER_BUSY" 73 or session:getVariable("originate_disposition") == "RECOVERY_ON_TIMER_EXPIRE" 74 or session:getVariable("originate_disposition") == "failure" 75 or session:getVariable("originate_disposition") == "ORIGINATOR_CANCEL" 76 ) then 77 --set the status 78 status = 'missed' 79 --send missed call notification 80 missed(); 81 end 82 83 --send the ring group event 84 event = freeswitch.Event("CUSTOM", "RING_GROUPS"); 85 event:addHeader("domain_uuid", domain_uuid); 86 event:addHeader("domain_name", domain_name); 87 event:addHeader("ring_group_uuid", ring_group_uuid); 88 event:addHeader("user_uuid", user_uuid); 89 event:addHeader("ring_group_name", ring_group_name); 90 event:addHeader("ring_group_extension", ring_group_extension); 91 event:addHeader("status", status); 92 event:addHeader("call_uuid", uuid); 93 event:addHeader("caller_id_name", caller_id_name); 94 event:addHeader("caller_id_number", caller_id_number); 95 event:fire(); 96 97 end 98 99--define iterator function to iterate over key/value pairs in string 100 local function split_vars_pairs(str) 101 local last_pos = 1 102 return function() 103 -- end of string 104 if not str then return end 105 106 -- handle case when there exists comma after kv pair 107 local action, next_pos = string.match(str, "([^=]+=%b''),()", last_pos) 108 if not action then 109 action, next_pos = string.match(str, "([^=]+=[^'][^,]-),()", last_pos) 110 if not action then 111 action, next_pos = string.match(str, "([^=]+=),()", last_pos) 112 end 113 end 114 if action then 115 last_pos = next_pos 116 return action 117 end 118 119 -- last kv pair may not have comma after it 120 if last_pos < #str then 121 action = string.match(str, "([^=]+=%b'')$", last_pos) 122 if not action then 123 action = string.match(str, "([^=]+=[^,]-)$", last_pos) 124 end 125 str = nil -- end of iteration 126 end 127 128 return action 129 end 130 end 131 132--set the hangup hook function 133 if (session:ready()) then 134 session:setHangupHook("session_hangup_hook"); 135 end 136 137--get the variables 138 if (session:ready()) then 139 session:setAutoHangup(false); 140 domain_name = session:getVariable("domain_name"); 141 domain_uuid = session:getVariable("domain_uuid"); 142 ring_group_uuid = session:getVariable("ring_group_uuid"); 143 recordings_dir = session:getVariable("recordings_dir"); 144 sounds_dir = session:getVariable("sounds_dir"); 145 username = session:getVariable("username"); 146 dialplan = session:getVariable("dialplan"); 147 caller_id_name = session:getVariable("caller_id_name"); 148 caller_id_number = session:getVariable("caller_id_number"); 149 network_addr = session:getVariable("network_addr"); 150 ani = session:getVariable("ani"); 151 aniii = session:getVariable("aniii"); 152 rdnis = session:getVariable("rdnis"); 153 destination_number = session:getVariable("destination_number"); 154 source = session:getVariable("source"); 155 uuid = session:getVariable("uuid"); 156 context = session:getVariable("context"); 157 call_direction = session:getVariable("call_direction"); 158 accountcode = session:getVariable("accountcode"); 159 end 160 161--default to local if nil 162 if (call_direction == nil) then 163 call_direction = "local"; 164 end 165 166---set the call_timeout to a higher value to prevent the early timeout of the ring group 167 if (session:ready()) then 168 session:setVariable("call_timeout","300"); 169 end 170 171--set ring ready 172 if (session:ready()) then 173 session:execute("ring_ready", ""); 174 end 175 176--define additional variables 177 uuids = ""; 178 external = "false"; 179 180--set the sounds path for the language, dialect and voice 181 if (session:ready()) then 182 default_language = session:getVariable("default_language"); 183 default_dialect = session:getVariable("default_dialect"); 184 default_voice = session:getVariable("default_voice"); 185 if (not default_language) then default_language = 'en'; end 186 if (not default_dialect) then default_dialect = 'us'; end 187 if (not default_voice) then default_voice = 'callie'; end 188 end 189 190--get record_ext 191 record_ext = session:getVariable("record_ext"); 192 if (not record_ext) then 193 record_ext = "wav"; 194 end 195 196--set the recording path 197 record_path = recordings_dir .. "/" .. domain_name .. "/archive/" .. os.date("%Y/%b/%d"); 198 record_path = record_path:gsub("\\", "/"); 199 200--set the recording file 201 record_name = uuid .. "." .. record_ext; 202 203--prepare the api object 204 api = freeswitch.API(); 205 206--define the session hangup 207 --function on_hangup(s,status) 208 -- freeswitch.consoleLog("NOTICE","---- on_hangup: "..status.."\n"); 209 -- error(); 210 --end 211 212--get current switchname 213 hostname = trim(api:execute("switchname", "")) 214 215--get the domain_uuid if it not already set 216 if (domain_uuid == nil or domain_uuid == '' and domain_name) then 217 sql = "SELECT domain_uuid FROM v_domains as d "; 218 sql = sql .. "where d.domain_name = :domain_name "; 219 local params = {domain_name = domain_name}; 220 status = dbh:query(sql, params, function(row) 221 domain_uuid = row["domain_uuid"]; 222 end); 223 end 224 225--get the ring group 226 ring_group_forward_enabled = ""; 227 ring_group_forward_destination = ""; 228 sql = "SELECT r.* FROM v_ring_groups as r "; 229 sql = sql .. "where r.ring_group_uuid = :ring_group_uuid "; 230 sql = sql .. "and r.domain_uuid = :domain_uuid "; 231 local params = {ring_group_uuid = ring_group_uuid, domain_uuid = domain_uuid}; 232 status = dbh:query(sql, params, function(row) 233 ring_group_name = row["ring_group_name"]; 234 ring_group_extension = row["ring_group_extension"]; 235 ring_group_greeting = row["ring_group_greeting"]; 236 ring_group_forward_enabled = row["ring_group_forward_enabled"]; 237 ring_group_forward_destination = row["ring_group_forward_destination"]; 238 ring_group_forward_toll_allow = row["ring_group_forward_toll_allow"]; 239 ring_group_caller_id_name = row["ring_group_caller_id_name"]; 240 ring_group_caller_id_number = row["ring_group_caller_id_number"]; 241 ring_group_cid_name_prefix = row["ring_group_cid_name_prefix"]; 242 ring_group_cid_number_prefix = row["ring_group_cid_number_prefix"]; 243 missed_call_app = row["ring_group_missed_call_app"]; 244 missed_call_data = row["ring_group_missed_call_data"]; 245 end); 246 247--play the greeting 248 if (session:ready()) then 249 if (ring_group_greeting and #ring_group_greeting > 0) then 250 session:sleep(1000); 251 play_file(dbh, domain_name, domain_uuid, ring_group_greeting) 252 session:sleep(1000); 253 end 254 end 255 256--get the ring group user 257 sql = "SELECT r.*, u.user_uuid FROM v_ring_groups as r, v_ring_group_users as u "; 258 sql = sql .. "where r.ring_group_uuid = :ring_group_uuid "; 259 sql = sql .. "and r.ring_group_uuid = u.ring_group_uuid "; 260 sql = sql .. "and r.domain_uuid = :domain_uuid "; 261 local params = {ring_group_uuid = ring_group_uuid, domain_uuid = domain_uuid}; 262 status = dbh:query(sql, params, function(row) 263 user_uuid = row["user_uuid"]; 264 end); 265 266--set the caller id 267 if (session:ready()) then 268 if (ring_group_cid_name_prefix ~= nil and string.len(ring_group_cid_name_prefix) > 0) then 269 session:execute("export", "effective_caller_id_name="..ring_group_cid_name_prefix.."#"..caller_id_name); 270 end 271 if (ring_group_cid_number_prefix ~= nil and string.len(ring_group_cid_number_prefix) > 0) then 272 session:execute("export", "effective_caller_id_number="..ring_group_cid_number_prefix..caller_id_number); 273 end 274 end 275 276--check the missed calls 277 function missed() 278 279 --send missed call email 280 if (missed_call_app ~= nil and missed_call_data ~= nil) then 281 if (missed_call_app == "email") then 282 --set the sounds path for the language, dialect and voice 283 default_language = session:getVariable("default_language"); 284 default_dialect = session:getVariable("default_dialect"); 285 default_voice = session:getVariable("default_voice"); 286 if (not default_language) then default_language = 'en'; end 287 if (not default_dialect) then default_dialect = 'us'; end 288 if (not default_voice) then default_voice = 'callie'; end 289 290 --get the templates 291 local sql = "SELECT * FROM v_email_templates "; 292 sql = sql .. "WHERE (domain_uuid = :domain_uuid or domain_uuid is null) "; 293 sql = sql .. "AND template_language = :template_language "; 294 sql = sql .. "AND template_category = 'missed' " 295 sql = sql .. "AND template_enabled = 'true' " 296 sql = sql .. "ORDER BY domain_uuid DESC " 297 local params = {domain_uuid = domain_uuid, template_language = default_language.."-"..default_dialect}; 298 if (debug["sql"]) then 299 freeswitch.consoleLog("notice", "[voicemail] SQL: " .. sql .. "; params:" .. json.encode(params) .. "\n"); 300 end 301 dbh:query(sql, params, function(row) 302 subject = row["template_subject"]; 303 body = row["template_body"]; 304 end); 305 306 --prepare the headers 307 headers = '{"X-FusionPBX-Domain-UUID":"'..domain_uuid..'",'; 308 headers = headers..'"X-FusionPBX-Domain-Name":"'..domain_name..'",'; 309 headers = headers..'"X-FusionPBX-Call-UUID":"'..uuid..'",'; 310 headers = headers..'"X-FusionPBX-Email-Type":"missed"}'; 311 312 --prepare the subject 313 subject = subject:gsub("${caller_id_name}", caller_id_name); 314 subject = subject:gsub("${caller_id_number}", caller_id_number); 315 subject = subject:gsub("${ring_group_name}", ring_group_name); 316 subject = subject:gsub("${ring_group_extension}", ring_group_extension); 317 subject = subject:gsub("${sip_to_user}", ring_group_name); 318 subject = subject:gsub("${dialed_user}", ring_group_extension); 319 subject = trim(subject); 320 subject = '=?utf-8?B?'..base64.encode(subject)..'?='; 321 322 --prepare the body 323 body = body:gsub("${caller_id_name}", caller_id_name); 324 body = body:gsub("${caller_id_number}", caller_id_number); 325 body = body:gsub("${ring_group_name}", ring_group_name); 326 body = body:gsub("${ring_group_extension}", ring_group_extension); 327 body = body:gsub("${sip_to_user}", ring_group_name); 328 body = body:gsub("${dialed_user}", ring_group_extension); 329 body = body:gsub(" ", " "); 330 body = body:gsub("%s+", ""); 331 body = body:gsub(" ", " "); 332 body = body:gsub("\n", ""); 333 body = body:gsub("\n", ""); 334 body = body:gsub("'", "'"); 335 body = body:gsub([["]], """); 336 body = trim(body); 337 338 --send the email 339 cmd = "luarun email.lua "..missed_call_data.." "..missed_call_data.." "..headers.." '"..subject.."' '"..body.."'"; 340 if (debug["info"]) then 341 freeswitch.consoleLog("notice", "[missed call] cmd: " .. cmd .. "\n"); 342 end 343 api = freeswitch.API(); 344 result = api:executeString(cmd); 345 end 346 end 347 end 348 349--get the destination and follow the forward 350 function get_forward_all(count, destination_number, domain_name) 351 cmd = "user_exists id ".. destination_number .." "..domain_name; 352 freeswitch.consoleLog("notice", "[ring groups][call forward all] " .. cmd .. "\n"); 353 user_exists = api:executeString(cmd); 354 if (user_exists == "true") then 355 ---check to see if the new destination is forwarded - third forward 356 cmd = "user_data ".. destination_number .."@" ..domain_name.." var forward_all_enabled"; 357 if (api:executeString(cmd) == "true") then 358 --get the toll_allow var 359 cmd = "user_data ".. destination_number .."@" ..leg_domain_name.." var toll_allow"; 360 toll_allow = api:executeString(cmd); 361 freeswitch.consoleLog("notice", "[ring groups][call forward all] " .. destination_number .. " toll_allow is ".. toll_allow .."\n"); 362 363 --get the new destination - third foward 364 cmd = "user_data ".. destination_number .."@" ..domain_name.." var forward_all_destination"; 365 destination_number = api:executeString(cmd); 366 freeswitch.consoleLog("notice", "[ring groups][call forward all] " .. count .. " " .. cmd .. " ".. destination_number .."\n"); 367 count = count + 1; 368 if (count < 5) then 369 count, destination_number = get_forward_all(count, destination_number, domain_name); 370 end 371 end 372 end 373 return count, destination_number, toll_allow; 374 end 375 376--process the ring group 377 if (ring_group_forward_enabled == "true" and string.len(ring_group_forward_destination) > 0) then 378 --forward the ring group 379 session:setVariable("toll_allow",ring_group_forward_toll_allow); 380 session:execute("transfer", ring_group_forward_destination.." XML "..context); 381 else 382 --get the strategy of the ring group, if random, we use random() to order the destinations 383 local sql = [[ 384 SELECT 385 r.ring_group_strategy 386 FROM 387 v_ring_groups as r 388 WHERE 389 ring_group_uuid = :ring_group_uuid 390 AND r.domain_uuid = :domain_uuid 391 AND r.ring_group_enabled = 'true' 392 ]]; 393 394 local params = {ring_group_uuid = ring_group_uuid, domain_uuid = domain_uuid}; 395 396 assert(dbh:query(sql, params, function(row) 397 if (row.ring_group_strategy == "random") then 398 if (database["type"] == "mysql") then 399 sql_order = 'rand()' 400 else 401 sql_order = 'random()' --both postgresql and sqlite uses random() instead of rand() 402 end 403 else 404 sql_order='d.destination_delay, d.destination_number asc' 405 end 406 end)); 407 408 --get the ring group destinations 409 sql = [[ 410 SELECT 411 r.ring_group_strategy, r.ring_group_timeout_app, r.ring_group_distinctive_ring, 412 d.destination_number, d.destination_delay, d.destination_timeout, d.destination_prompt, 413 r.ring_group_caller_id_name, r.ring_group_caller_id_number, 414 r.ring_group_cid_name_prefix, r.ring_group_cid_number_prefix, 415 r.ring_group_timeout_data, r.ring_group_ringback 416 FROM 417 v_ring_groups as r, v_ring_group_destinations as d 418 WHERE 419 d.ring_group_uuid = r.ring_group_uuid 420 AND d.ring_group_uuid = :ring_group_uuid 421 AND r.domain_uuid = :domain_uuid 422 AND r.ring_group_enabled = 'true' 423 ORDER BY 424 ]]..sql_order..[[ 425 ]]; 426 if debug["sql"] then 427 freeswitch.consoleLog("notice", "[ring group] SQL:" .. sql .. "; params:" .. json.encode(params) .. "\n"); 428 end 429 destinations = {}; 430 x = 1; 431 destination_count = 0; 432 assert(dbh:query(sql, params, function(row) 433 if (row.destination_prompt == "1" or row.destination_prompt == "2") then 434 prompt = "true"; 435 end 436 437 local array = explode("@",row.destination_number); 438 if (array[2] == nil) then 439 -- no @ 440 leg_domain_name = domain_name; 441 else 442 leg_domain_name = array[2]; 443 end 444 445 --follow the forwards 446 count, destination_number, toll_allow = get_forward_all(0, row.destination_number, leg_domain_name); 447 448 --update values 449 row['destination_number'] = destination_number 450 row['toll_allow'] = toll_allow; 451 452 --check if the user exists 453 cmd = "user_exists id ".. destination_number .." "..domain_name; 454 user_exists = api:executeString(cmd); 455 456 --cmd = "user_exists id ".. destination_number .." "..leg_domain_name; 457 if (user_exists == "true") then 458 --add user_exists true or false to the row array 459 row['user_exists'] = "true"; 460 --handle do_not_disturb 461 cmd = "user_data ".. destination_number .."@" ..leg_domain_name.." var do_not_disturb"; 462 if (api:executeString(cmd) ~= "true") then 463 --add the row to the destinations array 464 destinations[x] = row; 465 end 466 else 467 --set the values 468 external = "true"; 469 row['user_exists'] = "false"; 470 --add the row to the destinations array 471 destinations[x] = row; 472 end 473 row['domain_name'] = leg_domain_name; 474 destination_count = destination_count + 1; 475 x = x + 1; 476 end)); 477 --freeswitch.consoleLog("NOTICE", "[ring_group] external "..external.."\n"); 478 479 --get the dialplan data and save it to a table 480 if (external == "true") then 481 dialplans = route_to_bridge.preload_dialplan( 482 dbh, domain_uuid, {hostname = hostname, context = context} 483 ) 484 end 485 486 --process the destinations 487 x = 0; 488 for key, row in pairs(destinations) do 489 --set the values from the database as variables 490 user_exists = row.user_exists; 491 ring_group_strategy = row.ring_group_strategy; 492 ring_group_timeout_app = row.ring_group_timeout_app; 493 ring_group_timeout_data = row.ring_group_timeout_data; 494 ring_group_caller_id_name = row.ring_group_caller_id_name; 495 ring_group_caller_id_number = row.ring_group_caller_id_number; 496 ring_group_cid_name_prefix = row.ring_group_cid_name_prefix; 497 ring_group_cid_number_prefix = row.ring_group_cid_number_prefix; 498 ring_group_distinctive_ring = row.ring_group_distinctive_ring; 499 ring_group_ringback = row.ring_group_ringback; 500 destination_number = row.destination_number; 501 destination_delay = row.destination_delay; 502 destination_timeout = row.destination_timeout; 503 destination_prompt = row.destination_prompt; 504 domain_name = row.domain_name; 505 toll_allow = row.toll_allow; 506 507 --determine if the user is registered if not registered then lookup 508 cmd = "sofia_contact */".. destination_number .."@" ..domain_name; 509 if (api:executeString(cmd) == "error/user_not_registered") then 510freeswitch.consoleLog("NOTICE", "[ring_group] "..cmd.."\n"); 511 cmd = "user_data ".. destination_number .."@" ..domain_name.." var forward_user_not_registered_enabled"; 512freeswitch.consoleLog("NOTICE", "[ring_group] "..cmd.."\n"); 513 if (api:executeString(cmd) == "true") then 514 --get the new destination number 515 cmd = "user_data ".. destination_number .."@" ..domain_name.." var forward_user_not_registered_destination"; 516freeswitch.consoleLog("NOTICE", "[ring_group] "..cmd.."\n"); 517 not_registered_destination_number = api:executeString(cmd); 518freeswitch.consoleLog("NOTICE", "[ring_group] "..not_registered_destination_number.."\n"); 519 if (not_registered_destination_number ~= nil) then 520 destination_number = not_registered_destination_number; 521 end 522 523 --check the new destination number for user_exists 524 cmd = "user_exists id ".. destination_number .." "..domain_name; 525 user_exists = api:executeString(cmd); 526 if (user_exists == "true") then 527 row['user_exists'] = "true"; 528 else 529 row['user_exists'] = "false"; 530 end 531 end 532 end 533 534 --follow the forwards 535 count, destination_number = get_forward_all(0, destination_number, leg_domain_name); 536 537 --check if the user exists 538 cmd = "user_exists id ".. destination_number .." "..domain_name; 539 user_exists = api:executeString(cmd); 540 541 --set ringback 542 ring_group_ringback = format_ringback(ring_group_ringback); 543 session:setVariable("ringback", ring_group_ringback); 544 session:setVariable("transfer_ringback", ring_group_ringback); 545 546 --set the timeout if there is only one destination 547 if (destination_count == 1) then 548 session:execute("set", "call_timeout="..row.destination_timeout); 549 end 550 551 --setup the delimiter 552 delimiter = ","; 553 if (ring_group_strategy == "rollover") then 554 delimiter = "|"; 555 end 556 if (ring_group_strategy == "sequence") then 557 delimiter = "|"; 558 end 559 if (ring_group_strategy == "random") then 560 delimiter = "|"; 561 end 562 if (ring_group_strategy == "simultaneous") then 563 delimiter = ","; 564 end 565 if (ring_group_strategy == "enterprise") then 566 delimiter = ":_:"; 567 end 568 569 --leg delay settings 570 if (ring_group_strategy == "enterprise") then 571 delay_name = "originate_delay_start"; 572 destination_delay = destination_delay * 1000; 573 else 574 delay_name = "leg_delay_start"; 575 end 576 577 --create a new uuid and add it to the uuid list 578 new_uuid = api:executeString("create_uuid"); 579 if (string.len(uuids) == 0) then 580 uuids = new_uuid; 581 else 582 uuids = uuids ..",".. new_uuid; 583 end 584 session:execute("set", "uuids="..uuids); 585 586 --export the ringback 587 if (ring_group_distinctive_ring ~= nil) then 588 ring_group_distinctive_ring = ring_group_distinctive_ring:gsub("${local_ip_v4}", session:getVariable("local_ip_v4")); 589 ring_group_distinctive_ring = ring_group_distinctive_ring:gsub("${domain_name}", session:getVariable("domain_name")); 590 session:execute("export", "sip_h_Alert-Info="..ring_group_distinctive_ring); 591 end 592 593 --set confirm 594 if (ring_group_strategy == "simultaneous" 595 or ring_group_strategy == "sequence" 596 or ring_group_strategy == "rollover") then 597 session:execute("set", "group_confirm_key=exec"); 598 session:execute("set", "group_confirm_file=lua ".. scripts_dir:gsub('\\','/') .."/confirm.lua"); 599 end 600 601 --determine confirm prompt 602 if (destination_prompt == nil) then 603 group_confirm = "confirm=false,"; 604 elseif (destination_prompt == "1") then 605 group_confirm = "group_confirm_key=exec,group_confirm_file=lua ".. scripts_dir:gsub('\\','/') .."/confirm.lua,confirm=true,"; 606 elseif (destination_prompt == "2") then 607 group_confirm = "group_confirm_key=exec,group_confirm_file=lua ".. scripts_dir:gsub('\\','/') .."/confirm.lua,confirm=true,"; 608 else 609 group_confirm = "confirm=false,"; 610 end 611 612 --get user_record value and determine whether to record the session 613 cmd = "user_data ".. destination_number .."@"..domain_name.." var user_record"; 614 user_record = trim(api:executeString(cmd)); 615 --set the record_session variable 616 record_session = false; 617 if (user_record == "all") then 618 record_session = true; 619 end 620 if (user_record == "inbound" and call_direction == "inbound") then 621 record_session = true; 622 end 623 if (user_record == "outbound" and call_direction == "outbound") then 624 record_session = true; 625 end 626 if (user_record == "local" and call_direction == "local") then 627 record_session = true; 628 end 629 630 --record the session 631 if (record_session) then 632 record_session = ",api_on_answer='uuid_record "..uuid.." start ".. record_path .. "/" .. record_name .. "',record_path='".. record_path .."',record_name="..record_name; 633 else 634 record_session = "" 635 end 636 row.record_session = record_session 637 638 --process according to user_exists, sip_uri, external number 639 if (user_exists == "true") then 640 --get the extension_uuid 641 cmd = "user_data ".. destination_number .."@"..domain_name.." var extension_uuid"; 642 extension_uuid = trim(api:executeString(cmd)); 643 --send to user 644 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; 645 dial_string = dial_string_to_user; 646 elseif (tonumber(destination_number) == nil) then 647 --sip uri 648 dial_string = "[sip_invite_domain="..domain_name..",call_direction="..call_direction..","..group_confirm.."leg_timeout="..destination_timeout..","..delay_name.."="..destination_delay.."]" .. row.destination_number; 649 else 650 --external number or direct dial 651 dial_string = nil 652 653 --prepare default actions 654 local confirm = string.gsub(group_confirm, ',$', '') -- remove `,` from end of string 655 local route = { -- predefined actions 656 "domain_name=${domain_name}", 657 "domain_uuid=${domain_uuid}", 658 "sip_invite_domain=${domain_name}", 659 "call_direction=${call_direction}", 660 "leg_timeout=${destination_timeout}", 661 delay_name .. "=${destination_delay}", 662 "ignore_early_media=true", 663 confirm, 664 } 665 666 --prepare default variables 667 local session_mt = {__index = function(_, k) return session:getVariable(k) end} 668 local params = setmetatable({ 669 __api__ = api, 670 destination_number = destination_number, 671 user_exists = 'false', 672 call_direction = 'outbound', 673 domain_name = domain_name, 674 domain_uuid = domain_uuid, 675 destination_timeout = destination_timeout, 676 destination_delay = destination_delay, 677 toll_allow = toll_allow, 678 }, session_mt) 679 680 --find destination route 681 if (tonumber(destination_number) == nil) then 682 --user define direct destination like `[key=value]sofia/gateway/carrier/123456` 683 local variables, destination = string.match(destination_number, "^%[(.-)%](.+)$") 684 if not variables then 685 destination = destination_number 686 else 687 for action in split_vars_pairs(variables) do 688 route[#route + 1] = action 689 end 690 end 691 route = route_to_bridge.apply_vars(route, params) 692 route.bridge = destination 693 else 694 --user define external number as destination 695 route = route_to_bridge.apply_vars(route, params) 696 route = route_to_bridge(dialplans, domain_uuid, params, route) 697 end 698 699 --build the dial string 700 if route and route.bridge then 701 local remove_actions = { 702 ["effective_caller_id_name="] = true; 703 ["effective_caller_id_number="] = true; 704 ['sip_h_X-accountcode='] = true; 705 } 706 707 -- cleanup variables 708 local i = 1 while i < #route do 709 -- remove vars from prev variant 710 if remove_actions[ route[i] ] then 711 table.remove(route, i) 712 i = i - 1 713 -- remove vars with unresolved vars 714 elseif string.find(route[i], '%${.+}') then 715 table.remove(route, i) 716 i = i - 1 717 -- remove vars with empty values 718 elseif string.find(route[i], '=$') then 719 table.remove(route, i) 720 i = i - 1 721 end 722 i = i + 1 723 end 724 725 --set the caller id 726 caller_id = ''; 727 if (ring_group_caller_id_name ~= nil) then 728 caller_id = "origination_caller_id_name='"..ring_group_caller_id_name.."'" 729 end 730 if (ring_group_caller_id_number ~= nil) then 731 caller_id = caller_id .. ",origination_caller_id_number="..ring_group_caller_id_number..","; 732 end 733 734 --set the destination dial string 735 dial_string = '['.. caller_id .. table.concat(route, ',') .. ']' .. route.bridge 736 end 737 end 738 739 --add a delimiter between destinations 740 if (dial_string ~= nil) then 741 --freeswitch.consoleLog("notice", "[ring group] dial_string: " .. dial_string .. "\n"); 742 if (x == 0) then 743 if (ring_group_strategy == "enterprise") then 744 app_data = dial_string; 745 else 746 app_data = "{ignore_early_media=true}"..dial_string; 747 end 748 else 749 if (app_data == nil) then 750 if (ring_group_strategy == "enterprise") then 751 app_data = dial_string; 752 else 753 app_data = "{ignore_early_media=true}"..dial_string; 754 end 755 else 756 app_data = app_data .. delimiter .. dial_string; 757 end 758 end 759 end 760 761 --increment the value of x 762 x = x + 1; 763 end 764 765 --session execute 766 if (session:ready()) then 767 --set the variables 768 session:execute("set", "hangup_after_bridge=true"); 769 session:execute("set", "continue_on_fail=true"); 770 771 -- support conf-xfer feature 772 -- do 773 -- local uuid = api:executeString("create_uuid") 774 -- session:execute("export", "conf_xfer_number=xfer-" .. uuid .. "-" .. domain_name) 775 -- end 776 --set bind digit action 777 local bind_target = 'peer' 778 if session:getVariable("sip_authorized") == "true" then 779 bind_target = 'both'; 780 end 781 local bindings = { 782 "local,*2,exec:record_session," .. record_path .. "/" .. record_name, 783 -- "local,*0,exec:execute_extension,conf_xfer_from_dialplan XML conf-xfer@" .. context 784 } 785 for _, str in ipairs(bindings) do 786 session:execute("bind_digit_action", str .. "," .. bind_target) 787 end 788 session:execute("digit_action_set_realm", "local"); 789 790 --if the user is busy rollover to the next destination 791 if (ring_group_strategy == "rollover") then 792 x = 0; 793 app_data = '{ignore_early_media=true}'; 794 for key, row in pairs(destinations) do 795 --set the values from the database as variables 796 user_exists = row.user_exists; 797 destination_number = row.destination_number; 798 domain_name = row.domain_name; 799 800 --get the extension_uuid 801 if (user_exists == "true") then 802 cmd = "user_data ".. destination_number .."@"..domain_name.." var extension_uuid"; 803 extension_uuid = trim(api:executeString(cmd)); 804 end 805 806 --if the timeout was reached go to the timeout action 807 if (x > 0) then 808 if (session:getVariable("originate_disposition") == "ALLOTTED_TIMEOUT" 809 or session:getVariable("originate_disposition") == "NO_ANSWER" 810 or session:getVariable("originate_disposition") == "NO_USER_RESPONSE") then 811 break; 812 end 813 end 814 815 --send the call to the destination 816 if (user_exists == "true") then 817 dial_string = "["..group_confirm.."sip_invite_domain="..domain_name..",leg_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; 818 elseif (tonumber(destination_number) == nil) then 819 dial_string = "["..group_confirm.."sip_invite_domain="..domain_name..",call_timeout="..destination_timeout..",call_direction=outbound,domain_name="..domain_name..",domain_uuid="..domain_uuid.."]" .. destination_number; 820 else 821 dial_string = "["..group_confirm.."sip_invite_domain="..domain_name..",call_timeout="..destination_timeout..",domain_name="..domain_name..",domain_uuid="..domain_uuid..",call_direction=outbound]loopback/" .. destination_number; 822 end 823 824 --add the delimiter 825 if (x == 0) then 826 app_data = app_data .. dial_string; 827 else 828 app_data = app_data .. delimiter .. dial_string; 829 end 830 831 --increment the value of x 832 x = x + 1; 833 end 834 end 835 836 --execute the bridge 837 if (app_data ~= nil) then 838 if (ring_group_strategy == "enterprise") then 839 app_data = app_data:gsub("%[", "{"); 840 app_data = app_data:gsub("%]", "}"); 841 end 842 freeswitch.consoleLog("NOTICE", "[ring group] app_data: "..app_data.."\n"); 843 -- 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") 844 session:execute("bridge", app_data); 845 -- 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") 846 end 847 848 --timeout destination 849 if (app_data ~= nil) then 850 if session:ready() and ( 851 session:getVariable("originate_disposition") == "ALLOTTED_TIMEOUT" 852 or session:getVariable("originate_disposition") == "NO_ANSWER" 853 or session:getVariable("originate_disposition") == "NO_USER_RESPONSE" 854 or session:getVariable("originate_disposition") == "USER_NOT_REGISTERED" 855 or session:getVariable("originate_disposition") == "NORMAL_TEMPORARY_FAILURE" 856 or session:getVariable("originate_disposition") == "NO_ROUTE_DESTINATION" 857 or session:getVariable("originate_disposition") == "USER_BUSY" 858 or session:getVariable("originate_disposition") == "RECOVERY_ON_TIMER_EXPIRE" 859 or session:getVariable("originate_disposition") == "failure" 860 ) then 861 --execute the time out action 862 if ring_group_timeout_app and #ring_group_timeout_app > 0 then 863 session:execute(ring_group_timeout_app, ring_group_timeout_data); 864 end 865 end 866 else 867 if (ring_group_timeout_app ~= nil) then 868 --execute the time out action 869 if ring_group_timeout_app and #ring_group_timeout_app > 0 then 870 session:execute(ring_group_timeout_app, ring_group_timeout_data); 871 end 872 else 873 local sql = "SELECT ring_group_timeout_app, ring_group_timeout_data FROM v_ring_groups "; 874 sql = sql .. "where ring_group_uuid = :ring_group_uuid"; 875 local params = {ring_group_uuid = ring_group_uuid}; 876 if debug["sql"] then 877 freeswitch.consoleLog("notice", "[ring group] SQL:" .. sql .. "; params:" .. json.encode(params) .. "\n"); 878 end 879 dbh:query(sql, params, function(row) 880 --execute the time out action 881 if row.ring_group_timeout_app and #row.ring_group_timeout_app > 0 then 882 session:execute(row.ring_group_timeout_app, row.ring_group_timeout_data); 883 end 884 end); 885 end 886 end 887 end 888 889 end 890 891--actions 892 --ACTIONS = {} 893 --table.insert(ACTIONS, {"set", "hangup_after_bridge=true"}); 894 --table.insert(ACTIONS, {"set", "continue_on_fail=true"}); 895 --table.insert(ACTIONS, {"bridge", app_data}); 896 --table.insert(ACTIONS, {ring_group_timeout_app, ring_group_timeout_data}); 897