diff --git a/resources/install/scripts/ivr_menu.lua b/resources/install/scripts/ivr_menu.lua index 7f8b887d8c..09c83397fc 100644 --- a/resources/install/scripts/ivr_menu.lua +++ b/resources/install/scripts/ivr_menu.lua @@ -38,8 +38,12 @@ require "resources.functions.database_handle"; dbh = database_handle('system'); +--get logger + local log = require "resources.functions.log".ivr_menu + --include functions require "resources.functions.format_ringback" + require "resources.functions.split" --get the variables domain_name = session:getVariable("domain_name"); @@ -49,6 +53,8 @@ caller_id_number = session:getVariable("caller_id_number"); domain_uuid = session:getVariable("domain_uuid"); + local recordings_dir = recordings_dir .. "/" .. domain_name + --settings require "resources.functions.settings"; settings = settings(domain_uuid); @@ -79,11 +85,6 @@ end end - ---set default variable(s) - tries = 0; - option_found = "false"; - --define the trim function require "resources.functions.trim" @@ -98,9 +99,9 @@ WHERE ivr_menu_uuid = ']] .. ivr_menu_uuid ..[[' AND ivr_menu_enabled = 'true' ]]; if (debug["sql"]) then - freeswitch.consoleLog("notice", "[ivr_menu] SQL: " .. sql .. "\n"); + log.notice("SQL: " .. sql); end - status = dbh:query(sql, function(row) + dbh:query(sql, function(row) domain_uuid = row["domain_uuid"]; ivr_menu_name = row["ivr_menu_name"]; --ivr_menu_extension = row["ivr_menu_extension"]; @@ -127,12 +128,10 @@ end); --set the caller id name - if (caller_id_name) then - if (string.len(ivr_menu_cid_prefix) > 0) then - caller_id_name = ivr_menu_cid_prefix .. "#" .. caller_id_name; - session:setVariable("caller_id_name", caller_id_name); - session:setVariable("effective_caller_id_name", caller_id_name); - end + if caller_id_name and #caller_id_name >0 then + caller_id_name = ivr_menu_cid_prefix .. "#" .. caller_id_name; + session:setVariable("caller_id_name", caller_id_name); + session:setVariable("effective_caller_id_name", caller_id_name); end --set ringback @@ -142,38 +141,22 @@ --get the sounds dir, language, dialect and voice sounds_dir = session:getVariable("sounds_dir"); - 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 + default_language = session:getVariable("default_language") or 'en'; + default_dialect = session:getVariable("default_dialect") or 'us'; + default_voice = session:getVariable("default_voice") or 'callie'; --make the path relative - if (string.sub(ivr_menu_greet_long,0,71) == "$${sounds_dir}/${default_language}/${default_dialect}/${default_voice}/") then - ivr_menu_greet_long = string.sub(ivr_menu_greet_long,72); - end - if (string.sub(ivr_menu_greet_short,0,71) == "$${sounds_dir}/${default_language}/${default_dialect}/${default_voice}/") then - ivr_menu_greet_short = string.sub(ivr_menu_greet_short,72); - end - if (string.sub(ivr_menu_invalid_sound,0,71) == "$${sounds_dir}/${default_language}/${default_dialect}/${default_voice}/") then - ivr_menu_invalid_sound = string.sub(ivr_menu_invalid_sound,72); - end - if (string.sub(ivr_menu_exit_sound,0,71) == "$${sounds_dir}/${default_language}/${default_dialect}/${default_voice}/") then - ivr_menu_exit_sound = string.sub(ivr_menu_exit_sound,72); - end + local strip_pattern = "^$${sounds_dir}/${default_language}/${default_dialect}/${default_voice}/" + ivr_menu_greet_long = string.gsub(ivr_menu_greet_long, strip_pattern, "") + ivr_menu_greet_short = string.gsub(ivr_menu_greet_short, strip_pattern, "") + ivr_menu_invalid_sound = string.gsub(ivr_menu_invalid_sound, strip_pattern, "") + ivr_menu_exit_sound = string.gsub(ivr_menu_exit_sound, strip_pattern, "") --parse file names - greet_long_file_name = ivr_menu_greet_long:match("([^/]+)$"); - greet_short_file_name = ivr_menu_greet_short:match("([^/]+)$"); - invalid_sound_file_name = ivr_menu_invalid_sound:match("([^/]+)$"); - exit_sound_file_name = ivr_menu_exit_sound:match("([^/]+)$"); - ---prevent nil concatenation errors - if (greet_long_file_name == nil) then greet_long_file_name = ""; end - if (greet_short_file_name == nil) then greet_short_file_name = ""; end - if (invalid_sound_file_name == nil) then invalid_sound_file_name = ""; end - if (exit_sound_file_name == nil) then exit_sound_file_name = ""; end + greet_long_file_name = ivr_menu_greet_long:match("([^/]+)$") or ""; + greet_short_file_name = ivr_menu_greet_short:match("([^/]+)$") or ""; + invalid_sound_file_name = ivr_menu_invalid_sound:match("([^/]+)$") or ""; + exit_sound_file_name = ivr_menu_exit_sound:match("([^/]+)$") or ""; --get the recordings from the database ivr_menu_greet_long_is_base64 = false; @@ -181,105 +164,66 @@ ivr_menu_invalid_sound_is_base64 = false; ivr_menu_exit_sound_is_base64 = false; if (storage_type == "base64") then + + --add functions + require "resources.functions.base64"; + require "resources.functions.mkdir"; + + --make sure the recordings directory exists + mkdir(recordings_dir); + + --define function to load file from db + local function load_file(file_name) + local full_path = recordings_dir .. "/" .. file_name + if file_exists(full_path) then + return full_path + end + + local sql = [[SELECT * FROM v_recordings WHERE domain_uuid = ']]..domain_uuid.. + [['AND recording_filename = ']]..file_name..[[' ]]; + + if (debug["sql"]) then + log.notice("SQL: "..sql); + end + + local is_base64 + dbh:query(sql, function(row) + if #row.recording_base64 > 32 then + local file, err = io.open(full_path, "w"); + if not file then + log.err("can not create file: "..full_path.."; Error - " .. tostring(err)); + return + end + file:write(base64.decode(row.recording_base64)); + file:close(); + is_base64 = true; + end + end); + + -- return path in any case + return full_path, is_base64 + end + --greet long - if (string.len(ivr_menu_greet_long) > 1) then - if (not file_exists(recordings_dir.."/"..domain_name.."/"..greet_long_file_name)) then - sql = [[SELECT * FROM v_recordings - WHERE domain_uuid = ']]..domain_uuid..[[' - AND recording_filename = ']]..greet_long_file_name..[[' ]]; - if (debug["sql"]) then - freeswitch.consoleLog("notice", "[ivr_menu] SQL: "..sql.."\n"); - end - status = dbh:query(sql, function(row) - --add functions - require "resources.functions.base64"; - --make sure the recordings directory exists - require "resources.functions.mkdir"; - mkdir(recordings_dir.."/"..domain_name); - --add the path to filename - ivr_menu_greet_long = recordings_dir.."/"..domain_name.."/"..greet_long_file_name; - ivr_menu_greet_long_is_base64 = true; - --save the recording to the file system - if (string.len(row["recording_base64"]) > 32) then - local file = io.open(ivr_menu_greet_long, "w"); - file:write(base64.decode(row["recording_base64"])); - file:close(); - end - end); - end + if #ivr_menu_greet_long > 1 then + ivr_menu_greet_long, ivr_menu_greet_long_is_base64 = load_file(greet_long_file_name) end + --greet short - if (string.len(ivr_menu_greet_short) > 1) then - if (not file_exists(recordings_dir.."/"..domain_name.."/"..greet_short_file_name)) then - sql = [[SELECT * FROM v_recordings - WHERE domain_uuid = ']]..domain_uuid..[[' - AND recording_filename = ']]..greet_short_file_name..[[' ]]; - if (debug["sql"]) then - freeswitch.consoleLog("notice", "[ivr_menu] SQL: "..sql.."\n"); - end - status = dbh:query(sql, function(row) - --add functions - require "resources.functions.base64"; - --add the path to filename - ivr_menu_greet_short = recordings_dir.."/"..domain_name.."/"..greet_short_file_name; - ivr_menu_greet_short_is_base64 = true; - --save the recording to the file system - if (string.len(row["recording_base64"]) > 32) then - local file = io.open(ivr_menu_greet_short, "w"); - file:write(base64.decode(row["recording_base64"])); - file:close(); - end - end); - end + if #ivr_menu_greet_short > 1 then + ivr_menu_greet_short, ivr_menu_greet_short_is_base64 = load_file(greet_short_file_name) end + --invalid sound - if (string.len(ivr_menu_invalid_sound) > 1) then - if (not file_exists(recordings_dir.."/"..domain_name.."/"..invalid_sound_file_name)) then - sql = [[SELECT * FROM v_recordings - WHERE domain_uuid = ']]..domain_uuid..[[' - AND recording_filename = ']]..invalid_sound_file_name..[[' ]]; - if (debug["sql"]) then - freeswitch.consoleLog("notice", "[ivr_menu] SQL: "..sql.."\n"); - end - status = dbh:query(sql, function(row) - --add functions - require "resources.functions.base64"; - --add the path to filename - ivr_menu_invalid_sound = recordings_dir.."/"..domain_name.."/"..invalid_sound_file_name; - ivr_menu_invalid_sound_is_base64 = true; - --save the recording to the file system - if (string.len(row["recording_base64"]) > 32) then - local file = io.open(ivr_menu_invalid_sound, "w"); - file:write(base64.decode(row["recording_base64"])); - file:close(); - end - end); - end + if #ivr_menu_invalid_sound > 1 then + ivr_menu_invalid_sound, ivr_menu_invalid_sound_is_base64 = load_file(invalid_sound_file_name) end + --exit sound - if (string.len(ivr_menu_exit_sound) > 1) then - if (not file_exists(recordings_dir.."/"..domain_name.."/"..exit_sound_file_name)) then - sql = [[SELECT * FROM v_recordings - WHERE domain_uuid = ']]..domain_uuid..[[' - AND recording_filename = ']]..exit_sound_file_name..[[' ]]; - if (debug["sql"]) then - freeswitch.consoleLog("notice", "[ivr_menu] SQL: "..sql.."\n"); - end - status = dbh:query(sql, function(row) - --add functions - require "resources.functions.base64"; - --add the path to filename - ivr_menu_exit_sound = recordings_dir.."/"..domain_name.."/"..exit_sound_file_name; - ivr_menu_exit_sound_is_base64 = true; - --save the recording to the file system - if (string.len(row["recording_base64"]) > 32) then - local file = io.open(ivr_menu_exit_sound, "w"); - file:write(base64.decode(row["recording_base64"])); - file:close(); - end - end); - end + if #ivr_menu_exit_sound > 1 then + ivr_menu_exit_sound, ivr_menu_exit_sound_is_base64 = load_file(exit_sound_file_name) end + elseif (storage_type == "http_cache") then --add the path to file name ivr_menu_greet_long = storage_path.."/"..ivr_menu_greet_long; @@ -289,204 +233,196 @@ end --adjust file paths + local function adjust_file_path(full_path, file_name) + return file_exists(full_path) + or file_exists(recordings_dir.."/"..file_name) + or file_exists(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/"..file_name) + or full_path + end --greet long - if (not file_exists(ivr_menu_greet_long)) then - if (file_exists(recordings_dir.."/"..domain_name.."/"..greet_long_file_name)) then - ivr_menu_greet_long = recordings_dir.."/"..domain_name.."/"..greet_long_file_name; - elseif (file_exists(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/"..greet_long_file_name)) then - ivr_menu_greet_long = sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/"..greet_long_file_name; - end - end + ivr_menu_greet_long = adjust_file_path(ivr_menu_greet_long, greet_long_file_name) --greet short - if (string.len(ivr_menu_greet_short) > 1) then - if (not file_exists(ivr_menu_greet_short)) then - if (file_exists(recordings_dir.."/"..domain_name.."/"..greet_short_file_name)) then - ivr_menu_greet_short = recordings_dir.."/"..domain_name.."/"..greet_short_file_name; - elseif (file_exists(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/"..greet_short_file_name)) then - ivr_menu_greet_short = sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/"..greet_short_file_name; - end - end + if #ivr_menu_greet_short > 1 then + ivr_menu_greet_short = adjust_file_path(ivr_menu_greet_short, greet_short_file_name) else - ivr_menu_greet_short = ivr_menu_greet_long; + ivr_menu_greet_short = ivr_menu_greet_long end --invalid sound - if (not file_exists(ivr_menu_invalid_sound)) then - if (file_exists(recordings_dir.."/"..domain_name.."/"..invalid_sound_file_name)) then - ivr_menu_invalid_sound = recordings_dir.."/"..domain_name.."/"..invalid_sound_file_name; - elseif (file_exists(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/"..invalid_sound_file_name)) then - ivr_menu_invalid_sound = sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/"..invalid_sound_file_name; - end - end + ivr_menu_invalid_sound = adjust_file_path(ivr_menu_invalid_sound, invalid_sound_file_name) --exit sound - if (not file_exists(ivr_menu_exit_sound)) then - if (file_exists(recordings_dir.."/"..domain_name.."/"..exit_sound_file_name)) then - ivr_menu_exit_sound = recordings_dir.."/"..domain_name.."/"..exit_sound_file_name; - elseif (file_exists(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/"..exit_sound_file_name)) then - ivr_menu_exit_sound = sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/"..exit_sound_file_name; - end - end + ivr_menu_exit_sound = adjust_file_path(ivr_menu_exit_sound, exit_sound_file_name) --define the ivr menu + local menu_options, menu + local tries = 0; function menu() - --increment the tries - tries = tries + 1; - min_digits = 1; - session:setVariable("slept", "false"); - if (tries == 1) then - if (debug["tries"]) then - freeswitch.consoleLog("notice", "[ivr_menu] greet long: " .. ivr_menu_greet_long .. "\n"); + -- check number of failures + if (tries > 0) and (tries >= tonumber(ivr_menu_max_failures)) then + return end - --check if phrase - pos = string.find(ivr_menu_greet_long, ":", 0, true); - if (pos ~= nil and string.sub(ivr_menu_greet_long, 0, pos-1) == 'phrase') then - freeswitch.consoleLog("notice", "[ivr_menu] phrase detected\n"); - dtmf_digits = session:playAndGetDigits(min_digits, ivr_menu_digit_len, 1, ivr_menu_timeout, ivr_menu_confirm_key, ivr_menu_greet_long, "", ".*"); + + -- increment the tries + tries = tries + 1; + + --log the dtmf digits + if (debug["tries"]) then + log.noticef("tries: %d/%d", tries, tonumber(ivr_menu_max_failures) or '-1'); + end + + -- set the minimum dtmf lengts + local min_digits = 1; + + -- set sound file and number of attempts + local sound, sound_type, attempts + + if tries == 1 then + sound, sound_type, attempts = ivr_menu_greet_long or "", "long", 1 + else + sound, sound_type, attempts = ivr_menu_greet_short or "", "short", tonumber(ivr_menu_max_timeouts) or 3 + end + + if (debug["tries"]) then + log.notice("greet " .. sound_type .. ": " .. sound); + end + + -- read dtmf + local dtmf_digits + if attempts > 0 then + dtmf_digits = session:playAndGetDigits(min_digits, ivr_menu_digit_len, attempts, ivr_menu_timeout, ivr_menu_confirm_key, sound, "", ".*"); + -- need pause before stream file session:setVariable("slept", "false"); - else - dtmf_digits = session:playAndGetDigits(min_digits, ivr_menu_digit_len, 1, ivr_menu_timeout, ivr_menu_confirm_key, ivr_menu_greet_long, "", ".*"); end - else - if (debug["tries"]) then - freeswitch.consoleLog("notice", "[ivr_menu] greet long: " .. ivr_menu_greet_short .. "\n"); + + -- proceed dtmf + if dtmf_digits and #dtmf_digits > 0 then + if (debug["tries"]) then + log.notice("dtmf_digits: " .. dtmf_digits); + end + return menu_options(session, dtmf_digits); end - dtmf_digits = session:playAndGetDigits(min_digits, ivr_menu_digit_len, ivr_menu_max_timeouts, ivr_menu_timeout, ivr_menu_confirm_key, ivr_menu_greet_short, "", ".*"); - end - if (dtmf_digits ~= nil and string.len(dtmf_digits) > 0) then - if (debug["tries"]) then - freeswitch.consoleLog("notice", "[ivr_menu] dtmf_digits: " .. dtmf_digits .. "\n"); - end - menu_options(session, dtmf_digits); - else - if (tries < tonumber(ivr_menu_max_failures)) then - --log the dtmf digits - if (debug["tries"]) then - freeswitch.consoleLog("notice", "[ivr_menu] tries: " .. tries .. "\n"); - end - --run the menu again - menu(); - end - end + + return menu(); end function menu_options(session, digits) --log the dtmf digits if (debug["dtmf"]) then - freeswitch.consoleLog("notice", "[ivr_menu] dtmf: " .. digits .. "\n"); + log.notice("dtmf: " .. digits); end --get the ivr menu options sql = [[SELECT * FROM v_ivr_menu_options WHERE ivr_menu_uuid = ']] .. ivr_menu_uuid ..[[' ORDER BY ivr_menu_option_order asc ]]; if (debug["sql"]) then - freeswitch.consoleLog("notice", "[ivr_menu] SQL: " .. sql .. "\n"); + log.notice("SQL: " .. sql); end - status = dbh:query(sql, function(row) + + local actions = {} + dbh:query(sql, function(row) + -- declare vars + local action, script, data + --check for matching options - if (tonumber(row.ivr_menu_option_digits) ~= nil) then + if tonumber(row.ivr_menu_option_digits) then row.ivr_menu_option_digits = "^"..row.ivr_menu_option_digits.."$"; end - if (api:execute("regex", "m:~"..digits.."~"..row.ivr_menu_option_digits) == "true") then - if (row.ivr_menu_option_action == "menu-exec-app") then - --get the action and data - pos = string.find(row.ivr_menu_option_param, " ", 0, true); - if pos then - action = string.sub(row.ivr_menu_option_param, 0, pos-1); - data = string.sub(row.ivr_menu_option_param, pos+1); - else - action, data = row.ivr_menu_option_param, "" - end - --check if the option uses a regex - regex = string.find(row.ivr_menu_option_digits, "(", 0, true); - if (regex) then - --get the regex result - result = trim(api:execute("regex", "m:~"..digits.."~"..row.ivr_menu_option_digits.."~$1")); - if (debug["regex"]) then - freeswitch.consoleLog("notice", "[ivr_menu] regex m:~"..digits.."~"..row.ivr_menu_option_digits.."~$1\n"); - freeswitch.consoleLog("notice", "[ivr_menu] result: "..result.."\n"); - end - - --replace the $1 and the domain name - data = data:gsub("$1", result); - data = data:gsub("${domain_name}", domain_name); - end --if regex - end --if menu-exex-app - if (row.ivr_menu_option_action == "phrase") then - action = 'phrase'; - data = row.ivr_menu_option_param; - end - if (action == "lua") then - pos = string.find(data, " ", 0, true); - if pos then - script = string.sub(data, 0, pos-1); - else - script = data - end - end - end --if regex match - - --execute - if (action) then - if (string.len(action) > 0) then - --option found - option_found = "true"; - - --send to the log - if (debug["action"]) then - freeswitch.consoleLog("notice", "[ivr_menu] action: " .. action .. " data: ".. data .. "\n"); - end - --run the action - if (action == 'phrase' or (script ~= nil and script == 'streamfile.lua')) then - session:execute(action, data); - menu(); - else - if (ivr_menu_exit_sound ~= nil) then - session:streamFile(ivr_menu_exit_sound); - end - session:execute(action, data); - end - end + if api:execute("regex", "m:~"..digits.."~"..row.ivr_menu_option_digits) ~= "true" then + return end - --clear the variables - action = ""; - data = ""; + if row.ivr_menu_option_action == "menu-exec-app" then + --get the action and data + action, data = split_first(row.ivr_menu_option_param, ' ', true) + data = data or "" + + --check if the option uses a regex + local regex = string.find(row.ivr_menu_option_digits, "(", 0, true); + if regex then + --get the regex result + regex = "m:~"..digits.."~"..row.ivr_menu_option_digits.."~$1" + local result = trim(api:execute("regex", regex)); + if (debug["regex"]) then + log.notice("regex "..regex); + log.notice("result: "..result); + end + + --replace the $1 and the domain name + data = data:gsub("$1", result); + data = data:gsub("${domain_name}", domain_name); + end --if regex + end --if menu-exex-app + + if row.ivr_menu_option_action == "phrase" then + action = 'phrase'; + data = row.ivr_menu_option_param; + end + + if action == "lua" then + script = split_first(data, " ", true) + end + + -- break loop + if action and #action > 0 then + actions[#actions + 1] = {action, script, data} + return + end + + -- we have unsupported IVR action + log.warning("invalid action in ivr: " .. row.ivr_menu_option_action); end); --end results - --direct dial - if (ivr_menu_direct_dial == "true") then - if (string.len(digits) < 6 and option_found == "false") then - --replace the $1 and the domain name - digits = digits:gsub("*", ""); - --check to see if the user extension exists - cmd = "user_exists id ".. digits .." "..domain_name; - result = api:executeString(cmd); - freeswitch.consoleLog("NOTICE", "[ivr_menu][direct dial] "..cmd.." "..result.."\n"); - if (result == "true") then - --log the action - freeswitch.consoleLog("NOTICE", "[ivr_menu][direct dial] "..digits.." XML "..context.."\n"); - --run the action - session:execute("transfer", digits.." XML "..context); - else - --run the menu again - menu(); + --execute + if #actions > 0 then + for _, t in ipairs(actions) do + local action, script, data = t[1],t[2],t[3] + -- send to the log + if (debug["action"]) then + log.notice("action: " .. action .. " data: ".. data); + end + + -- run the action (with return to menu) + if action == 'phrase' or script == 'streamfile.lua' then + session:execute(action, data); + else + if ivr_menu_exit_sound and #ivr_menu_exit_sound > 0 then + session:streamFile(ivr_menu_exit_sound); end + -- run the action (without return to menu) + return session:execute(action, data); + end end + return menu(); end - --execute - if (action) then - if (string.len(action) == 0) then - session:streamFile(ivr_menu_invalid_sound); - menu(); - end - else - if (action ~= 'phrase' and (script == nil or script ~= 'streamfile.lua')) then - session:streamFile(ivr_menu_invalid_sound); - end - menu(); + --direct dial + if ivr_menu_direct_dial == "true" and #digits > 0 and #digits < 6 then + -- remove *# + digits = digits:gsub("[*#]", ""); + + -- check to see if the user extension exists + local cmd = "user_exists id ".. digits .." "..domain_name; + local result = api:executeString(cmd); + log.notice("[direct dial] "..cmd.." "..result); + if result == "true" then + --log the action + log.notice("[direct dial] "..digits.." XML "..context); + --run the action + return session:execute("transfer", digits.." XML "..context); + end + + --run the menu again (without play ivr_menu_invalid_sound) + return menu(); end - + + --invalid input try again + if (debug["action"]) then + log.notice("unrecgnized action"); + end + if ivr_menu_invalid_sound and #ivr_menu_invalid_sound then + session:streamFile(ivr_menu_invalid_sound); + end + return menu(); end --end function --answer the session