<commit_before>-- Gemeinschaft 5 module: conference class
-- (c) AMOOMA GmbH 2012
-- 

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 {}
  object = arg.object or {}
  setmetatable(object, self);
  self.__index = self;
  self.class = 'conference';
  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.find_by_id(self, id)
  local sql_query = 'SELECT * 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.uuid = conference_entry.uuid;
    conference.max_members = tonumber(conference.record.max_members) or MEMBERS_MAX;
  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
  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 invitee = nil;

  self.database:query(sql_query, function(conference_entry)
    invitee = conference_entry;
  end)

  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'};

  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");

  -- 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 };

  local conference_count = self:count();

  -- 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;
  end

  -- Check if conference is within time frame
  if self.record.start and 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
  end

  require 'common.str'
  -- 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
  end

  if not pin and self.record.pin then
    pin = self.record.pin
  end

  caller:answer();
  caller:sleep(1000);
  caller.session:streamFile('conference/conf-welcome.wav');

  if pin and pin ~= "" then
    local digits = "";
    for i = 1, 3, 1 do
      if digits == pin then
        break
      elseif digits ~= "" then
        caller.session:streamFile('conference/conf-bad-pin.wav');
      end
      digits = caller.session:read(PIN_LENGTH_MIN, PIN_LENGTH_MAX, 'conference/conf-enter_conf_pin.wav', PIN_TIMEOUT, '#');
    end
    if digits ~= pin then
      caller.session:streamFile("conference/conf-goodbye.wav");
      return "CALL_REJECTED";
    end
  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();
  end

  -- Enter the conference
  local name_file = nil;

  -- 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 = "/tmp/conference_caller_name_" .. uid .. ".wav";
    caller.session:streamFile("voicemail/vm-record_name1.wav");
    caller.session:execute("playback", "tone_stream://%(1000,0,500)");
    session:recordFile(name_file, ANNOUNCEMENT_MAX_LEN, ANNOUNCEMENT_SILENCE_THRESHOLD, ANNOUNCEMENT_SILENCE_LEN);
    caller.session:streamFile(name_file);
  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, "#");
  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:streamFile("conference/conf-goodbye.wav")

  -- 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);
      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;
end
<commit_msg>nil vs. empty_string<commit_after>-- Gemeinschaft 5 module: conference class
-- (c) AMOOMA GmbH 2012
-- 

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 {}
  object = arg.object or {}
  setmetatable(object, self);
  self.__index = self;
  self.class = 'conference';
  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.find_by_id(self, id)
  local sql_query = 'SELECT * 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.uuid = conference_entry.uuid;
    conference.max_members = tonumber(conference.record.max_members) or MEMBERS_MAX;
  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
  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 invitee = nil;

  self.database:query(sql_query, function(conference_entry)
    invitee = conference_entry;
  end)

  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'};

  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");

  -- 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 };

  local conference_count = self:count();

  -- 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;
  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
  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
  end

  if not pin and self.record.pin then
    pin = self.record.pin
  end

  caller:answer();
  caller:sleep(1000);
  caller.session:streamFile('conference/conf-welcome.wav');

  if pin and pin ~= "" then
    local digits = "";
    for i = 1, 3, 1 do
      if digits == pin then
        break
      elseif digits ~= "" then
        caller.session:streamFile('conference/conf-bad-pin.wav');
      end
      digits = caller.session:read(PIN_LENGTH_MIN, PIN_LENGTH_MAX, 'conference/conf-enter_conf_pin.wav', PIN_TIMEOUT, '#');
    end
    if digits ~= pin then
      caller.session:streamFile("conference/conf-goodbye.wav");
      return "CALL_REJECTED";
    end
  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();
  end

  -- Enter the conference
  local name_file = nil;

  -- 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 = "/tmp/conference_caller_name_" .. uid .. ".wav";
    caller.session:streamFile("voicemail/vm-record_name1.wav");
    caller.session:execute("playback", "tone_stream://%(1000,0,500)");
    session:recordFile(name_file, ANNOUNCEMENT_MAX_LEN, ANNOUNCEMENT_SILENCE_THRESHOLD, ANNOUNCEMENT_SILENCE_LEN);
    caller.session:streamFile(name_file);
  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, "#");
  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:streamFile("conference/conf-goodbye.wav")

  -- 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);
      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;
end

{'fraction_non_alphanumeric': 0.14655221099000396, 'fraction_numerical': 0.006325182131360479, 'mean_word_length': 3.762775685852609, 'pattern_counts': {'":': 2, '<': 5, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6262311', 'n_tokens_mistral': 6101, 'n_tokens_neox': 5932, 'n_words': 1260}