-- conference.lua -- Part of FusionPBX -- Copyright (C) 2013 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; --connect to the database dofile(scripts_dir.."/resources/functions/database_handle.lua"); dbh = database_handle('system'); --prepare the api object api = freeswitch.API(); --general functions dofile(scripts_dir.."/resources/functions/base64.lua"); dofile(scripts_dir.."/resources/functions/trim.lua"); dofile(scripts_dir.."/resources/functions/file_exists.lua"); dofile(scripts_dir.."/resources/functions/explode.lua"); dofile(scripts_dir.."/resources/functions/format_seconds.lua"); dofile(scripts_dir.."/resources/functions/mkdir.lua"); --get the session variables uuid = session:getVariable("uuid"); --answer the call session:answer(); --define a function to send email function send_email(email, attachment, default_language, default_dialect) --require the email address to send the email if (string.len(email) > 2) then --format the message length and date --message_length_formatted = format_seconds(message_length); --if (debug["info"]) then -- freeswitch.consoleLog("notice", "[conference_center] message length: " .. message_length .. "\n"); --end local conference_date_end = os.date("%A, %d %b %Y %I:%M %p"); --os.time(); --prepare the files file_subject = scripts_dir.."/app/conference_center/resources/templates/"..default_language.."/"..default_dialect.."email_subject.tpl"; file_body = scripts_dir.."/app/conference_center/resources/templates/"..default_language.."/"..default_dialect.."/email_body.tpl"; if (not file_exists(file_subject)) then file_subject = scripts_dir.."/app/conference_center/resources/templates/en/us/email_subject.tpl"; file_body = scripts_dir.."/app/conference_center/resources/templates/en/us/email_body.tpl"; end --get the moderator_pin sql = [[SELECT moderator_pin FROM v_meetings WHERE meeting_uuid = ']] .. meeting_uuid ..[[']]; freeswitch.consoleLog("notice", "[voicemail] sql: " .. sql .. "\n"); status = dbh:query(sql, function(row) moderator_pin = string.lower(row["moderator_pin"]); end); --prepare the subject local f = io.open(file_subject, "r"); local subject = f:read("*all"); f:close(); subject = subject:gsub("${moderator_pin}", moderator_pin); subject = subject:gsub("${conference_date_end}", conference_date_end); --subject = subject:gsub("${conference_duration}", message_length_formatted); subject = subject:gsub("${domain_name}", domain_name); subject = trim(subject); subject = '=?utf-8?B?'..base64.enc(subject)..'?='; --prepare the body local f = io.open(file_body, "r"); local body = f:read("*all"); f:close(); body = body:gsub("${moderator_pin}", moderator_pin); body = body:gsub("${conference_date_end}", conference_date_end); body = body:gsub("${conference_uuid}", conference_session_uuid); --body = body:gsub("${conference_duration}", message_length_formatted); body = body:gsub("${domain_name}", domain_name); 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 if (string.len(attachment) > 4) then cmd = "luarun email.lua "..email.." "..email.." '"..subject.."' '"..body.."' '"..attachment.."'"; else cmd = "luarun email.lua "..email.." "..email.." '"..subject.."' '"..body.."'"; end if (debug["info"]) then freeswitch.consoleLog("notice", "[voicemail] cmd: " .. cmd .. "\n"); end result = api:executeString(cmd); end 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"); default_language = session:getVariable("default_language"); default_dialect = session:getVariable("default_dialect"); --recording = session:getVariable("recording"); --set the end epoch end_epoch = os.time(); --connect to the database dofile(scripts_dir.."/resources/functions/database_handle.lua"); dbh = database_handle('system'); --get the conference sessions if (conference_session_uuid) then 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 (tonumber(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, "); --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, "profile "); 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 .."', "); --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, "'".. profile .."' "); table.insert(sql, ") "); SQL_STRING = table.concat(sql, "\n"); dbh:query(SQL_STRING); freeswitch.consoleLog("notice", "[conference] SQL: " .. SQL_STRING .. "\n"); end end --add the conference sessions details if (conference_session_uuid) then 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 --if the conference is empty if (conference_session_uuid) then cmd = "conference "..meeting_uuid.."-"..domain_name.." xml_list"; result = trim(api:executeString(cmd)); if (string.sub(result, -9) == "not found") then --get the conference start_epoch sql = [[SELECT start_epoch FROM v_conference_session_details WHERE conference_session_uuid = ']] .. conference_session_uuid ..[[' ORDER BY start_epoch ASC LIMIT 1]]; status = dbh:query(sql, function(row) start_epoch = string.lower(row["start_epoch"]); end); freeswitch.consoleLog("notice", "[conference] sql: " .. sql .. "\n"); --set the conference_recording conference_recording = recordings_dir.."/archive/"..os.date("%Y", start_epoch).."/"..os.date("%b", start_epoch).."/"..os.date("%d", start_epoch) .."/"..conference_session_uuid; --conference has ended set the end_epoch local sql = {} table.insert(sql, "update v_conference_sessions set "); table.insert(sql, "recording = '".. conference_recording .."', "); table.insert(sql, "start_epoch = '".. start_epoch .."', "); table.insert(sql, "end_epoch = '".. end_epoch .."' "); table.insert(sql, "where conference_session_uuid = '"..conference_session_uuid.."' "); SQL_STRING = table.concat(sql, "\n"); freeswitch.consoleLog("notice", "[conference] SQL: " .. SQL_STRING .. "\n"); dbh:query(SQL_STRING); --convert the wav to an mp3 if (record == "true") then --cmd = "sox "..conference_recording..".wav -r 16000 -c 1 "..conference_recording..".mp3"; cmd = "/usr/bin/lame -b 32 --resample 8 -a "..conference_recording..".wav "..conference_recording..".mp3"; freeswitch.consoleLog("notice", "[conference] cmd: " .. cmd .. "\n"); os.execute(cmd); --if (file_exists(conference_recording..".mp3")) then -- cmd = "rm "..conference_recording..".wav"; -- os.execute(cmd); --end end --send the email addresses sql = [[SELECT c.contact_email FROM v_users as u, v_meeting_users as m, v_contacts as c WHERE m.domain_uuid = ']] .. domain_uuid ..[[' AND u.user_uuid = m.user_uuid AND m.meeting_uuid = ']] .. meeting_uuid ..[[' and u.contact_uuid = c.contact_uuid]]; if (debug["sql"]) then freeswitch.consoleLog("notice", "[conference] SQL: " .. sql .. "\n"); end status = dbh:query(sql, function(row) if (row["contact_email"] ~= nil) then contact_email = string.lower(row["contact_email"]); if (string.len(contact_email) > 3) then freeswitch.consoleLog("notice", "[conference] contact_email: " .. contact_email .. "\n"); if (record == "true") then if (file_exists(conference_recording..".wav")) then send_email(contact_email, "", default_language, default_dialect); end end end end end); end end --close the database connection if (conference_session_uuid) then dbh:release(); end end --make sure the session is ready if (session:ready()) then --answer the call session:answer(); --set the hangup hook function session:setHangupHook("session_hangup_hook"); --get session variables 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://v=-20;%(100,1000,100);v=-20;%(90,60,440);%(90,60,620)"; exit_sound = "tone_stream://v=-20;%(90,60,620);/%(90,60,440)"; --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"); --get the pin number function get_pin_number(domain_uuid) --if the pin number is provided then require it if (not pin_number) then min_digits = 3; max_digits = 12; max_tries = 1; digit_timeout = 5000; pin_number = session:playAndGetDigits(min_digits, max_digits, max_tries, digit_timeout, "#", sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/conference/conf-pin.wav", "", "\\d+"); end if (pin_number ~= "") then sql = [[SELECT * FROM v_conference_rooms as r, v_meetings as m WHERE r.domain_uuid = ']] .. domain_uuid ..[[' AND r.meeting_uuid = m.meeting_uuid AND m.domain_uuid = ']] .. domain_uuid ..[[' AND (m.moderator_pin = ']] .. pin_number ..[[' or m.participant_pin = ']] .. pin_number ..[[') AND r.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"]); end); end if (conference_room_uuid == nil) then return nil; else return pin_number; end end --get the pin pin_number = session:getVariable("pin_number"); if (not pin_number) then pin_number = nil; pin_number = get_pin_number(domain_uuid); end if (pin_number == nil) then pin_number = get_pin_number(domain_uuid); end if (pin_number == nil) then session:streamFile(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/conference/conf-bad-pin.wav"); pin_number = get_pin_number(domain_uuid); end if (pin_number == nil) then session:streamFile(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/conference/conf-bad-pin.wav"); pin_number = get_pin_number(domain_uuid); end if (pin_number ~= nil) then sql = [[SELECT * FROM v_conference_rooms as r, v_meetings as m WHERE r.domain_uuid = ']] .. domain_uuid ..[[' AND r.meeting_uuid = m.meeting_uuid AND m.domain_uuid = ']] .. domain_uuid ..[[' AND (m.moderator_pin = ']] .. pin_number ..[[' or m.participant_pin = ']] .. pin_number ..[[') AND r.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"]; return pin_number; end); freeswitch.consoleLog("INFO","conference_room_uuid: " .. conference_room_uuid .. "\n"); end --close the database connection dbh:release(); --set the meeting uuid if (meeting_uuid) then session:setVariable("meeting_uuid", meeting_uuid); end if (conference_center_uuid == nil) then --invalid pin number session:streamFile(sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/conference/conf-bad-pin.wav"); 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 --check if the conference is locked if (string.find(result, [[locked="true"]]) == nil) then conference_locked = false; else conference_locked = 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 (conference_locked) then announce = "false"; end 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 = "3"; 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 profile = "wait_mod"; 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 (wait_mod == "true") then if (member_type == "participant") then flags = flags .. "wait-mod"; end end 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 --get the conference xml_list cmd = "conference "..meeting_uuid.."-"..domain_name.." xml_list"; freeswitch.consoleLog("INFO","" .. cmd .. "\n"); result = trim(api:executeString(cmd)); --get the content to the tag result = string.match(result,[[]],1); --get the uuid out of the xml tag contents if (result ~= nil) then conference_session_uuid = string.match(result,[[uuid="(.-)"]],1); end --log entry if (conference_session_uuid ~= nil) then freeswitch.consoleLog("INFO","conference_session_uuid: " .. conference_session_uuid .. "\n"); end --set the start epoch start_epoch = os.time(); --set the recording variable if (conference_session_uuid ~= nil) then if (record == "true") then recordings_dir = recordings_dir.."/archive/"..os.date("%Y", start_epoch).."/"..os.date("%b", start_epoch).."/"..os.date("%d", start_epoch); mkdir(recordings_dir); recording = recordings_dir.."/"..conference_session_uuid; session:execute("set","recording="..recording); end end --record the conference if (record == "true") then --play a message that the conference is being a recorded session:execute("playback", sounds_dir.."/"..default_language.."/"..default_dialect.."/"..default_voice.."/ivr/ivr-recording_started.wav"); --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); --record the conference when it exists if (conference_exists) then --send a command to record the conference if (not file_exists(recording..".wav")) then cmd = "conference "..meeting_uuid.."-"..domain_name.." record "..recording..".wav"; freeswitch.consoleLog("notice", "[conference] cmd: " .. cmd .. "\n"); response = api:executeString(cmd); end 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 (not conference_locked) then if (sounds == "true") then cmd = "conference "..meeting_uuid.."-"..domain_name.." play "..enter_sound; response = api:executeString(cmd); end end end --close the database connection dbh:release(); --send the call to the conference profile = "default"; cmd = meeting_uuid.."-"..domain_name.."@"..profile.."+flags{".. flags .."}"; session:execute("conference", cmd); end end