This commit is contained in:
Christopher Cookman 2023-02-07 00:40:35 -07:00
commit 34a024e8cf
Signed by: ChrisChrome
GPG key ID: A023A26E42C33A42
6 changed files with 3180 additions and 0 deletions

134
.gitignore vendored Normal file
View file

@ -0,0 +1,134 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
config.json
config.json.old
config.json.disabled

17
commands.json Normal file
View file

@ -0,0 +1,17 @@
[
{
"name": "whoami",
"description": "Get your extension info if you have one",
"type": 1
},
{
"name": "new",
"description": "Get an extension on the Telefreunde Community Phone System",
"type": 1
},
{
"name": "list",
"description": "List all extensions on the Telefreunde Community Phone System",
"type": 1
}
]

73
funcs.js Normal file
View file

@ -0,0 +1,73 @@
// Some random functions, as to not clutter the main file
module.exports = {
// Input validation
validateInput: function (input, type) {
switch (type) {
case 'extention':
// Check if input is a 3 digit number
if (input.length != 3) {
return false;
}
if (isNaN(input)) {
return false;
}
return true;
break;
}
},
// Generate GraphQL query
generateQuery: function (type, args) {
switch (type) {
case 'lookup':
return `query {
fetchExtension(extensionId: "${args.ext}") {
user {
extension
name
extPassword
voicemail
}
}
fetchVoiceMail(extensionId: "${args.ext}") {
password
email
}
}`
break;
case 'list':
return `query {
fetchAllExtensions {
extension {
user {
extension
name
voicemail
}
}
}
}`;
break;
case 'add':
return `mutation {
addExtension(input: {
extensionId: "${args.ext}"
name: "${args.name}"
email: "${args.uid}"
vmEnable: true
vmPassword: "${args.ext}"
}) {
status
}
}`;
break;
case 'reload':
return `mutation {
doreload(input: {clientMutationId: "${args.id}"}) {
status
}
}`;
break;
}
}
}

289
index.js Normal file
View file

@ -0,0 +1,289 @@
//Load static files
const config = require("./config.json");
const funcs = require("./funcs.js");
// FreePBX GraphQL Client
const {
FreepbxGqlClient,
gql
} = require("freepbx-graphql-client");
console.log(config.freepbx.clientid)
const pbxClient = new FreepbxGqlClient(config.freepbx.url, {
client: {
id: config.freepbx.clientid,
secret: config.freepbx.secret,
}
});
// Some functions for FreePBX
const createExtension = (ext, name, uid) => {
return new Promise((resolve, reject) => {
pbxClient.request(funcs.generateQuery('lookup', {
ext: ext
})).then((result) => {
// Extension exists
res = {
"status": "exists",
}
resolve(res);
}).catch((error) => {
// Extension does not exist, create it, reload, look it up, and return the result
pbxClient.request(funcs.generateQuery('add', {
ext: ext,
name: name,
uid: uid
})).then((result) => {
pbxClient.request(funcs.generateQuery('reload', {
id: "CreateExt"
})).then((result) => {
pbxClient.request(funcs.generateQuery('lookup', {
ext: ext
})).then((result) => {
res = {
"status": "created",
"result": result
}
resolve(res);
}).catch((error) => {
reject(error);
});
}).catch((error) => {
reject(error);
});
}).catch((error) => {
reject(error);
});
});
});
}
const lookupExtension = (ident, type) => { // type is either "ext" or "uid"
return new Promise((resolve, reject) => {
switch (type) {
case "ext":
pbxClient.request(funcs.generateQuery('lookup', {
ext: ident
})).then((result) => {
res = {
"status": "exists",
"result": result
}
resolve(res);
}).catch((error) => {
res = {
"status": "notfound",
"result": error
}
reject(res);
});
break;
case "uid":
// Find the extension based on Discord ID in the voicemail email field
pbxClient.request(funcs.generateQuery('list', {})).then(async (result) => {
// loop through all extensions, run a lookup on each one, and return the first one that matches
var found = false;
var ext = "";
var count = 0;
result.fetchAllExtensions.extension.forEach(async (ext) => {
pbxClient.request(funcs.generateQuery('lookup', {
ext: ext.user.extension
})).then((result) => {
if (result.fetchVoiceMail.email == ident && !found) {
found = true;
ext = result;
clearInterval(x);
resolve({
"status": "exists",
"result": ext
})
}
count++;
}).catch((error) => {
reject(error);
});
});
x = setInterval(() => {
if (count == result.fetchAllExtensions.extension.length) {
clearInterval(x);
if (!found) {
reject("Not found");
}
}
}, 100);
}).catch((error) => {
reject(error);
});
break;
default:
reject("Invalid type");
}
});
}
const findNextExtension = () => {
return new Promise((resolve, reject) => {
pbxClient.request(funcs.generateQuery('list', {})).then((result) => {
// Find the highest extension
var highest = 0;
// output looks like {fetchAllExtensions: { extension: [{user:{extension: 100, name: "Test"}}]}}
result.fetchAllExtensions.extension.forEach((ext) => {
if (ext.user.extension > highest) {
highest = ext.user.extension;
}
});
// Return the next extension
res = {
"status": "success",
"result": String(Number(highest) + 1)
}
resolve(res);
}).catch((error) => {
reject(error);
});
});
}
// Load Discord.js
const Discord = require("discord.js");
const {
REST,
Routes
} = require('discord.js');
const dcClient = new Discord.Client({
intents: ["Guilds", "GuildMembers"]
});
const rest = new REST({
version: '10'
}).setToken(config.discord.token);
dcClient.on('ready', () => {
console.log(`Logged in as ${dcClient.user.tag}!`);
// Set up application commands
const commands = require('./commands.json');
(async () => {
try {
console.log('Started refreshing application (/) commands.');
await rest.put(
Routes.applicationGuildCommands(dcClient.user.id, config.discord.guildId), {
body: commands
}
);
console.log('Successfully reloaded application (/) commands.');
} catch (error) {
console.error(error);
}
})();
});
dcClient.on('interactionCreate', async interaction => {
if (!interaction.isCommand()) return;
if (interaction.user.id != config.discord.devId) return; // Only allow the dev to use this bot (for now)
const {
commandName
} = interaction;
switch (commandName) {
case "new":
interaction.reply({
content: "Please Wait...",
ephemeral: true
})
lookupExtension(interaction.user.id, "uid").then((result) => {
if (result.status == "exists") {
// The user already has an extension, return an ephemeral message saying so
interaction.editReply({
content: "You already have an extension!",
ephemeral: true
});
}
}).catch((error) => {
// The user doesn't have an extension, create one
findNextExtension().then((result) => {
if (result.status == "success") {
let uid = interaction.user.id;
let ext = result.result;
let name = interaction.user.tag;
interaction.editReply(`Creating extension ${ext}...`)
console.log(`Creating extension ${ext} for ${name} (${uid})`)
// Create the extension
createExtension(ext, name, uid).then((result) => {
if (result.status == "created") {
interaction.editReply({
content: "",
embeds: [{
"title": "Extension Created!",
"color": 0x00ff00,
"description": `The SIP server is \`${config.freepbx.server}\``,
"fields": [{
"name": "Extension/Username",
"value": ext
},
{
"name": "Password",
"value": result.result.fetchExtension.user.extPassword
}
]
}]
})
// Add the role to the user on Discord based on the ID in the config file
let role = interaction.guild.roles.cache.find(role => role.id === config.discord.roleId);
interaction.member.roles.add(role);
}
}).catch((error) => {
interaction.reply(`Error creating extension: ${error}`);
});
}
}).catch((error) => {
interaction.reply(`Error finding next available extension: ${error}`);
});
});
break;
case "whoami":
interaction.reply({ content: "Please Wait...", ephemeral: true })
lookupExtension(interaction.user.id, "uid").then((result) => {
if (result.status == "exists") {
// The user already has an extension, return an ephemeral message saying so
interaction.editReply({
content: "",
embeds: [{
"title": "Extension Info",
"color": 0x00ff00,
"description": `The SIP server is \`${config.freepbx.server}\``,
"fields": [{
"name": "Extension/Username",
"value": result.result.fetchExtension.user.extension
},
{
"name": "Password",
"value": result.result.fetchExtension.user.extPassword
}
]
}],
ephemeral: true
})
}
}).catch((error) => {
// The user doesn't have an extension, create one
console.log(error)
interaction.reply({
content: "You don't have an extension!",
ephemeral: true
});
});
break;
case "list":
interaction.reply({
content: "Not Implemented Yet",
ephemeral: true
})
break;
default:
break;
}
});
dcClient.login(config.discord.token);

2651
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

16
package.json Normal file
View file

@ -0,0 +1,16 @@
{
"name": "freepbx-manager",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"discord.js": "^14.7.1",
"freepbx-graphql-client": "^0.1.1",
"sqlite3": "^5.1.4"
}
}