From 68d6402ffd1b8fb07b0b76070d9f30326ee28e72 Mon Sep 17 00:00:00 2001 From: ChrisChrome Date: Tue, 11 Feb 2025 08:28:41 -0700 Subject: [PATCH] - Disable unfinished CDR commands - Impliment delayed auto-delete of orphaned extensions, with auto cancel. --- commands.js | 48 ++++++++++++++---------- deletions.js | 48 ++++++++++++++++++++++++ index.js | 48 +++++++++++++++++++++++- interactionHandlers/commands/admin.js | 13 +++++++ interactionHandlers/common/getExtInfo.js | 3 +- migrations/002_init_deletions_table.sql | 5 +++ package-lock.json | 22 +++++++++++ package.json | 3 +- 8 files changed, 168 insertions(+), 22 deletions(-) create mode 100644 deletions.js create mode 100644 migrations/002_init_deletions_table.sql diff --git a/commands.js b/commands.js index 895b1a5..e37ebea 100644 --- a/commands.js +++ b/commands.js @@ -105,6 +105,16 @@ module.exports = [ "description": "Reboot the server (LAST RESORT)", "type": 1, "default_member_permissions": 0 + }, + { + "name": "list-deletions", + "description": "List pending deletions", + "type": 1 + }, + { + "name": "check-deletions", + "description": "Check for orphaned extensions", + "type": 1 } ] }, @@ -164,25 +174,25 @@ module.exports = [ } ] }, - { - "name": "cdr", - "description": "Get the call detail records for your extension", - "type": 1, - "options": [ - { - "name": "start_date", - "description": "The start date for the CDR (mm/dd/yyyy)", - "type": 3, - "required": false - }, - { - "name": "end_date", - "description": "The end date for the CDR (mm/dd/yyyy)", - "type": 3, - "required": false - } - ] - }, + // { + // "name": "cdr", + // "description": "Get the call detail records for your extension", + // "type": 1, + // "options": [ + // { + // "name": "start_date", + // "description": "The start date for the CDR (mm/dd/yyyy)", + // "type": 3, + // "required": false + // }, + // { + // "name": "end_date", + // "description": "The end date for the CDR (mm/dd/yyyy)", + // "type": 3, + // "required": false + // } + // ] + // }, { "name": "lookup", "description": "Find extension by Discord user", diff --git a/deletions.js b/deletions.js new file mode 100644 index 0000000..07bf68e --- /dev/null +++ b/deletions.js @@ -0,0 +1,48 @@ +const pool = global.pool +const fpbx = global.fpbx +const client = global.client +const log = global.log + +module.exports = {}; + +module.exports.handleScheduled = async () => { + const deletions = await pool.query('SELECT * FROM discord_deletions'); + if (!deletions) return; + + for (const deletion of deletions) { + const guild = client.guilds.cache.get(process.env.DISCORD_GUILD); + const member = guild ? await guild.members.fetch(deletion.discordId).catch(() => null) : null; + + if (member) { + log.info(`User ${deletion.discordId} rejoined, removing deletion`); + await pool.query('DELETE FROM discord_deletions WHERE discordId = ?', [deletion.discordId]); + return; + } else if (new Date(deletion.deleteAt) < new Date()) { + log.info(`Deleting extension for ${deletion.discordId}`); + await fpbx.deleteExtension(deletion.extension); + await fpbx.reload(); + await pool.query('DELETE FROM discord_deletions WHERE discordId = ?', [deletion.discordId]); + return; + } + } +}; + +module.exports.findOrphans = async () => { + const users = await pool.query('SELECT * FROM discord_users'); + const deletions = await pool.query('SELECT * FROM discord_deletions'); + const guild = client.guilds.cache.get(process.env.DISCORD_GUILD); + if (!users) return + for (const user of users) { + const member = guild ? await guild.members.fetch(user.discordId).catch(() => null) : null; + if (!member) { + log.info(`User ${user.discordId} not found in guild, marking for deletion`); + const isMarkedForDeletion = deletions.some(deletion => deletion.discordId === user.discordId); + if (isMarkedForDeletion) { + log.info(`User ${user.discordId} is already marked for deletion`); + continue; + } + const deleteAt = new Date(Date.now() + 60 * 60 * 1000); // 1 hour in the future + await pool.query('INSERT INTO discord_deletions (discordId, extension, deleteAt) VALUES (?, ?, ?)', [user.discordId, user.extension, deleteAt]); + } + } +}; \ No newline at end of file diff --git a/index.js b/index.js index 16ddbec..7a615be 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,5 @@ require("dotenv").config(); - +const cron = require("node-cron") const fs = require('fs'); const mariadb = require("mariadb"); const pool = mariadb.createPool({ @@ -53,6 +53,8 @@ global.fpbx = fpbx; global.client = client; global.log = log; +const deletion = require("./deletions.js"); + client.on('ready', async () => { log.success(`Logged in as ${client.user.displayName}`); const commands = require("./commands") @@ -84,6 +86,25 @@ client.on('ready', async () => { log.error(error) } })(); + + cron.schedule('* * * * *', () => { + try { + deletion.handleScheduled(); + } catch (error) { + log.error(`Failed to execute deletion task: ${error}`); + } + }); + + cron.schedule('0 * * * *', () => { + log.info("Checking for orphaned extensions..."); + try { + deletion.findOrphans(); + } catch (error) { + log.error(`Failed to execute orphan task: ${error}`); + } + }); + deletion.findOrphans(); + deletion.handleScheduled(); }); client.on('interactionCreate', async interaction => { @@ -116,6 +137,31 @@ client.on('interactionCreate', async interaction => { } }); +client.on('guildMemberAdd', async member => { + log.info(`User ${member.id} joined`); + const [deletion] = await pool.query('SELECT * FROM discord_deletions WHERE discordId = ?', [member.id]); + if (deletion) { + await pool.query('DELETE FROM discord_deletions WHERE discordId = ?', [member.id]); + log.info(`User ${member.id} rejoined, removing deletion`); + } +}); + +client.on('guildMemberRemove', async member => { + log.info(`User ${member.id} left`); + const [deletion] = await pool.query('SELECT * FROM discord_deletions WHERE discordId = ?', [member.id]); + const [user] = await pool.query('SELECT * FROM discord_users WHERE discordId = ?', [member.id]); + if (!user) return; + log.info("User has extension...") + log.debug(typeof(deletion)) + log.debug(JSON.stringify(deletion)) + if (!deletion) { + log.info("No deletion, adding") + const deleteAt = new Date(Date.now() + 60 * 60 * 1000); // 1 hour in the future + await pool.query('INSERT INTO discord_deletions (discordId, extension, deleteAt) VALUES (?, ?, ?)', [member.id, user.extension, deleteAt]); + log.info(`User ${member.id} left, marking for deletion`); + } +}); + if (fs.existsSync("./import.json")) { const importData = JSON.parse(fs.readFileSync("./import.json", "utf8")); diff --git a/interactionHandlers/commands/admin.js b/interactionHandlers/commands/admin.js index 46f3928..7c4b38a 100644 --- a/interactionHandlers/commands/admin.js +++ b/interactionHandlers/commands/admin.js @@ -64,5 +64,18 @@ module.exports.execute = async (interaction) => { }); runCommand('reboot 0'); break; + case 'list-deletions': + const deletions = await pool.query('SELECT * FROM discord_deletions'); + if (!deletions.length) { + await interaction.reply({ content: 'No pending deletions.', ephemeral: true }); + return; + } + + const deletionList = deletions.map((deletion) => { + return `Discord ID: ${deletion.discordId}, Extension: ${deletion.extension}, Delete At: `; + }); + + await interaction.reply({ content: deletionList.join('\n'), ephemeral: true }); + break; } } \ No newline at end of file diff --git a/interactionHandlers/common/getExtInfo.js b/interactionHandlers/common/getExtInfo.js index 16f612d..c66f3af 100644 --- a/interactionHandlers/common/getExtInfo.js +++ b/interactionHandlers/common/getExtInfo.js @@ -11,8 +11,9 @@ module.exports.execute = async (interaction) => { await interaction.reply({ content: `We're sorry, It doesn't look like you have an extension!`, ephemeral: true }); return; } + await interaction.deferReply({ ephemeral: true }); const extInfo = await fpbx.getExtension(lookup.extension); - await interaction.reply({ + await interaction.editReply({ ephemeral: true, embeds: [ { title: "Your Extension Info", diff --git a/migrations/002_init_deletions_table.sql b/migrations/002_init_deletions_table.sql new file mode 100644 index 0000000..eca6377 --- /dev/null +++ b/migrations/002_init_deletions_table.sql @@ -0,0 +1,5 @@ +CREATE TABLE IF NOT EXISTS discord_deletions ( + extension VARCHAR(20) PRIMARY KEY, + discordId VARCHAR(25) NOT NULL, + deleteAt TIMESTAMP NOT NULL +); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 7db98ed..a7afd78 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "dotenv": "^16.4.7", "freepbx-graphql-client": "^0.1.1", "mariadb": "^3.2.0", + "node-cron": "^3.0.3", "ping": "^0.4.4", "sqlite3": "^5.1.4", "ssh2": "^1.15.0" @@ -1247,6 +1248,18 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==" }, + "node_modules/node-cron": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", + "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", + "license": "ISC", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -1725,6 +1738,15 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/package.json b/package.json index 4f1610e..1eb486d 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "start": "node index.js" + "start": "node index.js" }, "author": "", "license": "ISC", @@ -16,6 +16,7 @@ "dotenv": "^16.4.7", "freepbx-graphql-client": "^0.1.1", "mariadb": "^3.2.0", + "node-cron": "^3.0.3", "ping": "^0.4.4", "sqlite3": "^5.1.4", "ssh2": "^1.15.0"