summaryrefslogtreecommitdiff
path: root/misc/freeswitch
diff options
context:
space:
mode:
Diffstat (limited to 'misc/freeswitch')
-rw-r--r--misc/freeswitch/conf/freeswitch.xml2
-rw-r--r--misc/freeswitch/scripts/common/array.lua27
-rw-r--r--misc/freeswitch/scripts/common/conference.lua326
-rw-r--r--misc/freeswitch/scripts/configuration.lua2
-rw-r--r--misc/freeswitch/scripts/dialplan/dialplan.lua10
-rw-r--r--misc/freeswitch/scripts/dialplan/session.lua5
-rw-r--r--misc/freeswitch/scripts/event/presence_update.lua44
7 files changed, 234 insertions, 182 deletions
diff --git a/misc/freeswitch/conf/freeswitch.xml b/misc/freeswitch/conf/freeswitch.xml
index a2c2bf5..2c9b46a 100644
--- a/misc/freeswitch/conf/freeswitch.xml
+++ b/misc/freeswitch/conf/freeswitch.xml
@@ -470,7 +470,7 @@
</match>
</input>
</macro>
- <macro name="conference_welcome">
+ <macro name="conference_welcome">
<input pattern="^(.*)$">
<match>
<action function="play-file" data="conference/conf-welcome.wav"/>
diff --git a/misc/freeswitch/scripts/common/array.lua b/misc/freeswitch/scripts/common/array.lua
index b93ef69..5683dce 100644
--- a/misc/freeswitch/scripts/common/array.lua
+++ b/misc/freeswitch/scripts/common/array.lua
@@ -45,10 +45,35 @@ function expand_variable(variable_path, variable_sets)
end
end
-
+-- replace variables in a string by array values
function expand_variables(line, ...)
local variable_sets = {...};
return (line:gsub('{([%a%d%._]+)}', function(captured)
return expand_variable(captured, variable_sets);
end))
end
+
+
+-- concatenate array values
+function to_s(array, separator, prefix, suffix)
+ require 'common.str';
+
+ local buffer = '';
+ for key, value in pairs(array) do
+ buffer = common.str.append(buffer, value, separator, prefix, suffix);
+ end
+
+ return buffer;
+end
+
+-- concatenate array keys
+function keys_to_s(array, separator, prefix, suffix)
+ require 'common.str';
+
+ local buffer = '';
+ for key, value in pairs(array) do
+ buffer = common.str.append(buffer, key, separator, prefix, suffix);
+ end
+
+ return buffer;
+end
diff --git a/misc/freeswitch/scripts/common/conference.lua b/misc/freeswitch/scripts/common/conference.lua
index b56cba9..b50c134 100644
--- a/misc/freeswitch/scripts/common/conference.lua
+++ b/misc/freeswitch/scripts/common/conference.lua
@@ -1,19 +1,11 @@
-- Gemeinschaft 5 module: conference class
--- (c) AMOOMA GmbH 2012-2013
+-- (c) AMOOMA GmbH 2013
--
module(...,package.seeall)
Conference = {}
-MEMBERS_MAX = 100;
-PIN_LENGTH_MAX = 10;
-PIN_LENGTH_MIN = 2;
-PIN_TIMEOUT = 4000;
-ANNOUNCEMENT_MAX_LEN = 10
-ANNOUNCEMENT_SILENCE_THRESHOLD = 500
-ANNOUNCEMENT_SILENCE_LEN = 3
-
-- create conference object
function Conference.new(self, arg)
arg = arg or {}
@@ -24,215 +16,225 @@ function Conference.new(self, arg)
self.log = arg.log;
self.database = arg.database;
self.record = arg.record;
- self.max_members = 0;
return object;
end
--- find conference by id
+
+function Conference.settings_get(self)
+ require 'common.array';
+ require 'common.configuration_table';
+
+ local configuration = common.configuration_table.get(self.database, 'conferences');
+ if not configuration then
+ return nil;
+ end
+
+ local parameters = configuration.parameters or {};
+ local settings = configuration.settings or {};
+
+ settings.members_max = settings.members_max or tonumber(parameters['max-members']) or 100;
+ settings.pin_length_max = tonumber(settings.pin_length_max) or 10;
+ settings.pin_length_min = tonumber(settings.pin_length_min) or 2;
+ settings.pin_timeout = tonumber(settings.pin_timeout) or 4000;
+ settings.announcement_max_length = tonumber(settings.announcement_max_length) or 10;
+ settings.announcement_silence_threshold = tonumber(settings.announcement_silence_threshold) or 500;
+ settings.announcement_silence_length = tonumber(settings.announcement_silence_length) or 3;
+ settings.flags = settings.flags or { waste = true };
+ settings.pin_sound = parameters['pin-sound'];
+ settings.pin_bad_sound = parameters['bad-pin-sound'];
+ settings.key_enter = parameters.key_enter or '#';
+ settings.phrase_welcome = settings.phrase_welcome or 'conference_welcome';
+ settings.phrase_goodbye = settings.phrase_goodbye or 'conference_goodbye';
+ settings.phrase_record_name = settings.phrase_record_name or 'conference_record_name';
+ settings.spool_dir = settings.spool_dir or '/var/spool/freeswitch';
+ return settings;
+end
+
+
function Conference.find_by_id(self, id)
- local sql_query = 'SELECT * FROM `conferences` WHERE `id`= ' .. tonumber(id) .. ' LIMIT 1';
+ local sql_query = 'SELECT *, (NOW() >= `start` AND NOW() <= `end`) AS `open_now` FROM `conferences` WHERE `id`= ' .. tonumber(id) .. ' LIMIT 1';
local conference = nil;
self.database:query(sql_query, function(conference_entry)
conference = Conference:new(self);
conference.record = conference_entry;
conference.id = tonumber(conference_entry.id);
+ conference.identifier = 'conference' .. conference.id;
conference.uuid = conference_entry.uuid;
- conference.max_members = tonumber(conference.record.max_members) or MEMBERS_MAX;
- end)
+ conference.pin = conference_entry.pin;
+ conference.open_for_public = common.str.to_b(conference_entry.open_for_anybody);
+ conference.announce_entering = common.str.to_b(conference_entry.announce_new_member_by_name);
+ conference.announce_leaving = common.str.to_b(conference_entry.announce_left_member_by_name);
+ if not common.str.blank(conference_entry.open_now) then
+ conference.open_now = common.str.to_b(conference_entry.open_now);
+ end
+ conference.settings = self:settings_get();
+ if conference.settings then
+ conference.settings.members_max = tonumber(conference.record.max_members) or conference.settings.members_max;
+ else
+ conference.log:error('CONFERENCE - no basic configuration');
+ end
+ end)
+
return conference;
end
--- find invitee by phone numbers
+
function Conference.find_invitee_by_numbers(self, phone_numbers)
if not self.record then
- return false
+ return false;
end
- local sql_query = string.format(
- "SELECT `conference_invitees`.`pin` AS `pin`, `conference_invitees`.`speaker` AS `speaker`, `conference_invitees`.`moderator` AS `moderator` " ..
- "FROM `conference_invitees` JOIN `phone_numbers` ON `phone_numbers`.`phone_numberable_id` = `conference_invitees`.`id` " ..
- "WHERE `phone_numbers`.`phone_numberable_type` = 'ConferenceInvitee' AND `conference_invitees`.`conference_id` = %d " ..
- "AND `phone_numbers`.`number` IN ('%s') LIMIT 1", self.record.id, table.concat(phone_numbers, "','"));
+ local sql_query = 'SELECT `a`.* \
+ FROM `conference_invitees` `a` \
+ JOIN `phone_numbers` `b` ON `b`.`phone_numberable_id` = `a`.`id` \
+ WHERE `b`.`phone_numberable_type` = "ConferenceInvitee" \
+ AND `a`.`conference_id` = ' .. self.id .. ' \
+ AND `b`.`number` IN ("' .. table.concat(phone_numbers, "','") .. '") \
+ LIMIT 1';
local invitee = nil;
- self.database:query(sql_query, function(conference_entry)
- invitee = conference_entry;
+ self.database:query(sql_query, function(invitee_entry)
+ invitee = invitee_entry;
end)
- return invitee;
+ return invitee;
end
-function Conference.count(self)
- return tonumber(self.caller:result('conference ' .. self.record.id .. ' list count')) or 0;
-end
--- Try to enter a conference
-function Conference.enter(self, caller, domain)
- local cause = "NORMAL_CLEARING";
- local pin = nil;
- local flags = {'waste'};
+function Conference.members_count(self)
+ return tonumber(self.caller:result('conference ' .. self.identifier .. ' list count')) or 0;
+end
- self.caller = caller;
- require "common.phone_number"
- local phone_number_class = common.phone_number.PhoneNumber:new{log = self.log, database = self.database}
- local phone_numbers = phone_number_class:list_by_owner(self.record.id, "Conference");
+function Conference.check_pin(self, pin)
+ local digits = '';
+ for i = 1, 3 do
+ if digits == pin then
+ break
+ elseif digits ~= "" then
+ if self.settings.pin_bad_sound then
+ self.caller:playback(self.settings.pin_bad_sound);
+ else
+ self.caller.session:sayPhrase('conference_bad_pin');
+ end
+ end
+ digits = self.caller.session:read(self.settings.pin_length_min, self.settings.pin_length_max, self.settings.pin_sound, self.settings.pin_timeout, self.settings.key_enter);
+ end
- -- Set conference presence
- require "dialplan.presence"
- local presence = dialplan.presence.Presence:new();
- presence:init{ log = log, accounts = phone_numbers, domain = domain, uuid = "conference_" .. self.record.id };
+ if digits ~= pin then
+ return false
+ end
- local conference_count = self:count();
+ return true;
+end
- -- Check if conference is full
- if conference_count >= self.max_members then
- presence:early();
- self.log:debug(string.format("full conference %s (\"%s\"), members: %d, members allowed: %d", self.record.id, self.record.name, conference_count, self.max_members));
- if (tonumber(self.record.conferenceable_id) == caller.account_owner_id)
- and (self.record.conferenceable_type == caller.account_owner_type) then
- self.log:debug("Allow owner of this conterence to enter a full conference");
- else
- cause = "CALL_REJECTED";
- caller:hangup(cause);
- return cause;
- end;
+function Conference.check_ownership(self)
+ local auth_account_owner = common.array.try(self.caller, 'auth_account.owner');
+ if not auth_account_owner then
+ return false;
end
- require 'common.str'
- -- Check if conference is within time frame
- if not common.str.blank(self.record.start) and not common.str.blank(self.record['end']) then
- local d = {}
- _,_,d.year,d.month,d.day,d.hour,d.min,d.sec=string.find(self.record.start, "(%d+)-(%d+)-(%d+) (%d+):(%d+):(%d+)");
-
- local conference_start = os.time(d);
- _,_,d.year,d.month,d.day,d.hour,d.min,d.sec=string.find(self.record['end'], "(%d+)-(%d+)-(%d+) (%d+):(%d+):(%d+)");
- local conference_end = os.time(d);
- local now = os.time(os.date("!*t", os.time()));
-
- log:debug("conference - open: " .. os.date("%c",conference_start) .. " by " .. os.date("%c",conference_end) .. ", now: " .. os.date("%c",now));
-
- if now < conference_start or now > conference_end then
- cause = "CALL_REJECTED";
- caller:hangup(cause);
- return cause;
- end
+ if tonumber(self.record.conferenceable_id) == auth_account_owner.id and self.record.conferenceable_type:lower() == auth_account_owner.class then
+ return true;
end
+end
- -- Owner ist always moderator
- if (tonumber(self.record.conferenceable_id) == caller.account_owner_id) and (self.record.conferenceable_type == caller.account_owner_type) then
- table.insert(flags, 'moderator');
- log:debug("is owner - conference: " .. self.record.id .. ", owner: " .. caller.account_owner_type .. ":" .. caller.account_owner_id);
- else
- local invitee = self:find_invitee_by_numbers(caller.caller_phone_numbers);
-
- if not common.str.to_b(self.record.open_for_anybody) and not invitee then
- log:debug(string.format("conference %s (\"%s\"), caller %s not allowed to enter this conference", self.record.id, self.record.name, caller.caller_phone_number));
- cause = "CALL_REJECTED";
- caller:hangup(cause);
- return cause;
- end
- if invitee then
- log:debug("conference " .. self.record.id .. " member invited - speaker: " .. invitee.speaker .. ", moderator: " .. invitee.moderator);
- if common.str.to_b(invitee.moderator) then
- table.insert(flags, 'moderator');
- end
- if not common.str.to_b(invitee.speaker) then
- table.insert(flags, 'mute');
- end
- pin = invitee.pin;
- else
- log:debug("conference " .. self.record.id .. " caller not invited");
- end
+function Conference.record_name(self)
+ if not self.announce_entering and not announce_leaving then
+ return nil;
end
- if not pin and self.record.pin then
- pin = self.record.pin
+ local name_file = self.settings.spool_dir .. '/conference_caller_name_' .. self.caller.uuid .. '.wav';
+ self.caller.session:sayPhrase(self.settings.phrase_record_name);
+ self.caller.session:recordFile(name_file, self.settings.announcement_max_length, self.settings.announcement_silence_threshold, self.settings.announcement_max_length);
+ self.caller:playback(name_file);
+
+ return name_file;
+end
+
+
+function Conference.playback(self, file_name)
+ self.caller:execute('set',"result=${conference(" .. self.identifier .. " play ".. file_name .. ")}");
+end
+
+
+function Conference.enter(self, caller, domain)
+ self.caller = caller;
+ local members = self:members_count();
+
+ self.log:info('CONFERENCE ', self.id, ' - open_for_public: ', self.open_for_public, ', open_now: ', self.open_now, ', members: ', members, ', members_max: ', self.settings.members_max);
+
+ if self.open_now == false then
+ self.log:notice('CONFERENCE ', self.id, ' - currently closed, start: ', self.record.start, ', end: ', self.record['end']);
+ return { continue = false, code = 493, phrase = 'Conference closed' };
end
- caller:answer();
- caller:sleep(1000);
- caller.session:sayPhrase('conference_welcome');
-
- if not common.str.blank(pin) then
- local digits = "";
- for i = 1, 3, 1 do
- if digits == pin then
- break
- elseif digits ~= "" then
- caller.session:sayPhrase('conference_bad_pin');
- end
- digits = caller.session:read(PIN_LENGTH_MIN, PIN_LENGTH_MAX, 'conference/conf-pin.wav', PIN_TIMEOUT, '#');
- end
- if digits ~= pin then
- caller.session:sayPhrase('conference_goodbye');
- return "CALL_REJECTED";
- end
+ if members >= self.settings.members_max then
+ self.log:notice('CONFERENCE ', self.id, ' - full, members: ', members, ', members_max: ', self.settings.members_max);
+ return { continue = false, code = 493, phrase = 'Conference closed' };
end
- self.log:debug(string.format("entering conference %s - name: \"%s\", flags: %s, members: %d, max. members: %d",
- self.record.id, self.record.name, table.concat(flags, ','), conference_count, self.max_members));
-
- -- Members count will be incremented in a few milliseconds, set presence
- if (conference_count + 1) >= self.max_members then
- presence:early();
- else
- presence:confirmed();
+ local invitee = self:find_invitee_by_numbers(caller.caller_phone_numbers);
+ if invitee then
+ self.settings.flags.mute = not common.str.to_b(invitee.speaker);
+ self.settings.flags.moderator = common.str.to_b(invitee.moderator);
+ self.log:info('CONFERENCE ', self.id, ' - invitee=', invitee.id, '/', invitee.uuid, ', speaker: ', not self.settings.flags.mute, ', moderator: ', self.settings.flags.moderator);
+ self.pin = invitee.pin;
+ elseif self:check_ownership() then
+ self.settings.flags.moderator = true;
+ self.pin = nil;
+ self.log:info('CONFERENCE ', self.id, ' - owner: ', self.caller.auth_account.owner.class,'=', self.caller.auth_account.owner.id, '/', self.caller.auth_account.owner.uuid, ', speaker: ', not self.settings.flags.mute, ', moderator: ', self.settings.flags.moderator);
+ elseif not self.open_for_public then
+ self.log:notice('CONFERENCE ', self.id, ' - not open for public');
+ return { continue = false, code = 493, phrase = 'Conference closed' };
end
- -- Enter the conference
- local name_file = nil;
+ if not common.str.blank(self.pin) and not self:check_pin(self.pin) then
+ self.log:notice('CONFERENCE ', self.id, ' - PIN wrong');
+ if self.settings.phrase_goodbye then
+ caller.session:sayPhrase(self.settings.phrase_goodbye);
+ end
+ return { continue = false, code = 493, phrase = 'Not authorized' };
+ end
- -- Record caller's name
- if common.str.to_b(self.record.announce_new_member_by_name) or common.str.to_b(self.record.announce_left_member_by_name) then
- local uid = session:get_uuid();
- name_file = "/var/spool/freeswitch/conference_caller_name_" .. uid .. ".wav";
- caller.session:sayPhrase('conference_record_name');
- session:recordFile(name_file, ANNOUNCEMENT_MAX_LEN, ANNOUNCEMENT_SILENCE_THRESHOLD, ANNOUNCEMENT_SILENCE_LEN);
- caller.session:streamFile(name_file);
+ caller:answer();
+ caller:sleep(1000);
+ if self.settings.phrase_welcome then
+ caller.session:sayPhrase('conference_welcome');
end
- -- Play entering caller's name if recorded
- if name_file and (self:count() > 0) and common.str.to_b(self.record.announce_new_member_by_name) then
- caller.session:execute('set',"result=${conference(" .. self.record.id .. " play ".. name_file .. ")}");
- caller.session:execute('set',"result=${conference(" .. self.record.id .. " play conference/conf-has_joined.wav)}");
- else
- -- Ensure a surplus "#" digit is not passed to the conference
- caller.session:read(1, 1, '', 1000, "#");
+ local name_file = self:record_name(caller);
+ if name_file then
+ if self.announce_entering then
+ members = self:members_count();
+ if members > 0 then
+ caller.session:sayPhrase('conference_has_joined', name_file);
+ end
+ end
end
- local result = caller.session:execute('conference', self.record.id .. "@profile_" .. self.record.id .. "++flags{" .. table.concat(flags, '|') .. "}");
- self.log:debug('exited conference - result: ' .. tostring(result));
- caller.session:sayPhrase('conference_goodbye');
+ local result = caller:execute('conference', self.identifier .. "@profile_" .. self.identifier .. "++flags{" .. common.array.keys_to_s(self.settings.flags, '|') .. "}");
+ if self.settings.phrase_goodbye then
+ caller.session:sayPhrase(self.settings.phrase_goodbye);
+ end
- -- Play leaving caller's name if recorded
if name_file then
- if (self:count() > 0) and common.str.to_b(self.record.announce_left_member_by_name) then
- if (self:count() == 1) then
- caller.session:sleep(3000);
+ if self.announce_leaving then
+ members = self:members_count();
+ if members > 0 then
+ if members == 1 then
+ caller:sleep(3000);
+ end
+ caller.session:sayPhrase('conference_has_left', name_file);
end
- caller.session:execute('set',"result=${conference(" .. self.record.id .. " play ".. name_file .. ")}");
- caller.session:execute('set',"result=${conference(" .. self.record.id .. " play conference/conf-has_left.wav)}");
end
os.remove(name_file);
end
- -- Set presence according to member count
- conference_count = self:count();
- if conference_count >= self.max_members then
- presence:early();
- elseif conference_count > 0 then
- presence:confirmed();
- else
- presence:terminated();
- end
-
- cause = "NORMAL_CLEARING";
- caller.session:hangup(cause);
- return cause;
+ return { continue = false, code = 200, phrase = 'OK' }
end
diff --git a/misc/freeswitch/scripts/configuration.lua b/misc/freeswitch/scripts/configuration.lua
index be83ef5..8672f99 100644
--- a/misc/freeswitch/scripts/configuration.lua
+++ b/misc/freeswitch/scripts/configuration.lua
@@ -196,7 +196,7 @@ function conf_conference(database)
if conf_name then
require 'common.conference'
- conference = common.conference.Conference:new{log=log, database=database}:find_by_id(conf_name);
+ conference = common.conference.Conference:new{log=log, database=database}:find_by_id(common.str.to_i(conf_name));
if conference then
log:debug('CONFIG_CONFERENCE ', conf_name, ' name: ', conference.record.name, ', profile: ', profile_name);
config.parameters['caller-id-name'] = conference.record.name or '';
diff --git a/misc/freeswitch/scripts/dialplan/dialplan.lua b/misc/freeswitch/scripts/dialplan/dialplan.lua
index fabc5af..bbf1242 100644
--- a/misc/freeswitch/scripts/dialplan/dialplan.lua
+++ b/misc/freeswitch/scripts/dialplan/dialplan.lua
@@ -76,7 +76,7 @@ end
function Dialplan.configuration_read(self)
- require 'common.configuration_table'
+ require 'common.configuration_table';
-- dialplan configuration
self.config = common.configuration_table.get(self.database, 'dialplan');
@@ -462,16 +462,14 @@ end
function Dialplan.conference(self, destination)
- -- call local conference
- require 'common.conference'
- conference = common.conference.Conference:new{ log = self.log, database = self.database }:find_by_id(destination.id);
+ require 'common.conference';
+ local conference = common.conference.Conference:new{ log = self.log, database = self.database }:find_by_id(destination.id);
if not conference then
return { continue = false, code = 404, phrase = 'Conference not found' }
end
- local cause = conference:enter(self.caller, self.domain);
- return { continue = false, cause = cause }
+ return conference:enter(self.caller, self.domain);
end
diff --git a/misc/freeswitch/scripts/dialplan/session.lua b/misc/freeswitch/scripts/dialplan/session.lua
index 7de85ca..04d7675 100644
--- a/misc/freeswitch/scripts/dialplan/session.lua
+++ b/misc/freeswitch/scripts/dialplan/session.lua
@@ -226,3 +226,8 @@ function Session.expand_variables(self, line)
return self.session:getVariable(captured) or '';
end))
end
+
+
+function Session.playback(self, file)
+ self.session:streamFile(file);
+end
diff --git a/misc/freeswitch/scripts/event/presence_update.lua b/misc/freeswitch/scripts/event/presence_update.lua
index 01ec17b..4d0cb94 100644
--- a/misc/freeswitch/scripts/event/presence_update.lua
+++ b/misc/freeswitch/scripts/event/presence_update.lua
@@ -72,22 +72,31 @@ end
function PresenceUpdate.presence_in(self, event)
- if not event:getHeader('status') then
- return
- end
-
local account, domain = common.str.partition(event:getHeader('from'), '@');
- local direction = tostring(event:getHeader('presence-call-direction'))
+ local call_direction = tostring(event:getHeader('presence-call-direction') or event:getHeader('call-direction'))
local state = event:getHeader('presence-call-info-state');
local uuid = event:getHeader('Unique-ID');
local caller_id = event:getHeader('Caller-Caller-ID-Number');
+ local protocol = tostring(event:getHeader('proto'));
+
+ local direction = nil;
+
+ if call_direction == 'inbound' then
+ direction = true;
+ elseif call_direction == 'outbound' then
+ direction = false;
+ end
- if direction == 'inbound' then
- self.log:info('[', uuid,'] PRESENCE_INBOUND: account: ', account, ', state: ', state);
- self:sip_account(true, account, domain, state, uuid);
- elseif direction == 'outbound' then
- self.log:info('[', uuid,'] PRESENCE_OUTBOUND: account: ', account, ', state: ', state, ', caller: ', caller_id);
- self:sip_account(false, account, domain, state, uuid, caller_id);
+ if protocol == 'conf' then
+ state = event:getHeader('answer-state');
+ local login = tostring(event:getHeader('proto'));
+ self.log:info('[', uuid,'] PRESENCE_CONFERENCE_', call_direction:upper(), ' ', common.str.to_i(account), ' - identifier: ', account, ', state: ', state);
+ self:conference(direction, account, domain, state, uuid);
+ elseif protocol == 'sip' and common.str.blank(state) then
+ return;
+ else
+ self.log:info('[', uuid,'] PRESENCE_', call_direction:upper(),' - protocol: ', protocol, ', account: ', account, ', state: ', state);
+ self:sip_account(direction, account, domain, state, uuid, caller_id);
end
end
@@ -197,3 +206,16 @@ function PresenceUpdate.sip_account(self, inbound, account, domain, status, uuid
uuid = uuid
}:set(status_map[status] or 'terminated', caller_id);
end
+
+
+function PresenceUpdate.conference(self, inbound, account, domain, status, uuid)
+ require 'dialplan.presence'
+ local result = dialplan.presence.Presence:new{
+ log = self.log,
+ database = self.database,
+ inbound = inbound,
+ domain = domain,
+ accounts = { account },
+ uuid = uuid
+ }:set(status or 'terminated');
+end