A lotta stuff

This commit is contained in:
Christopher Cookman 2025-01-25 11:49:40 -07:00
parent 2171b25dd6
commit 5696df170f
7 changed files with 317 additions and 24 deletions

9
.env.example Normal file
View file

@ -0,0 +1,9 @@
PBX_HOSTNAME=what-gets-shown-to-users
FREEPBX_URL=http://your-pbx.local
FREEPBX_CLIENT_ID=
FREEPBX_CLIENT_SECRET=
DB_HOST=your-pbx.local
DB_USER=user
DB_PASS=pass
DISCORD_TOKEN=your-discord-token
OWNER_ID=your-discord-id

50
TODO.md
View file

@ -5,38 +5,48 @@
- [X] **/new** - Get an extension on the LiteNet Phone System.
- [X] **/delete** - Remove your extension from the LiteNet Phone System.
- [X] **/list** - List all extensions on the LiteNet Phone System.
- [ ] **/button** - Send the "Get an extension" button! *(Requires default_member_permissions: 0)*
- [X] **/button** - Send the "Get an extension" button! *(Requires default_member_permissions: 0)*
## Admin Commands
*(Requires default_member_permissions: 0)*
- [ ] **/admin**
- [ ] **silence** - Kill all ongoing calls.
- [ ] **reload** - Run an Asterisk reload.
- [ ] **reboot** - Reboot the server. *(LAST RESORT)*
- [X] **/admin**
- [X] **silence** - Kill all ongoing calls.
- [X] **reload** - Run an Asterisk reload.
- [X] **reboot** - Reboot the server. *(LAST RESORT)*
## Developer Commands
*(Requires default_member_permissions: 0)*
- [ ] **/dev**
- [ ] **fwconsole** - Run an `fwconsole` command.
- [ ] **command** (required) - The command to run.
- [ ] **asterisk** - Run an Asterisk CLI command.
- [ ] **command** (required) - The command to run.
- [ ] **shell** - Run a shell command.
- [ ] **command** (required) - The command to run.
- [ ] **restart** - Restart the bot.
- [X] **/dev**
- [X] **fwconsole** - Run an `fwconsole` command.
- [X] **command** (required) - The command to run.
- [X] **asterisk** - Run an Asterisk CLI command.
- [X] **command** (required) - The command to run.
- [X] **shell** - Run a shell command.
- [X] **command** (required) - The command to run.
- [X] **restart** - Restart the bot.
## Call Detail Records (CDR)
- [ ] **/cdr** - Get the call detail records for your extension.
- [ ] **start_date** (optional) - The start date for the CDR (mm/dd/yyyy).
- [ ] **end_date** (optional) - The end date for the CDR (mm/dd/yyyy).
## Context Menu Commands
- [ ] **Lookup Extension** - *(Type: 2)*
- [ ] **Create Extension** - *(Type: 2, Requires default_member_permissions: 0)*
- [ ] **Delete Extension** - *(Type: 2, Requires default_member_permissions: 0)*
## Management Commands (For admins to manage extensions)
*(Requires default_member_permissions: 0)*
- [X] **/manage**
- [X] **create** - Create an extension.
- [X] **extension** (required) - The extension number.
- [X] **discord user** (required) - The Discord user to assign the extension to.
- [X] **delete** - Delete an extension.
- [X] **discord user** (required) - The Discord user whose extension is to be deleted.
- [X] **lookup** - Lookup an extension. (With creds)
- [X] **extension** (required) - The extension number.
## Buttons (On button message)
- **Get an Extension** - Sends the "Get an extension" button.
- **See Your Info** - Sends the "See your info" button.
- **Delete Your Extension** - Sends the "Delete your extension" button.
- [X] **Get an Extension** - Sends the "Get an extension" button.
- [X] **See Your Info** - Sends the "See your info" button.
- [X] **Delete Your Extension** - Sends the "Delete your extension" button.
## Other Features
- [ ] **Extension List on Discord** - List all extensions on the Discord server. (Will do later)

View file

@ -173,5 +173,58 @@ module.exports = [
"required": true
}
]
},
{
"name": "manage",
"description": "Manage extensions",
"type": 1,
"default_member_permissions": 0,
"options": [
{
"name": "create",
"description": "Create an extension",
"type": 1,
"options": [
{
"name": "user",
"description": "The Discord user to assign the extension to",
"type": 6,
"required": true
},
{
"name": "extension",
"description": "The extension number",
"type": 4,
"required": false
}
]
},
{
"name": "delete",
"description": "Delete an extension",
"type": 1,
"options": [
{
"name": "user",
"description": "The Discord user whose extension is to be deleted",
"type": 6,
"required": true
}
]
},
{
"name": "lookup",
"description": "Lookup an extension",
"type": 1,
"options": [
{
"name": "user",
"description": "The Discord user whose extension is to be deleted",
"type": 6,
"required": true
}
]
}
]
}
]

View file

@ -52,8 +52,6 @@ global.fpbx = fpbx;
global.client = client;
global.log = log;
client.on('ready', async () => {
log.success(`Logged in as ${client.user.displayName}`);
const commands = require("./commands")

View file

@ -9,8 +9,37 @@ module.exports = {};
module.exports.execute = async (interaction) => {
interaction.channel.send({
embeds: [{
title: "Placeholder",
description: "I'll put the full embed here once we get it finalized.",
"title": "The LiteNet Community PBX",
"description": "The LiteNet Community PBX is hosted through, and is sponsored by SnakeCraft Hosting!\nOffering affordable game hosting, Discord bot hosting, and VPS services since 2020.\nGet started at https://go.litenet.tel/sch-affiliate\nCheck them out on [Discord](https://discord.gg/invite/xcnKUD8)\n\n-# The link above is an affiliate link. We will receive credits from any purchase made via this link.\n-# SnakeCraft Hosting has no administrative control over, nor has access to private information stored on LiteNet. SnakeCraft Hosting provides hosting for LiteNet free of charge. Specific details regarding their affiliate program can be found [here](https://my.snakecrafthosting.com/index.php?rp=/knowledgebase/4/Affiliate-Program-FAQs.html)",
"color": 7955428,
"fields": [
{
"name": "What's this?",
"value": "The community PBX is a public, free to use [FreePBX](https://freepbx.org) based phone system that any server member is welcome to get a number on!\nEveryone on the system has their own 4 digit number, that can be used to call between other members on the system.\nThe PBX runs on a SnakeCraft Hosting VPS graciously provided to us at no cost!"
},
{
"name": "What can it do?",
"value": "The LiteNet phone system offers many features, including but not limited to the following:\n- Free inbound/outbound calling via +1 (610) LITENET (548-3638)\n- Private Voicemail\n- Intercom/Paging\n- Conference Rooms\n- Direct dial access to [AstroCom](https://astrocom.tel)\n- [Full extension status page](https://pbx.litenet.tel/status)\n- And more!"
},
{
"name": "Privacy Policy",
"value": "LiteNet respects the privacy of all members, and as such, only very few select staff members have access to the system files directly. Voicemails are not tracked nor listened to under any circumstances. Call logs are kept and only reviewed during investigations into violations of community guidelines, or possible illegal activity. Call recordings may be kept at the request of any individual member, and will NOT be reviewed unless prior permission was given from said member.\nAll user data may be deleted by request, or by simply running the `/delete` command.\n\nIf you believe your privacy has been violated in any way, please don't hesistate to reach out to any of our staff members!"
},
{
"name": "Can it do `X`?",
"value": "Any specific questions are welcome to be asked in our <#1102782499756724239> chat!\nIf you have a suggestion for something we should add to the PBX or Discord server, feel free to leave it in <#1148099609428762634>!"
}
],
"footer": {
"text": "Made with <3 by Chris Chrome & The LiteNet Team • Sponsored by SnakeCraft Hosting",
"icon_url": "https://f.chrischro.me/assets/Snakecraft-Social-Media-purple-v2-smaller.png"
},
"image": {
"url": "https://f.chrischro.me/assets/litenet-full.png"
},
"thumbnail": {
"url": "https://f.chrischro.me/assets/Snakecraft-Social-Media-purple-v2-smaller-rounder.png"
}
}],
components: [
{

View file

@ -0,0 +1,111 @@
const { exec } = require('child_process');
const Discord = require('discord.js');
const { on } = require('events');
const pool = global.pool
const fpbx = global.fpbx
const client = global.client
const log = global.log
const runCommand = (command, onData) => {
try {
return new Promise((resolve, reject) => {
const process = exec(command);
let output = '';
const timeout = setTimeout(() => {
process.kill();
resolve({ output: output, code: 1 });
}, 60000);
process.stdout.on('data', (data) => {
output += data;
if (onData) {
onData(data);
}
});
process.stderr.on('data', (data) => {
onData(data);
});
process.on('close', (code) => {
clearTimeout(timeout);
if (code !== 0) {
resolve({ output: output, code: code });
}
resolve({ output: output, code: code });
});
process.on('error', (error) => {
reject(`error: ${error.message}`);
});
});
} catch (err) {
log.error(err)
}
}
module.exports = {};
module.exports.execute = async (interaction) => {
if (interaction.user.id !== process.env.OWNER_ID) {
await interaction.reply({ content: "You do not have permission to run this command.", ephemeral: true });
return;
}
const subcommand = interaction.options.getSubcommand();
switch (subcommand) {
case 'fwconsole': // Run an arbitrary fwconsole command
const command = interaction.options.getString('command');
await interaction.deferReply({ ephemeral: true });
var output = '';
runCommand(`fwconsole ${command}`, (data) => {
output += data;
if (output.length >= 1500) {
output = output.substring(output.length - 1500);
}
interaction.editReply({ content: `\`\`\`ansi\n${output}\`\`\``, ephemeral: true });
}).then((fullOutput) => {
output = output.length > 1500 ? output.substring(output.length - 1500) : output;
const buffer = Buffer.from(fullOutput.output, 'utf-8');
const attachment = new Discord.AttachmentBuilder(buffer, {name: 'output.txt'});
interaction.editReply({ content: `\`\`\`ansi\n${output}\`\`\`\nProcess returned code ${fullOutput.code}`, files: [attachment], ephemeral: true });
})
break;
case 'asterisk': // Run arbitrary asterisk command with asterisk -x "command"
const asteriskCommand = interaction.options.getString('command');
await interaction.deferReply({ ephemeral: true });
var output = '';
runCommand(`asterisk -x "${asteriskCommand}"`, (data) => {
output += data;
if (output.length >= 1500) {
output = output.substring(output.length - 1500);
}
interaction.editReply({ content: `\`\`\`ansi\n${output}\`\`\``, ephemeral: true });
}).then((fullOutput) => {
output = output.length > 1500 ? output.substring(output.length - 1500) : output;
const buffer = Buffer.from(fullOutput.output, 'utf-8');
const attachment = new Discord.AttachmentBuilder(buffer, {name: 'output.txt'});
interaction.editReply({ content: `\`\`\`ansi\n${output}\`\`\`\nProcess returned code ${fullOutput.code}`, files: [attachment], ephemeral: true });
})
break;
case 'shell': // Run any arbitrary shell command
const shellCommand = interaction.options.getString('command');
await interaction.deferReply({ ephemeral: true });
var output = '';
runCommand(shellCommand, (data) => {
output += data;
if (output.length >= 1500) {
output = output.substring(output.length - 1500);
}
interaction.editReply({ content: `\`\`\`ansi\n${output}\`\`\``, ephemeral: true });
}).then((fullOutput) => {
output = output.length > 1500 ? output.substring(output.length - 1500) : output;
const buffer = Buffer.from(fullOutput.output, 'utf-8');
const attachment = new Discord.AttachmentBuilder(buffer, {name: 'output.txt'});
interaction.editReply({ content: `Process returned code ${fullOutput.code}`, files: [attachment], ephemeral: true });
})
break;
}
}

View file

@ -0,0 +1,83 @@
const pool = global.pool
const fpbx = global.fpbx
const client = global.client
const log = global.log
module.exports = {};
module.exports.execute = async (interaction) => {
const subcommand = interaction.options.getSubcommand();
switch (subcommand) {
case 'create': // Create an extension for a user
await interaction.deferReply({ ephemeral: true });
var forUser = interaction.options.getUser('user');
var newExt = interaction.options.getInteger('extension') ? interaction.options.getInteger('extension') : await fpbx.getNextAvailableExtension();
var [lookup] = await pool.query('SELECT * FROM discord_users WHERE discordId = ?', [forUser.id]);
if (lookup) {
await interaction.editReply({ content: `User already has an extension, it's ${lookup.extension}!`, ephemeral: true });
return;
}
await interaction.editReply({ content: `Creating extension ${newExt} for ${forUser.username}`, ephemeral: true });
fpbx.addExtension(newExt, forUser.username).then(async (res) => {
if (res.addExtension.status != true) {
await interaction.editReply({ content: `Something went wrong :(`, ephemeral: true });
return;
}
await pool.query('INSERT INTO discord_users (discordId, extension) VALUES (?, ?)', [forUser.id, newExt]);
await interaction.editReply({ content: `Extension ${newExt} created! Getting info..`, ephemeral: true });
await fpbx.reload();
const extInfo = await fpbx.getExtension(newExt);
await interaction.editReply({ embeds: [{
title: "Extension Info",
description: `**PBX Address:** \`${process.env.PBX_HOSTNAME}\`\n**Extension:** \`${extInfo.fetchExtension.user.extension}\`\n**Name:** \`${extInfo.fetchExtension.user.name}\`\n**Password:** ||\`${extInfo.fetchExtension.user.extPassword}\`||`,
color: 0x00ff00
}], ephemeral: true })
}).catch(async (error) => {
log.error(error);
await interaction.editReply({ content: 'There was an error while creating the extension!', ephemeral: true });
});
break;
case "delete": // Delete an extension for a user
await interaction.deferReply({ ephemeral: true });
var forUser = interaction.options.getUser('user');
var [lookup] = await pool.query('SELECT * FROM discord_users WHERE discordId = ?', [forUser.id]);
if (!lookup) {
await interaction.editReply({ content: `User does not have an extension!`, ephemeral: true });
return;
}
await interaction.editReply({ content: `Deleting extension ${lookup.extension} for ${forUser.username}`, ephemeral: true });
fpbx.deleteExtension(lookup.extension).then(async (res) => {
console.log(res)
if (res[0].deleteExtension.status != true) {
await interaction.editReply({ content: `Something went wrong :(`, ephemeral: true });
return;
}
await pool.query('DELETE FROM discord_users WHERE discordId = ?', [forUser.id]);
await fpbx.reload();
await interaction.editReply({ content: `Extension ${lookup.extension} deleted!`, ephemeral: true });
}).catch(async (error) => {
log.error(error);
await interaction.editReply({ content: 'There was an error while deleting the extension!', ephemeral: true });
});
break;
case "lookup": // Get user extension info
var forUser = interaction.options.getUser('user');
var [lookup] = await pool.query('SELECT * FROM discord_users WHERE discordId = ?', [forUser.id]);
if (!lookup) {
await interaction.reply({ content: `User does not have an extension!`, ephemeral: true });
return;
}
const extInfo = await fpbx.getExtension(lookup.extension);
return await interaction.reply({
ephemeral: true, embeds: [
{
title: "Extension Info",
description: `**PBX Address:** \`${process.env.PBX_HOSTNAME}\`\n**Extension:** \`${extInfo.fetchExtension.user.extension}\`\n**Name:** \`${extInfo.fetchExtension.user.name}\`\n**Password:** ||\`${extInfo.fetchExtension.user.extPassword}\`||`,
color: 0x00ff00
}
]
});
break;
}
}