-- conference.lua -- Part of FusionPBX -- Copyright (C) 2012 Mark J Crane -- All rights reserved. -- -- Redistribution and use in source and binary forms, with or without -- modification, are permitted provided that the following conditions are met: -- -- 1. Redistributions of source code must retain the above copyright notice, -- this list of conditions and the following disclaimer. -- -- 2. Redistributions in binary form must reproduce the above copyright -- notice, this list of conditions and the following disclaimer in the -- documentation and/or other materials provided with the distribution. -- -- THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, -- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY -- AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -- AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, -- OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -- POSSIBILITY OF SUCH DAMAGE. --set variables flags = ""; max_tries = 3; digit_timeout = 5000; --include the lua script scripts_dir = string.sub(debug.getinfo(1).source,2,string.len(debug.getinfo(1).source)-(string.len(argv[0])+1)); include = assert(loadfile(scripts_dir .. "/resources/config.lua")); include(); --connect to the database --ODBC - data source name if (dsn_name) then dbh = freeswitch.Dbh(dsn_name,dsn_username,dsn_password); end --FreeSWITCH core db handler if (db_type == "sqlite") then dbh = freeswitch.Dbh("core:"..db_path.."/"..db_name); end --prepare the api object api = freeswitch.API(); --define the trim function function trim(s) return s:gsub("^%s+", ""):gsub("%s+$", "") end --define the session hangup function session_hangup_hook() --get the session variables conference_session_detail_uuid = api:executeString("create_uuid"); --conference_name = session:getVariable("conference_name"); conference_session_uuid = session:getVariable("conference_uuid"); conference_recording = session:getVariable("conference_recording"); conference_moderator = session:getVariable("conference_moderator"); --start_epoch = session:getVariable("start_epoch"); --set the epoch end_epoch = os.time(); --get the conference sessions sql = [[SELECT count(*) as num_rows FROM v_conference_sessions WHERE conference_session_uuid = ']] .. conference_session_uuid ..[[']]; status = dbh:query(sql, function(row) num_rows = string.lower(row["num_rows"]); end); freeswitch.consoleLog("notice", "[conference] SQL: " .. sql .. " Rows:"..num_rows.."\n"); if (num_rows == "0") then local sql = {} table.insert(sql, "INSERT INTO v_conference_sessions "); table.insert(sql, "("); table.insert(sql, "conference_session_uuid, "); table.insert(sql, "domain_uuid, "); table.insert(sql, "meeting_uuid, "); table.insert(sql, "profile, "); if (conference_recording) then table.insert(sql, "recording, "); end --if (wait_mod) then -- table.insert(sql, "wait_mod, "); --end table.insert(sql, "start_epoch, "); table.insert(sql, "end_epoch "); table.insert(sql, ") "); table.insert(sql, "VALUES "); table.insert(sql, "( "); table.insert(sql, "'".. conference_session_uuid .."', "); table.insert(sql, "'".. domain_uuid .."', "); table.insert(sql, "'".. meeting_uuid .."', "); table.insert(sql, "'".. profile .."', "); if (conference_recording) then table.insert(sql, "'".. conference_recording .."', "); end --if (wait_mod) then -- table.insert(sql, "'".. wait_mod .."', "); --end table.insert(sql, "'".. start_epoch .."', "); table.insert(sql, "'".. end_epoch .."' "); table.insert(sql, ") "); SQL_STRING = table.concat(sql, "\n"); dbh:query(SQL_STRING); freeswitch.consoleLog("notice", "[conference] SQL: " .. SQL_STRING .. "\n"); else freeswitch.consoleLog("notice", "[conference] number is greater than 0 \n"); end local sql = {} table.insert(sql, "INSERT INTO v_conference_session_details "); table.insert(sql, "("); table.insert(sql, "conference_session_detail_uuid, "); table.insert(sql, "domain_uuid, "); table.insert(sql, "conference_session_uuid, "); table.insert(sql, "meeting_uuid, "); table.insert(sql, "username, "); table.insert(sql, "caller_id_name, "); table.insert(sql, "caller_id_number, "); table.insert(sql, "network_addr, "); table.insert(sql, "uuid, "); if (conference_moderator) then table.insert(sql, "moderator, "); end table.insert(sql, "start_epoch, "); table.insert(sql, "end_epoch "); table.insert(sql, ") "); table.insert(sql, "VALUES "); table.insert(sql, "( "); table.insert(sql, "'".. conference_session_detail_uuid .."', "); table.insert(sql, "'".. domain_uuid .."', "); table.insert(sql, "'".. conference_session_uuid .."', "); table.insert(sql, "'".. meeting_uuid .."', "); table.insert(sql, "'".. username .."', "); table.insert(sql, "'".. caller_id_name .."', "); table.insert(sql, "'".. caller_id_number .."', "); table.insert(sql, "'".. network_addr .."', "); table.insert(sql, "'".. uuid .."', "); if (conference_moderator) then table.insert(sql, "'".. conference_moderator .."', "); end table.insert(sql, "'".. start_epoch .."', "); table.insert(sql, "'".. end_epoch .."' "); table.insert(sql, ") "); SQL_STRING = table.concat(sql, "\n"); dbh:query(SQL_STRING); end --make sure the session is ready if (session:ready()) then session:answer(); session:setHangupHook("session_hangup_hook"); sounds_dir = session:getVariable("sounds_dir"); hold_music = session:getVariable("hold_music"); domain_name = session:getVariable("domain_name"); pin_number = session:getVariable("pin_number"); --get the domain_uuid if (domain_name ~= nil) then sql = "SELECT domain_uuid FROM v_domains "; sql = sql .. "WHERE domain_name = '" .. domain_name .."' "; if (debug["sql"]) then freeswitch.consoleLog("notice", "[conference] SQL: " .. sql .. "\n"); end status = dbh:query(sql, function(rows) domain_uuid = string.lower(rows["domain_uuid"]); end); end --add the domain to the recording directory if (domain_count > 1) then recordings_dir = recordings_dir.."/"..domain_name; end --set the sounds path for the language, dialect and voice default_language = session:getVariable("default_language"); default_dialect = session:getVariable("default_dialect"); default_voice = session:getVariable("default_voice"); if (not default_language) then default_language = 'en'; end if (not default_dialect) then default_dialect = 'us'; end if (not default_voice) then default_voice = 'callie'; end --sounds enter_sound = "tone_stream://%(200,0,500,600,700)"; exit_sound = "tone_stream://%(500,0,300,200,100,50,25)"; --get the variables username = session:getVariable("username"); caller_id_name = session:getVariable("caller_id_name"); caller_id_number = session:getVariable("caller_id_number"); callee_id_name = session:getVariable("callee_id_name"); callee_id_number = session:getVariable("callee_id_number"); dialplan = session:getVariable("dialplan"); network_addr = session:getVariable("network_addr"); uuid = session:getVariable("uuid"); --context = session:getVariable("context"); chan_name = session:getVariable("chan_name"); --if the pin number is provided then require it if (not pin_number) then min_digits = 3; max_digits = 12; pin_number = session:playAndGetDigits(min_digits, max_digits, max_tries, digit_timeout, "#", "phrase:voicemail_enter_pass:#", "", "\\d+"); end --get the conference sessions sql = [[SELECT * FROM v_conference_rooms as s, v_meeting_pins as p WHERE s.domain_uuid = ']] .. domain_uuid ..[[' AND s.meeting_uuid = p.meeting_uuid AND p.domain_uuid = ']] .. domain_uuid ..[[' AND p.member_pin = ']] .. pin_number ..[[' AND enabled = 'true' ]]; if (debug["sql"]) then freeswitch.consoleLog("notice", "[conference] SQL: " .. sql .. "\n"); end status = dbh:query(sql, function(row) conference_room_uuid = string.lower(row["conference_room_uuid"]); conference_center_uuid = string.lower(row["conference_center_uuid"]); meeting_uuid = string.lower(row["meeting_uuid"]); record = string.lower(row["record"]); profile = string.lower(row["profile"]); max_members = row["max_members"]; wait_mod = row["wait_mod"]; member_type = row["member_type"]; announce = row["announce"]; mute = row["mute"]; sounds = row["sounds"]; created = row["created"]; created_by = row["created_by"]; enabled = row["enabled"]; description = row["description"]; end); --set the meeting uuid session:setVariable("meeting_uuid", meeting_uuid); if (conference_center_uuid == nil) then --invalid pin number session:streamFile("phrase:voicemail_fail_auth:#"); session:hangup("NORMAL_CLEARING"); else --check if the conference exists cmd = "conference "..meeting_uuid.."-"..domain_name.." xml_list"; result = trim(api:executeString(cmd)); if (string.sub(result, -9) == "not found") then conference_exists = false; else conference_exists = true; end --set a conference parameter if (max_members ~= nil) then if (tonumber(max_members) > 0) then --max members must be 2 or more session:execute("set","conference_max_members="..max_members); if (conference_exists) then cmd = "conference "..meeting_uuid.."-"..domain_name.." get count"; count = trim(api:executeString(cmd)); if (count ~= nil) then if (tonumber(count) >= tonumber(max_members)) then session:execute("playback", sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/conference/conf-locked.wav"); session:hangup("CALL_REJECTED"); end end end end end --announce the caller if (announce == "true") then --prompt for the name of the caller session:execute("playback", sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/ivr/ivr-say_name.wav"); session:execute("playback", "tone_stream://v=-7;%%(500,0,500.0)"); --record the response max_len_seconds = 5; silence_threshold = "500"; silence_secs = "2"; session:recordFile("/tmp/conference-"..uuid..".wav", max_len_seconds, silence_threshold, silence_secs); end --play a message that the conference is being a recorded if (record == "true") then session:execute("playback", sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/ivr/ivr-recording_started.wav"); end --wait for moderator if (wait_mod == "true") then if (conference_exists) then --continue else if (member_type == "participant") then --set music on hold variable cmd = "uuid_setvar "..uuid.." hold_music "..hold_music; result = api:executeString(cmd); --put the call on hold --cmd = "uuid_hold "..uuid; --result = api:executeString(cmd); --loop until the conference exists x = 0; y = 0; while true do --sleep a moment to prevent using unecessary resources session:sleep(3000); --play a tone if (x > 2) then session:execute("playback", "tone_stream://L=2;%(25,200,440,40);"); --session:execute("playback", "ivr/ivr-stay_on_line_call_answered_momentarily.wav"); x = 1; end --check if the conference exists cmd = "conference "..meeting_uuid.."-"..domain_name.." xml_list"; result = trim(api:executeString(cmd)); if (string.sub(result, -9) == "not found") then --continue the loop else --set hold to off --cmd = "uuid_hold off "..uuid; --result = api:executeString(cmd); --exit the loop; break; end --end the conference when the limit is reached park_timeout_seconds = 1200; if (y > tonumber(park_timeout_seconds)) then break; end --increment the value of x and y x = x + 1; y = y + 1; end end end end --set the exit sound if (sounds == "true") then session:execute("set","conference_exit_sound="..exit_sound); end --set flags and moderator controls if (mute == "true") then if (member_type == "participant") then flags = flags .. "mute"; end end if (member_type == "moderator") then --set as the moderator flags = flags .. "|moderator"; --when the moderator leaves end the conference --flags = flags .. "|endconf"; --set the moderator controls session:execute("set","conference_controls=moderator"); end --set the start epoch start_epoch = os.time(); --get the session uuid if (record == "true") then --get the conference xml_list cmd = "conference "..meeting_uuid.."-"..domain_name.." xml_list"; result = trim(api:executeString(cmd)); if (conference_exists) then --get the content to the tag result = string.match(result,[[]],1); --get the uuid out of the xml tag contents conference_session_uuid = string.match(result,[[uuid="(.-)"]],1); --log entry freeswitch.consoleLog("INFO","conference_session_uuid: " .. conference_session_uuid .. "\n"); --record the conference cmd = "conference "..meeting_uuid.."-"..domain_name.." record "..recordings_dir.."/archive/"..os.date("%Y").."/"..os.date("%b").."/"..os.date("%d") .."/"..conference_session_uuid..".wav"; response = api:executeString(cmd); --play a message that the conference is being a recorded cmd = "conference "..meeting_uuid.."-"..domain_name.." play "..sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/ivr/ivr-recording_started.wav"; freeswitch.consoleLog("notice", "[conference] ".. cmd .."\n"); response = api:executeString(cmd); end end --announce the caller if (announce == "true") then --announce the caller - play the recording cmd = "conference "..meeting_uuid.."-"..domain_name.." play /tmp/conference-"..uuid..".wav"; freeswitch.consoleLog("notice", "[conference] ".. cmd .."\n"); response = api:executeString(cmd); --play has entered the conference cmd = "conference "..meeting_uuid.."-"..domain_name.." play "..sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/conference/conf-has_joined.wav"; freeswitch.consoleLog("notice", "[conference] ".. cmd .."\n"); response = api:executeString(cmd); else if (sounds == "true") then cmd = "conference "..meeting_uuid.."-"..domain_name.." play "..enter_sound; response = api:executeString(cmd); end end --send the call to the conference cmd = meeting_uuid.."-"..domain_name.."@"..profile.."+flags{".. flags .."}"; session:execute("conference", cmd); end end