From b80bd744ad873f6fc43018bc4bfb90677de167bd Mon Sep 17 00:00:00 2001 From: Stefan Wintermeyer Date: Mon, 17 Dec 2012 12:01:45 +0100 Subject: Start of GS5. --- misc/freeswitch/scripts/common/call_forwarding.lua | 47 +++ misc/freeswitch/scripts/common/call_history.lua | 140 ++++++++ misc/freeswitch/scripts/common/conference.lua | 239 ++++++++++++++ .../scripts/common/configuration_file.lua | 70 ++++ misc/freeswitch/scripts/common/database.lua | 151 +++++++++ misc/freeswitch/scripts/common/fapi.lua | 80 +++++ misc/freeswitch/scripts/common/ipcalc.lua | 27 ++ misc/freeswitch/scripts/common/log.lua | 69 ++++ misc/freeswitch/scripts/common/node.lua | 73 +++++ misc/freeswitch/scripts/common/phone_number.lua | 359 +++++++++++++++++++++ misc/freeswitch/scripts/common/routing_tables.lua | 66 ++++ misc/freeswitch/scripts/common/sip_account.lua | 137 ++++++++ misc/freeswitch/scripts/common/str.lua | 136 ++++++++ misc/freeswitch/scripts/common/sync_log.lua | 39 +++ 14 files changed, 1633 insertions(+) create mode 100644 misc/freeswitch/scripts/common/call_forwarding.lua create mode 100644 misc/freeswitch/scripts/common/call_history.lua create mode 100644 misc/freeswitch/scripts/common/conference.lua create mode 100644 misc/freeswitch/scripts/common/configuration_file.lua create mode 100644 misc/freeswitch/scripts/common/database.lua create mode 100644 misc/freeswitch/scripts/common/fapi.lua create mode 100644 misc/freeswitch/scripts/common/ipcalc.lua create mode 100644 misc/freeswitch/scripts/common/log.lua create mode 100644 misc/freeswitch/scripts/common/node.lua create mode 100644 misc/freeswitch/scripts/common/phone_number.lua create mode 100644 misc/freeswitch/scripts/common/routing_tables.lua create mode 100644 misc/freeswitch/scripts/common/sip_account.lua create mode 100644 misc/freeswitch/scripts/common/str.lua create mode 100644 misc/freeswitch/scripts/common/sync_log.lua (limited to 'misc/freeswitch/scripts/common') diff --git a/misc/freeswitch/scripts/common/call_forwarding.lua b/misc/freeswitch/scripts/common/call_forwarding.lua new file mode 100644 index 0000000..3942d05 --- /dev/null +++ b/misc/freeswitch/scripts/common/call_forwarding.lua @@ -0,0 +1,47 @@ +-- Gemeinschaft 5 module: call forwarding class +-- (c) AMOOMA GmbH 2012 +-- + +module(...,package.seeall) + +CallForwarding = {} + +-- Create CallForwarding object +function CallForwarding.new(self, arg, object) + arg = arg or {} + object = arg.object or {} + setmetatable(object, self); + self.__index = self + self.log = arg.log; + self.database = arg.database; + self.record = arg.record; + self.domain = arg.domain; + return object; +end + +-- Find call forwarding by id +function CallForwarding.find_by_id(self, id) + local sql_query = 'SELECT * FROM `call_forwards` WHERE `id`= ' .. tonumber(id) .. ' LIMIT 1'; + local record = nil + + self.database:query(sql_query, function(entry) + record = entry; + end) + + if record then + call_forwarding = CallForwarding:new(self) + call_forwarding.record = record + return call_forwarding + end + + return nil +end + +function CallForwarding.presence_set(self, presence_state) + require 'dialplan.presence' + local presence = dialplan.presence.Presence:new(); + + presence:init{log = self.log, accounts = { 'f-cftg-' .. tostring(self.record.id) }, domain = self.domain, uuid = 'call_forwarding_' .. tostring(self.record.id)}; + + return presence:set(presence_state); +end diff --git a/misc/freeswitch/scripts/common/call_history.lua b/misc/freeswitch/scripts/common/call_history.lua new file mode 100644 index 0000000..c5bc0bf --- /dev/null +++ b/misc/freeswitch/scripts/common/call_history.lua @@ -0,0 +1,140 @@ +-- Gemeinschaft 5 module: call_history class +-- (c) AMOOMA GmbH 2012 +-- + +module(...,package.seeall) + + +function camelize_type(account_type) + ACCOUNT_TYPES = { + sipaccount = 'SipAccount', + conference = 'Conference', + faxaccount = 'FaxAccount', + callthrough = 'Callthrough', + huntgroup = 'HuntGroup', + automaticcalldistributor = 'AutomaticCallDistributor', + } + + return ACCOUNT_TYPES[account_type] or account_type; +end + + +CallHistory = {} + +-- Create CallHistory object +function CallHistory.new(self, arg) + arg = arg or {} + object = arg.object or {} + setmetatable(object, self); + self.__index = self; + self.class = 'callhistory'; + self.log = arg.log; + self.database = arg.database; + return object; +end + + +function CallHistory.insert_entry(self, call_history) + local keys = {} + local values = {} + + call_history.created_at = 'NOW()'; + call_history.updated_at = 'NOW()'; + + for key, value in pairs(call_history) do + table.insert(keys, key); + table.insert(values, value); + end + + local sql_query = 'INSERT INTO `call_histories` (`' .. table.concat(keys, "`, `") .. '`) VALUES (' .. table.concat(values, ", ") .. ')'; + local result = self.database:query(sql_query); + if not result then + self.log:error('[', call_history.caller_channel_uuid, '] CALL_HISTORY_SAVE - SQL: ', sql_query); + end + return result; +end + + +function CallHistory.insert_event(self, uuid, account_type, account_id, entry_type, event) + require 'common.str' + local call_history = {} + + call_history.entry_type = common.str.to_sql(entry_type); + call_history.call_historyable_type = common.str.to_sql(camelize_type(account_type)); + call_history.call_historyable_id = common.str.to_sql(account_id); + call_history.caller_channel_uuid = common.str.to_sql(uuid); + call_history.duration = common.str.to_sql(event:getHeader('variable_billsec')); + call_history.caller_id_number = common.str.to_sql(event:getHeader('variable_effective_caller_id_number')); + call_history.caller_id_name = common.str.to_sql(event:getHeader('variable_effective_caller_id_name')); + call_history.callee_id_number = common.str.to_sql(event:getHeader('variable_effective_callee_id_number')); + call_history.callee_id_name = common.str.to_sql(event:getHeader('variable_effective_callee_id_name')); + call_history.result = common.str.to_sql(event:getHeader('variable_hangup_cause')); + call_history.start_stamp = 'FROM_UNIXTIME(' .. math.floor(common.str.to_i(event:getHeader('Caller-Channel-Created-Time')) / 1000000) .. ')'; + call_history.caller_account_type = common.str.to_sql(camelize_type(event:getHeader('variable_gs_caller_account_type') or event:getHeader('variable_gs_account_type'))); + call_history.caller_account_id = common.str.to_sql(event:getHeader('variable_gs_caller_account_id') or event:getHeader('variable_gs_account_id')); + call_history.auth_account_type = common.str.to_sql(camelize_type(event:getHeader('variable_gs_auth_account_type'))); + call_history.auth_account_id = common.str.to_sql(event:getHeader('variable_gs_auth_account_id')); + call_history.callee_account_type = common.str.to_sql(camelize_type(event:getHeader('variable_gs_destination_type'))); + call_history.callee_account_id = common.str.to_sql(event:getHeader('variable_gs_destination_id')); + call_history.destination_number = common.str.to_sql(event:getHeader('variable_gs_destination_number')); + call_history.forwarding_service = common.str.to_sql(event:getHeader('variable_gs_forwarding_service')); + + if common.str.to_s(event:getHeader('variable_gs_call_service')) == 'pickup' then + call_history.forwarding_service = common.str.to_sql('pickup'); + end + + self.log:info('[', uuid,'] CALL_HISTORY_SAVE ', entry_type,' - account: ', account_type, '=', account_id, + ', caller: ', call_history.caller_id_number, ' ', call_history.caller_id_name, + ', callee: ', call_history.callee_id_number, ' ', call_history.callee_id_name, + ', result: ', call_history.result + ); + + return self:insert_entry(call_history); +end + + +function CallHistory.insert_forwarded(self, uuid, account_type, account_id, caller, destination, result) + require 'common.str' + + local call_history = {} + + call_history.entry_type = common.str.to_sql('forwarded'); + call_history.call_historyable_type = common.str.to_sql(camelize_type(account_type)); + call_history.call_historyable_id = common.str.to_sql(account_id); + call_history.caller_channel_uuid = common.str.to_sql(uuid); + + call_history.duration = common.str.to_sql(caller:to_i('billsec')); + call_history.caller_id_number = common.str.to_sql(caller.caller_id_number); + call_history.caller_id_name = common.str.to_sql(caller.caller_id_name); + call_history.callee_id_number = common.str.to_sql(caller.callee_id_number); + call_history.callee_id_name = common.str.to_sql(caller.callee_id_name); + call_history.result = common.str.to_sql(result.cause or 'UNSPECIFIED'); + call_history.start_stamp = 'FROM_UNIXTIME(' .. math.floor(caller:to_i('created_time') / 1000000) .. ')'; + + if caller.account then + call_history.caller_account_type = common.str.to_sql(camelize_type(caller.account.class)); + call_history.caller_account_id = common.str.to_sql(caller.account.id); + end + + if caller.auth_account then + call_history.auth_account_type = common.str.to_sql(camelize_type(caller.auth_account.class)); + call_history.auth_account_id = common.str.to_sql(caller.auth_account.id); + end + + if destination then + call_history.callee_account_type = common.str.to_sql(camelize_type(destination.type)); + call_history.callee_account_id = common.str.to_sql(destination.id); + call_history.destination_number = common.str.to_sql(destination.number); + end + + call_history.forwarding_service = common.str.to_sql(caller.forwarding_service); + + self.log:info('CALL_HISTORY_SAVE forwarded - account: ', account_type, '=', account_id, + ', service: ', call_history.forwarding_service, + ', caller: ', call_history.caller_id_number, ' ', call_history.caller_id_name, + ', callee: ', call_history.callee_id_number, ' ', call_history.callee_id_name, + ', result: ', call_history.result + ); + + return self:insert_entry(call_history); +end diff --git a/misc/freeswitch/scripts/common/conference.lua b/misc/freeswitch/scripts/common/conference.lua new file mode 100644 index 0000000..d2bf829 --- /dev/null +++ b/misc/freeswitch/scripts/common/conference.lua @@ -0,0 +1,239 @@ +-- 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 diff --git a/misc/freeswitch/scripts/common/configuration_file.lua b/misc/freeswitch/scripts/common/configuration_file.lua new file mode 100644 index 0000000..67e1f3b --- /dev/null +++ b/misc/freeswitch/scripts/common/configuration_file.lua @@ -0,0 +1,70 @@ +-- Gemeinschaft 5 module: configuration file +-- (c) AMOOMA GmbH 2012 +-- + +module(...,package.seeall) + +function ignore_comments(line) + return line:gsub(';+([^;]*)', function(entry) + return ''; + end); +end + +-- parse configuration +function parse(lines, filter_section_name) + require 'common.str' + local section = {} + local root = { [true] = section } + + for line in lines do + if line then + local ignore_line = false; + line = ignore_comments(line); + + line:gsub('^%s*%[(.-)%]%s*$', function(section_name) + if tostring(section_name):match('%=false$') then + section = {} + else + root[common.str.strip(section_name)] = {}; + section = root[common.str.strip(section_name)]; + end + ignore_line = true; + end); + + if not ignore_line then + key, value = common.str.partition(line, '='); + if value and key and not common.str.strip(key):match('%s') then + section[common.str.strip(key)] = common.str.strip(value); + else + line = common.str.strip(line); + if not common.str.blank(line) then + if line:match(',') then + table.insert(section, common.str.strip_to_a(line, ',')); + else + table.insert(section, line); + end + end + end + end + end + end + + if filter_section_name == false then + root[true] = nil; + elseif filter_section_name then + return root[filter_section_name]; + end + + return root; +end + +-- retrieve configuration from file +function get(file_name, filter_section_name) + local file = io.open(file_name); + + if file then + local result = parse(file:lines(), filter_section_name); + file:close(); + return result; + end +end diff --git a/misc/freeswitch/scripts/common/database.lua b/misc/freeswitch/scripts/common/database.lua new file mode 100644 index 0000000..3692f84 --- /dev/null +++ b/misc/freeswitch/scripts/common/database.lua @@ -0,0 +1,151 @@ +-- Gemeinschaft 5 module: database class +-- (c) AMOOMA GmbH 2012 +-- + +module(...,package.seeall) + +Database = {} + +DATABASE_DRIVER = 'mysql' + +function Database.new(self, arg) + arg = arg or {} + object = arg.object or {} + setmetatable(object, self); + self.__index = self; + self.class = 'database'; + self.log = arg.log; + self.conn = nil; + return object; +end + + +function Database.connect(self, database_name, user_name, password, host_name) + local database_driver = nil; + if not (database_name and user_name and password) then + require 'common.configuration_file' + local config = common.configuration_file.get('/opt/freeswitch/scripts/ini/database.ini'); + if config then + database_driver = config[true].driver + database_name = config[database_driver].database + user_name = config[database_driver].user + password = config[database_driver].password + host_name = config[database_driver].host + end + end + + host_name = host_name or 'localhost'; + database_driver = database_driver or DATABASE_DRIVER; + + if database_driver == 'mysql' then + require "luasql.mysql" + self.env = luasql.mysql(); + elseif database_driver == 'odbc' then + require "luasql.odbc" + self.env = luasql.odbc(); + end + + self.conn = self.env:connect(database_name, user_name, password, host_name); + self.conn_id = tostring(self.conn); + self.database_name = database_name; + self.user_name = user_name; + self.password = password; + self.host_name = host_name; + + -- self.log:debug('DATABASE_CONNECT - connection: ', self.conn_id, ', environment: ', self.env); + + return self; +end + + +function Database.reconnect(self) + self.conn = self.env:connect(self.database_name, self.user_name, self.password, self.host_name); + self.conn_id = tostring(self.conn); + + if self.log then + self.log:info('DATABASE_RECONNECT - connection: ', self.conn_id, ', environment: ', self.env); + end + + return self; +end + + +function Database.connected(self) + return self.conn; +end + + +function Database.query(self, sql_query, call_function) + local cursor = self.conn:execute(sql_query); + + if cursor == nil and not self.conn:execute('SELECT @@VERSION') then + if self.log then + self.log:error('DATABASE_QUERY - lost connection: ', self.conn_id, ', environment: ', self.env, ', query: ', sql_query); + end + self:reconnect(); + + if call_function then + cursor = self.conn:execute(sql_query); + self.log:notice('DATABASE_QUERY - retry: ', sql_query); + end + end + + if cursor and call_function then + repeat + row = cursor:fetch({}, 'a'); + if row then + call_function(row); + end + until not row; + end + + if type(cursor) == 'userdata' then + cursor:close(); + end + + return cursor; +end + + +function Database.query_return_value(self, sql_query) + local cursor = self.conn:execute(sql_query); + + if cursor == nil and not self.conn:execute('SELECT @@VERSION') then + if self.log then + self.log:error('DATABASE_QUERY - lost connection: ', self.conn_id, ', environment: ', self.env, ', query: ', sql_query); + end + self:reconnect(); + cursor = self.conn:execute(sql_query); + self.log:notice('DATABASE_QUERY - retry: ', sql_query); + end + + if type(cursor) == 'userdata' then + local row = cursor:fetch({}, 'n'); + cursor:close(); + + if not row then + return row; + else + return row[1]; + end + end + + return cursor; +end + + +function Database.last_insert_id(self) + return self:query_return_value('SELECT LAST_INSERT_ID()'); +end + + +function Database.release(self, sql_query, call_function) + if self.conn then + self.conn:close(); + end + if self.env then + self.env:close(); + end + + -- self.log:debug('DATABASE_RELEASE - connection: ', self.conn_id, ', status: ', self.env, ', ', self.conn); +end diff --git a/misc/freeswitch/scripts/common/fapi.lua b/misc/freeswitch/scripts/common/fapi.lua new file mode 100644 index 0000000..0a05155 --- /dev/null +++ b/misc/freeswitch/scripts/common/fapi.lua @@ -0,0 +1,80 @@ +-- Gemeinschaft 5 module: FS api class +-- (c) AMOOMA GmbH 2012 +-- + +module(...,package.seeall) + +FApi = {} + +-- create fapi object +function FApi.new(self, arg) + arg = arg or {} + object = arg.object or {} + setmetatable(object, self); + self.__index = self; + self.class = 'fapi'; + self.log = arg.log; + self.uuid = arg.uuid; + self.fs_api = freeswitch.API(); + return object; +end + + +function FApi.return_result(self, result, positive, negative, unspecified) + if not result then + return negative; + end + result = tostring(result); + + if result:match('^-ERR') then + return negative; + elseif result:match('^_undef_') then + return negative; + elseif result:match('^+OK') then + return positive; + else + return unspecified; + end +end + + +function FApi.sleep(self, value) + freeswitch.msleep(value); +end + + +function FApi.channel_exists(self, uuid) + require 'common.str' + uuid = uuid or self.uuid; + return common.str.to_b(freeswitch.API():execute('uuid_exists', tostring(uuid))); +end + + +function FApi.get_variable(self, variable_name) + local result = freeswitch.API():execute('uuid_getvar', tostring(self.uuid) .. ' ' .. tostring(variable_name)); + return self:return_result(result, result, nil, result); +end + + +function FApi.set_variable(self, variable_name, value) + value = value or ''; + + local result = freeswitch.API():execute('uuid_setvar', tostring(self.uuid) .. ' ' .. tostring(variable_name) .. ' ' .. tostring(value)); + return self:return_result(result, true); +end + + +function FApi.continue(self) + local result = freeswitch.API():execute('break', tostring(self.uuid)); + return self:return_result(result, true, false); +end + +function FApi.create_uuid(self, uuid) + local result = self.fs_api:execute('create_uuid', uuid); + return result; +end + +function FApi.execute(self, function_name, function_parameters) + local result = self.fs_api:execute(function_name, function_parameters); + return self:return_result(result, true); +end diff --git a/misc/freeswitch/scripts/common/ipcalc.lua b/misc/freeswitch/scripts/common/ipcalc.lua new file mode 100644 index 0000000..5c19d20 --- /dev/null +++ b/misc/freeswitch/scripts/common/ipcalc.lua @@ -0,0 +1,27 @@ +-- Gemeinschaft 5 module: ip calculation functions +-- (c) AMOOMA GmbH 2012 +-- + +module(...,package.seeall) + +function ipv4_to_i(ip_address_str) + local octet4, octet3, octet2, octet1 = ip_address_str:match('(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)'); + if octet4 and octet3 and octet2 and octet1 then + return (2^24*octet4 + 2^16*octet3 + 2^8*octet2 + octet1); + end +end + +function ipv4_to_network_netmask(ip_address_str) + local octet4, octet3, octet2, octet1, netmask = ip_address_str:match('(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)/(%d%d?)'); + if octet4 and octet3 and octet2 and octet1 and netmask then + return (2^24*octet4 + 2^16*octet3 + 2^8*octet2 + octet1), tonumber(netmask); + end +end + +function ipv4_network(ip_address, netmask) + return math.floor(ip_address / 2^(32-netmask)); +end + +function ipv4_in_network(ip_address, network, netmask) + return ipv4_network(ip_address, netmask) == ipv4_network(network, netmask); +end diff --git a/misc/freeswitch/scripts/common/log.lua b/misc/freeswitch/scripts/common/log.lua new file mode 100644 index 0000000..d0d13dc --- /dev/null +++ b/misc/freeswitch/scripts/common/log.lua @@ -0,0 +1,69 @@ +-- Gemeinschaft 5 module: log +-- (c) AMOOMA GmbH 2012 +-- + +module(...,package.seeall) + +Log = {} + +-- Create logger object +function Log.new(self, arg) + arg = arg or {} + object = arg.object or {} + setmetatable(object, self); + self.__index = self; + self.prefix = arg.prefix or '### '; + + self.level_console = arg.level_console or 0; + self.level_alert = arg.level_alert or 1; + self.level_critical = arg.level_critical or 2; + self.level_error = arg.level_error or 3; + self.level_warning = arg.level_warning or 4; + self.level_notice = arg.level_notice or 5; + self.level_info = arg.level_info or 6; + self.level_debug = arg.level_debug or 7; + + return object; +end + +function Log.message(self, log_level, message_arguments ) + local message = tostring(self.prefix); + for index, value in pairs(message_arguments) do + if type(index) == 'number' then + message = message .. tostring(value); + end + end + freeswitch.consoleLog(log_level, message .. '\n'); +end + +function Log.console(self, ...) + self:message(self.level_console, arg); +end + +function Log.alert(self, ...) + self:message(self.level_alert, arg); +end + +function Log.critical(self, ...) + self:message(self.level_critical, arg); +end + +function Log.error(self, ...) + self:message(self.level_error, arg); +end + +function Log.warning(self, ...) + self:message(self.level_warning, arg); +end + +function Log.notice(self, ...) + self:message(self.level_notice, arg); +end + +function Log.info(self, ...) + self:message(self.level_info, arg); +end + +function Log.debug(self, ...) + self:message(self.level_debug, arg); +end diff --git a/misc/freeswitch/scripts/common/node.lua b/misc/freeswitch/scripts/common/node.lua new file mode 100644 index 0000000..544ede9 --- /dev/null +++ b/misc/freeswitch/scripts/common/node.lua @@ -0,0 +1,73 @@ +-- CommonModule: Node +-- +module(...,package.seeall) + +Node = {} + +-- Create Node object +function Node.new(self, arg) + arg = arg or {} + object = arg.object or {} + setmetatable(object, self) + self.__index = self + self.log = arg.log + self.database = arg.database + self.record = arg.record + self.session = arg.session + return object +end + +-- Find Node account by name +function Node.find_by_id(self, node_id) + + if not tonumber(node_id) then + return nil + end + + local sql_query = 'SELECT * FROM `gs_nodes` WHERE `id`= ' .. node_id .. ' LIMIT 1'; + local record = nil + + self.database:query(sql_query, function(node_entry) + record = node_entry + end) + + if record then + local node_object = Node:new(self); + node_object.record = record + + return node_object + end + + return nil +end + +-- Find Node account by name +function Node.find_by_address(self, address) + local sql_query = 'SELECT * FROM `gs_nodes` WHERE `ip_address`= "' .. tostring(address):gsub('[^A-F0-9%.%:]', '') .. '" LIMIT 1'; + local record = nil + + self.database:query(sql_query, function(node_entry) + record = node_entry + end) + + if record then + local node_object = Node:new(self); + node_object.record = record + + return node_object + end + + return nil +end + +-- List Nodes +function Node.all(self) + local sql_query = 'SELECT * FROM `gs_nodes`'; + nodes = {}; + + self.database:query(sql_query, function(node_entry) + nodes[tonumber(node_entry.id)] = node_entry; + end) + + return nodes +end \ No newline at end of file diff --git a/misc/freeswitch/scripts/common/phone_number.lua b/misc/freeswitch/scripts/common/phone_number.lua new file mode 100644 index 0000000..f4f4bfe --- /dev/null +++ b/misc/freeswitch/scripts/common/phone_number.lua @@ -0,0 +1,359 @@ +-- Gemeinschaft 5 module: phone number class +-- (c) AMOOMA GmbH 2012 +-- + +module(...,package.seeall) + +PhoneNumber = {} + +PHONE_NUMBER_INTERNAL_TYPES = { 'SipAccount', 'Conference', 'FaxAccount', 'Callthrough', 'HuntGroup', 'AutomaticCallDistributor' } + +-- create phone number object +function PhoneNumber.new(self, arg) + arg = arg or {} + object = arg.object or {} + setmetatable(object, self); + self.__index = self; + self.class = 'phonenumber'; + self.log = arg.log; + self.database = arg.database; + self.record = arg.record; + self.domain = arg.domain; + self.DEFAULT_CALL_FORWARDING_DEPTH = 20; + return object; +end + +-- find phone number by id +function PhoneNumber.find_by_id(self, id) + local sql_query = 'SELECT * FROM `phone_numbers` WHERE `id`= ' .. tonumber(id) .. ' LIMIT 1'; + + local phone_number = nil; + + self.database:query(sql_query, function(number_entry) + phone_number = PhoneNumber:new(self); + phone_number.record = number_entry; + phone_number.id = tonumber(number_entry.id); + phone_number.uuid = number_entry.uuid; + end) + + return phone_number; +end + +-- find phone number by number +function PhoneNumber.find_by_number(self, number, phone_numberable_types) + require 'common.str' + + phone_numberable_types = phone_numberable_types or PHONE_NUMBER_INTERNAL_TYPES + + local sql_query = 'SELECT * FROM `phone_numbers` \ + WHERE `number`= ' .. common.str.to_sql(number) .. ' \ + AND `phone_numberable_type` IN ("' .. table.concat(phone_numberable_types, '","') .. '") \ + AND `state` = "active" LIMIT 1'; + + local phone_number = nil; + + self.database:query(sql_query, function(number_entry) + phone_number = PhoneNumber:new(self); + phone_number.record = number_entry; + end) + + return phone_number; +end + +-- Find numbers by owner id and type +function PhoneNumber.find_all_by_owner(self, owner_id, owner_type) + local sql_query = 'SELECT * FROM `phone_numbers` WHERE `phone_numberable_type`="' .. owner_type .. '" AND `phone_numberable_id`= ' .. tonumber(owner_id) ..' ORDER BY `position`'; + local phone_numbers = {} + + self.database:query(sql_query, function(number_entry) + phone_numbers[tonumber(number_entry.id)] = PhoneNumber:new(self); + phone_numbers[tonumber(number_entry.id)].record = number_entry; + end) + + return phone_numbers; +end + +-- List numbers by owner id and type +function PhoneNumber.list_by_owner(self, owner_id, owner_type) + local sql_query = 'SELECT * FROM `phone_numbers` WHERE `phone_numberable_type`="' .. owner_type .. '" AND `phone_numberable_id`= ' .. tonumber(owner_id) ..' ORDER BY `position`'; + local phone_numbers = {} + + self.database:query(sql_query, function(number_entry) + table.insert(phone_numbers, number_entry.number) + end) + + return phone_numbers; +end + +-- List numbers by same owner +function PhoneNumber.list_by_same_owner(self, number, owner_types) + local phone_number = self:find_by_number(number, owner_types) + + if phone_number then + return self:list_by_owner(phone_number.record.phone_numberable_id, phone_number.record.phone_numberable_type); + end +end + +-- Retrieve call forwarding +function PhoneNumber.call_forwarding(self, sources) + require 'common.str' + + sources = sources or {}; + table.insert(sources, ''); + + local sql_query = 'SELECT \ + `a`.`destination` AS `number`, \ + `a`.`call_forwardable_id` AS `id`, \ + `a`.`call_forwardable_type` AS `type`, \ + `a`.`timeout`, `a`.`depth`, \ + `b`.`value` AS `service` \ + FROM `call_forwards` `a` JOIN `call_forward_cases` `b` ON `a`.`call_forward_case_id` = `b`.`id` \ + WHERE `a`.`phone_number_id`= ' .. tonumber(self.record.id) .. ' \ + AND `a`.`active` IS TRUE \ + AND (`a`.`source` IS NULL OR `a`.`source` IN ("' .. table.concat( sources, '","') .. '"))'; + + local call_forwarding = {} + + self.database:query(sql_query, function(forwarding_entry) + call_forwarding[forwarding_entry.service] = forwarding_entry; + self.log:debug('CALL_FORWARDING_GET - PhoneNumber=', self.record.id, '/', self.record.uuid, '@', self.record.gs_node_id, + ', number: ', self.record.number, + ', service: ', forwarding_entry.service, + ', destination: ',forwarding_entry.type, '=', forwarding_entry.id, + ', number: ', forwarding_entry.number); + end) + + return call_forwarding; +end + + +function PhoneNumber.call_forwarding_effective(self, service, source) + local conditions = {} + table.insert(conditions, '`phone_number_id` = ' .. self.record.id); + + if source then + table.insert(conditions, '`source` = "' .. source); + else + table.insert(conditions, '(`source` = "" OR `source` IS NULL)'); + end + + if service then + table.insert(conditions, '`call_forward_case_id` IN (SELECT `id` FROM `call_forward_cases` WHERE `value` = "' .. service .. '")'); + end + + -- get call forwarding entry + local sql_query = 'SELECT `destination`,`active`,`timeout`,`call_forwardable_type`, `call_forwardable_id` FROM `call_forwards` WHERE ' .. table.concat(conditions, ' AND ') .. ' ORDER BY `active` DESC LIMIT 1'; + local call_forwarding = nil; + + self.database:query(sql_query, function(entry) + call_forwarding = entry; + end) + + return call_forwarding; +end + + +function PhoneNumber.call_forwarding_off(self, service, source, delete) + local conditions = {} + table.insert(conditions, '`phone_number_id` = ' .. self.record.id); + + if source then + table.insert(conditions, '`source` = "' .. source); + else + table.insert(conditions, '(`source` = "" OR `source` IS NULL)'); + end + + if service then + table.insert(conditions, '`call_forward_case_id` IN (SELECT `id` FROM `call_forward_cases` WHERE `value` = "' .. service .. '")'); + end + + self.log:info('PHONE_NUMBER_CALL_FORWARDING_OFF - service: ', service, ', number: ', self.record.number); + + local call_forwarding_ids = {} + + local sql_query = 'SELECT `id` FROM `call_forwards` WHERE ' .. table.concat(conditions, ' AND '); + self.database:query(sql_query, function(record) + table.insert(call_forwarding_ids, record.id); + end) + + require 'common.call_forwarding' + local call_forwarding_class = common.call_forwarding.CallForwarding:new{ log = self.log, database = self.database, domain = self.domain }; + + for index, call_forwarding_id in ipairs(call_forwarding_ids) do + if tonumber(call_forwarding_id) then + local call_forwarding = call_forwarding_class:find_by_id(call_forwarding_id); + call_forwarding:presence_set('terminated'); + end + end + + -- set call forwarding entry inactive + local sql_query = 'UPDATE `call_forwards` SET `active` = FALSE, `updated_at` = NOW() WHERE ' .. table.concat(conditions, ' AND '); + + local call_forwards = {}; + + -- or delete call forwarding entry + if delete then + sql_query = 'SELECT * FROM `call_forwards` WHERE ' .. table.concat(conditions, ' AND '); + self.database:query(sql_query, function(forwarding_entry) + table.insert(call_forwards, forwarding_entry) + end) + sql_query = 'DELETE FROM `call_forwards` WHERE ' .. table.concat(conditions, ' AND '); + end + + if not self.database:query(sql_query) then + self.log:notice('PHONE_NUMBER_CALL_FORWARDING_OFF - call forwarding could not be deactivated - number: ', self.record.number); + return false; + end + + if delete then + require 'common.sync_log' + local sync_log_class = common.sync_log.SyncLog:new{ log = self.log, database = self.database, homebase_ip_address = '' } + + for index, call_forward in ipairs(call_forwards) do + sync_log_class:insert('CallForward', call_forward, 'destroy', nil); + end + end + + return true; +end + + +function PhoneNumber.call_forwarding_on(self, service, destination, destination_type, timeout, source) + require 'common.str' + if call_forwarding_service == 'noanswer' then + timeout = tonumber(timeout) or '30'; + else + timeout = 'NULL'; + end + + if source then + sql_query = 'SELECT `id`, `destination`, `call_forwardable_type`, `call_forward_case_id` FROM `call_forwards` \ + WHERE `phone_number_id` = ' .. self.record.id .. ' \ + AND `call_forward_case_id` IN (SELECT `id` FROM `call_forward_cases` WHERE `value` = "' .. service .. '") \ + AND `source` = "' .. source .. '" AND `phone_number_id` = ' .. self.record.id .. ' ORDER BY `active` DESC LIMIT 1'; + else + sql_query = 'SELECT `id`, `destination`, `call_forwardable_type`, `call_forward_case_id` FROM `call_forwards` \ + WHERE `phone_number_id` = ' .. self.record.id .. ' \ + AND `call_forward_case_id` IN (SELECT `id` FROM `call_forward_cases` WHERE `value` = "' .. service .. '") \ + AND (`source` = "" OR `source` IS NULL) AND `phone_number_id` = ' .. self.record.id .. ' ORDER BY `active` DESC LIMIT 1'; + end + + destination_type = destination_type or ''; + destination = destination or ''; + local service_id = nil; + local entry_id = 'NULL'; + + self.database:query(sql_query, function(record) + entry_id = record.id; + service_id = record.call_forward_case_id; + if common.str.blank(destination) then + if not common.str.blank(record.call_forwardable_type) then + destination_type = common.str.downcase(record.call_forwardable_type); + end + if not common.str.blank(record.destination) then + destination = record.destination; + end + end + end) + + if destination == '' and destination_type:lower() ~= 'voicemail' then + self.log:notice('PHONE_NUMBER_CALL_FORWARDING_ON - destination not specified - destination: ', destination, ', type: ', destination_type,', number: ' .. self.record.number); + return false; + end + + if destination_type == '' then + destination_type = 'PhoneNumber'; + end + + self.log:info('PHONE_NUMBER_CALL_FORWARDING_ON - service: ', service, ', number: ', self.record.number, ', destination: ', destination, ', type: ', destination_type, ', timeout: ', timeout); + + if not service_id then + sql_query = 'SELECT `id` FROM `call_forward_cases` WHERE `value` = "' .. service .. '"'; + self.database:query(sql_query, function(record) + service_id = tonumber(record.id); + end); + end + + sql_query = 'REPLACE INTO `call_forwards` \ + (`active`, `uuid`, `depth`, `updated_at`, `id`, `phone_number_id`, `call_forward_case_id`, `destination`, `call_forwardable_type`, `timeout`) \ + VALUES \ + (TRUE, UUID(), ' .. self.DEFAULT_CALL_FORWARDING_DEPTH .. ', NOW(), ' .. entry_id .. ', ' .. self.record.id .. ', ' .. service_id .. ', "' .. destination .. '", "' .. destination_type .. '", ' .. timeout .. ')' + + if not self.database:query(sql_query) then + self.log:error('PHONE_NUMBER_CALL_FORWARDING_ON - could not be activated - destination: ', destination, ', type: ', destination_type,', number: ' .. self.record.number); + return false; + end + + require 'common.call_forwarding' + local call_forwarding_class = common.call_forwarding.CallForwarding:new{ log = self.log, database = self.database, domain = self.domain }; + if tonumber(entry_id) then + local call_forwarding = call_forwarding_class:find_by_id(entry_id); + end + + if call_forwarding then + if destination_type:lower() == 'voicemail' then + call_forwarding:presence_set('early'); + else + call_forwarding:presence_set('confirmed'); + end + end + + return true; +end + + +function PhoneNumber.call_forwarding_toggle(self, service, source) + local call_forwarding = self:call_forwarding_effective(service, source); + + -- no call_forwarding entry: all forwarding is deactivated + if not call_forwarding then + return false; + end + + if tostring(call_forwarding.active) == '1' then + if self:call_forwarding_off(service, source) then + return {destination = call_forwarding.destination, destination_type = call_forwarding.destination_type, active = false}; + end + end + + if self:call_forwarding_on(service, call_forwarding.destination, call_forwarding.destination_type, call_forwarding.timeout, source) then + return {destination = call_forwarding.destination, destination_type = call_forwarding.destination_type, active = true}; + end + + return nil; +end + + +function PhoneNumber.call_forwarding_presence_set(self, presence_state, service) + service = service or 'always'; + local dialplan_function = 'f-cfutg'; + + if service == 'assistant' then + dialplan_function = 'f-cfatg'; + end + + require "dialplan.presence" + local presence = dialplan.presence.Presence:new(); + + presence:init{log = self.log, accounts = { dialplan_function .. '-' .. tostring(self.record.id) }, domain = self.domain, uuid = 'call_forwarding_number_' .. tostring(self.record.id)}; + + return presence:set(presence_state); +end + + +-- Retrieve ringtone +function PhoneNumber.ringtone(self, id) + id = id or self.record.id; + if not id then + return false; + end + + local sql_query = "SELECT * FROM `ringtones` WHERE `ringtoneable_type` = \"PhoneNumber\" AND `ringtoneable_id`=" .. self.record.id .. " LIMIT 1"; + local ringtone = nil; + + self.database:query(sql_query, function(entry) + ringtone = entry; + end) + + return ringtone; +end diff --git a/misc/freeswitch/scripts/common/routing_tables.lua b/misc/freeswitch/scripts/common/routing_tables.lua new file mode 100644 index 0000000..34d0143 --- /dev/null +++ b/misc/freeswitch/scripts/common/routing_tables.lua @@ -0,0 +1,66 @@ +-- Gemeinschaft 5 module: routing table functions +-- (c) AMOOMA GmbH 2012 +-- + +module(...,package.seeall) + +function expand_variables(line, variables_list) + variables_list = variables_list or {}; + + return (line:gsub('{([%a%d_]+)}', function(captured) + return variables_list[captured] or ''; + end)) +end + + +function match_route(entry, search_str, variables_list) + if not entry or not search_str then + return { error = 'No input values' }; + end + + local result = nil; + local success = nil; + success, result = pcall(string.find, search_str, entry[1]); + + if not success then + return { error = result, line = line } + elseif result then + local route = { + pattern = entry[1], + value = search_str:gsub(entry[1], expand_variables(entry[#entry], variables_list)), + } + + for index = 2, #entry-1 do + local attribute = entry[index]:match('^(.-)%s*='); + if attribute then + route[attribute] = entry[index]:match('=%s*(.-)$'); + end + end + + return route; + end + + return {}; +end + + +function match_caller_id(entry, search_str, variables_list) + if not entry or not search_str then + return { error = 'No input values' }; + end + local result = nil; + local success = nil; + success, result = pcall(string.find, search_str, entry[1]); + if not success then + return { error = result, line = line } + elseif result then + return { + value = search_str:gsub(entry[1], expand_variables(entry[4], variables_list)), + class = entry[2], + endpoint = entry[3], + pattern = entry[1], + } + end + + return {}; +end diff --git a/misc/freeswitch/scripts/common/sip_account.lua b/misc/freeswitch/scripts/common/sip_account.lua new file mode 100644 index 0000000..28a00df --- /dev/null +++ b/misc/freeswitch/scripts/common/sip_account.lua @@ -0,0 +1,137 @@ +-- Gemeinschaft 5 module: sip account class +-- (c) AMOOMA GmbH 2012 +-- + +module(...,package.seeall) + +SipAccount = {} + +-- Create SipAccount object +function SipAccount.new(self, arg) + arg = arg or {} + object = arg.object or {} + setmetatable(object, self); + self.__index = self; + self.class = 'sipaccount'; + self.log = arg.log; + self.database = arg.database; + self.record = arg.record; + return object; +end + + +function SipAccount.find_by_sql(self, where) + local sql_query = 'SELECT \ + `a`.`id`, \ + `a`.`uuid`, \ + `a`.`auth_name`, \ + `a`.`caller_name`, \ + `a`.`password`, \ + `a`.`voicemail_pin`, \ + `a`.`tenant_id`, \ + `a`.`sip_domain_id`, \ + `a`.`call_waiting`, \ + `a`.`clir`, \ + `a`.`clip`, \ + `a`.`clip_no_screening`, \ + `a`.`sip_accountable_type`, \ + `a`.`sip_accountable_id`, \ + `a`.`hotdeskable`, \ + `a`.`gs_node_id`, \ + `b`.`host` \ + FROM `sip_accounts` `a` JOIN `sip_domains` `b` ON `a`.`sip_domain_id` = `b`.`id` \ + WHERE ' .. where .. ' LIMIT 1'; + + local sip_account = nil; + self.database:query(sql_query, function(account_entry) + sip_account = SipAccount:new(self); + sip_account.record = account_entry; + sip_account.id = tonumber(account_entry.id); + sip_account.uuid = account_entry.uuid; + end) + + return sip_account; +end + + +-- find sip account by id +function SipAccount.find_by_id(self, id) + local sql_query = '`a`.`id`= ' .. tonumber(id); + return self:find_by_sql(sql_query); +end + +-- find sip account by uuid +function SipAccount.find_by_uuid(self, uuid) + local sql_query = '`a`.`uuid`= "' .. uuid .. '"'; + return self:find_by_sql(sql_query); +end + +-- Find SIP Account by auth_name +function SipAccount.find_by_auth_name(self, auth_name, domain) + local sql_query = '`a`.`auth_name`= "' .. auth_name .. '"'; + + if domain then + sql_query = sql_query .. ' AND `b`.`host` = "' .. domain .. '"'; + end + + return self:find_by_sql(sql_query); +end + +-- retrieve Phone Numbers for SIP Account +function SipAccount.phone_numbers(self, id) + id = id or self.record.id; + if not id then + return false; + end + + local sql_query = "SELECT * FROM `phone_numbers` WHERE `phone_numberable_type` = \"SipAccount\" AND `phone_numberable_id`=" .. self.record.id; + local phone_numbers = {} + + self.database:query(sql_query, function(entry) + table.insert(phone_numbers,entry.number); + end) + + return phone_numbers; +end + +-- retrieve Ringtone for SIP Account +function SipAccount.ringtone(self, id) + id = id or self.record.id; + if not id then + return false; + end + + local sql_query = "SELECT * FROM `ringtones` WHERE `ringtoneable_type` = \"SipAccount\" AND `ringtoneable_id`=" .. self.record.id .. " LIMIT 1"; + local ringtone = nil; + + self.database:query(sql_query, function(entry) + ringtone = entry; + end) + + return ringtone; +end + +function SipAccount.send_text(self, text) + local event = freeswitch.Event("NOTIFY"); + event:addHeader("profile", "gemeinschaft"); + event:addHeader("event-string", "text"); + event:addHeader("user", self.record.auth_name); + event:addHeader("host", self.record.host); + event:addHeader("content-type", "text/plain"); + event:addBody(text); + event:fire(); +end + + +function SipAccount.call_state(self) + local state = nil + local sql_query = "SELECT `callstate` FROM `channels` \ + WHERE `name` LIKE (\"\%" .. self.record.auth_name .. "@%\") \ + OR `name` LIKE (\"\%" .. self.record.auth_name .. "@%\") LIMIT 1"; + + self.database:query(sql_query, function(channel_entry) + state = channel_entry.callstate; + end) + + return state; +end diff --git a/misc/freeswitch/scripts/common/str.lua b/misc/freeswitch/scripts/common/str.lua new file mode 100644 index 0000000..b19f299 --- /dev/null +++ b/misc/freeswitch/scripts/common/str.lua @@ -0,0 +1,136 @@ +-- Gemeinschaft 5 module: string functions +-- (c) AMOOMA GmbH 2012 +-- + +module(...,package.seeall) + +function try(array, arguments) + local argument = arguments:match('^(.-)%.') or arguments; + local remaining_arguments = arguments:match('%.(.-)$'); + + if argument and type(array) == 'table' then + if remaining_arguments then + if type(array[argument]) == 'table' then + return try(array[argument], remaining_arguments); + else + return nil; + end + else + return array[argument]; + end + end + + return nil; +end + +-- to number +function to_n(value) + value = tostring(value):gsub('[^%d%.%+%-]', ''); + return tonumber(value) or 0; +end + +-- to integer +function to_i(value) + return math.floor(to_n(value)); +end + +-- to string +function to_s(value) + if value == nil then + return ''; + end + + return tostring(value); +end + +-- to boolean +function to_b(value) + if type(value) == 'boolean' then + return value; + elseif tonumber(value) then + return (tonumber(value) > 0); + else + return (tostring(value) == 'yes' or tostring(value) == 'true'); + end +end + +-- to array +function to_a(line, separator) + line = line or ''; + separator = separator or ';'; + local result = {} + line:gsub('([^' .. separator .. ']+)', function(entry) + table.insert(result, entry); + end); + + return result; +end + +-- stripped to array +function strip_to_a(line, separator) + + local result = {} + line:gsub('([^' .. separator .. ']+)', function(entry) + table.insert(result, (entry:gsub('^%s+', ''):gsub('%s+$', ''))); + end); + + return result; +end + +-- downcase +function downcase(value) + if value == nil then + return ''; + end + + return tostring(value):lower(); +end + +-- remove special characters +function to_ascii(value) + return (to_s(value):gsub('[^A-Za-z0-9%-%_ %(%)]', '')); +end + +-- to SQL +function to_sql(value) + if type(value) == 'boolean' then + return tostring(value):upper(); + elseif type(value) == 'number' then + return tostring(value); + elseif type(value) == 'string' then + return '"' .. value:gsub('"', '\\"'):gsub("'", "\\'") .. '"'; + else + return 'NULL'; + end +end + +-- to JSON +function to_json(value) + if type(value) == 'boolean' then + return tostring(value):lower(); + elseif type(value) == 'number' then + return tostring(value); + elseif type(value) == 'string' then + return '"' .. value:gsub('"', '\\"'):gsub("'", "\\'") .. '"'; + else + return 'null'; + end +end + +-- remove leading/trailing whitespace +function strip(value) + return (tostring(value):gsub('^%s+', ''):gsub('%s+$', '')); +end + +-- split string +function partition(value, separator) + value = tostring(value); + separator = separator or ':' + + return value:match('^(.-)' .. separator), value:match(separator .. '(.-)$'); +end + +-- check if value is empty string or nil +function blank(value) + return (value == nil or to_s(value) == ''); +end diff --git a/misc/freeswitch/scripts/common/sync_log.lua b/misc/freeswitch/scripts/common/sync_log.lua new file mode 100644 index 0000000..05b0dcf --- /dev/null +++ b/misc/freeswitch/scripts/common/sync_log.lua @@ -0,0 +1,39 @@ +-- Gemeinschaft 5 module: sync log class +-- (c) AMOOMA GmbH 2012 +-- + +module(...,package.seeall) + +SyncLog = {} + +-- create sync log object +function SyncLog.new(self, arg) + arg = arg or {} + object = arg.object or {} + setmetatable(object, self); + self.__index = self; + self.log = arg.log; + self.database = arg.database; + self.homebase_ip_address = arg.homebase_ip_address; + return object; +end + +-- create new entry +function SyncLog.insert(self, entry_name, entry_record, action, history_entries) + local content = {} + for key, value in pairs(entry_record) do + require 'common.str' + table.insert(content, '"'.. key ..'":' .. common.str.to_json(value)); + end + + local history = ''; + if action == 'update' then + history = 'Changed: ["' .. table.concat(history_entries, '","') .. '"]'; + end + + local sql_query = 'INSERT INTO `gs_cluster_sync_log_entries` (`waiting_to_be_synced`,`created_at`,`updated_at`,`class_name`,`action`,`content`,`history`,`homebase_ip_address`) \ + VALUES \ + (TRUE, NOW(), NOW(), \'' .. entry_name .. '\', \'' .. action .. '\', \'{' .. table.concat(content, ',') .. '}\', \'' .. history .. '\', \'' .. self.homebase_ip_address .. '\')'; + + return self.database:query(sql_query); +end -- cgit v1.2.3