add pbx calling capabilities to page
parent
948f3f994e
commit
700a912e28
|
@ -9,6 +9,9 @@ src/config.yaml
|
||||||
build/config.yaml
|
build/config.yaml
|
||||||
.vscode
|
.vscode
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
|
google.json
|
||||||
|
src/google.json
|
||||||
|
build/google.json
|
||||||
|
|
||||||
# Build/Distribution Files
|
# Build/Distribution Files
|
||||||
build
|
build
|
||||||
|
|
|
@ -30,6 +30,9 @@
|
||||||
"typescript": "^3.9.2"
|
"typescript": "^3.9.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@google-cloud/text-to-speech": "^3.1.2",
|
||||||
|
"@types/ari-client": "^2.2.2",
|
||||||
|
"ari-client": "^2.2.0",
|
||||||
"awesome-phonenumber": "^2.41.0",
|
"awesome-phonenumber": "^2.41.0",
|
||||||
"axios": "^0.19.2",
|
"axios": "^0.19.2",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
|
|
|
@ -30,6 +30,7 @@ export default class LocalStorage {
|
||||||
const data = gzipSync(JSON.stringify(setup));
|
const data = gzipSync(JSON.stringify(setup));
|
||||||
writeFileSync(this.storagePath, data);
|
writeFileSync(this.storagePath, data);
|
||||||
}
|
}
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,7 +1,77 @@
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
import { Client, RichEmbed } from '.';
|
import { Client, RichEmbed } from '.';
|
||||||
|
import { ScoreHistoricalRaw } from '../models/ScoreHistorical';
|
||||||
|
|
||||||
export default class Report {
|
export default class Report {
|
||||||
public static async soft(userID: string) {
|
public client: Client;
|
||||||
//
|
|
||||||
|
constructor(client: Client) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async soft(userID: string) {
|
||||||
|
const report = await this.client.db.Score.findOne({ userID });
|
||||||
|
if (!report) return null;
|
||||||
|
let totalScore: number;
|
||||||
|
let activityScore: number;
|
||||||
|
const moderationScore = Math.round(report.moderation);
|
||||||
|
let roleScore: number;
|
||||||
|
let cloudServicesScore: number;
|
||||||
|
const otherScore = Math.round(report.other);
|
||||||
|
let miscScore: number;
|
||||||
|
|
||||||
|
if (report.total < 200) totalScore = 0;
|
||||||
|
else if (report.total > 800) totalScore = 800;
|
||||||
|
else totalScore = Math.round(report.total);
|
||||||
|
|
||||||
|
if (report.activity < 10) activityScore = 0;
|
||||||
|
else if (report.activity > Math.floor((Math.log1p(3000 + 300 + 200 + 100) * 12))) activityScore = Math.floor((Math.log1p(3000 + 300 + 200 + 100) * 12));
|
||||||
|
else activityScore = Math.round(report.activity);
|
||||||
|
|
||||||
|
if (report.roles <= 0) roleScore = 0;
|
||||||
|
else if (report.roles > 54) roleScore = 54;
|
||||||
|
else roleScore = Math.round(report.roles);
|
||||||
|
|
||||||
|
if (report.staff <= 0) miscScore = 0;
|
||||||
|
else miscScore = Math.round(report.staff);
|
||||||
|
|
||||||
|
if (report.cloudServices === 0) cloudServicesScore = 0;
|
||||||
|
else if (report.cloudServices > 10) cloudServicesScore = 10;
|
||||||
|
else cloudServicesScore = Math.round(report.cloudServices);
|
||||||
|
|
||||||
|
const historicalData = await this.client.db.ScoreHistorical.find({ userID: member.userID }).lean().exec();
|
||||||
|
const array: ScoreHistoricalRaw[] = [];
|
||||||
|
for (const data of historicalData) {
|
||||||
|
let total: number;
|
||||||
|
let activity: number;
|
||||||
|
const moderation = Math.round(data.report.moderation);
|
||||||
|
let role: number;
|
||||||
|
let cloud: number;
|
||||||
|
const other = Math.round(data.report.other);
|
||||||
|
let misc: number;
|
||||||
|
|
||||||
|
if (data.report.total < 200) total = 0;
|
||||||
|
else if (data.report.total > 800) total = 800;
|
||||||
|
else total = Math.round(data.report.total);
|
||||||
|
|
||||||
|
if (data.report.activity < 10) activity = 0;
|
||||||
|
else if (data.report.activity > Math.floor((Math.log1p(3000 + 300 + 200 + 100) * 12))) activity = Math.floor((Math.log1p(3000 + 300 + 200 + 100) * 12));
|
||||||
|
else activity = Math.round(data.report.activity);
|
||||||
|
|
||||||
|
if (data.report.roles <= 0) role = 0;
|
||||||
|
else if (data.report.roles > 54) role = 54;
|
||||||
|
else role = Math.round(data.report.roles);
|
||||||
|
|
||||||
|
if (data.report.staff <= 0) role = 0;
|
||||||
|
else misc = Math.round(data.report.staff);
|
||||||
|
|
||||||
|
if (data.report.cloudServices === 0) cloud = 0;
|
||||||
|
else if (data.report.cloudServices > 10) cloud = 10;
|
||||||
|
else cloud = Math.round(data.report.cloudServices);
|
||||||
|
|
||||||
|
data.report.total = total; data.report.activity = activity; data.report.moderation = moderation; data.report.roles = role; data.report.cloudServices = cloud; data.report.other = other; data.report.staff = misc;
|
||||||
|
|
||||||
|
array.push(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
/* eslint-disable no-bitwise */
|
/* eslint-disable no-bitwise */
|
||||||
import nodemailer from 'nodemailer';
|
import nodemailer from 'nodemailer';
|
||||||
|
import GoogleTTS, { TextToSpeechClient } from '@google-cloud/text-to-speech';
|
||||||
|
import childProcess from 'child_process';
|
||||||
|
import ARIClient from 'ari-client';
|
||||||
import signale from 'signale';
|
import signale from 'signale';
|
||||||
import { Member, Message, Guild, PrivateChannel, GroupChannel, Role, AnyGuildChannel, WebhookPayload } from 'eris';
|
import { Member, Message, Guild, PrivateChannel, GroupChannel, Role, AnyGuildChannel, WebhookPayload } from 'eris';
|
||||||
import { Client, Command, Moderation, RichEmbed } from '.';
|
import { Client, Command, Moderation, RichEmbed } from '.';
|
||||||
|
@ -14,6 +17,10 @@ export default class Util {
|
||||||
|
|
||||||
public transporter: nodemailer.Transporter;
|
public transporter: nodemailer.Transporter;
|
||||||
|
|
||||||
|
public ari: ARIClient.Client;
|
||||||
|
|
||||||
|
public tts: TextToSpeechClient;
|
||||||
|
|
||||||
constructor(client: Client) {
|
constructor(client: Client) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.moderation = new Moderation(this.client);
|
this.moderation = new Moderation(this.client);
|
||||||
|
@ -28,6 +35,16 @@ export default class Util {
|
||||||
port: 587,
|
port: 587,
|
||||||
auth: { user: 'internal', pass: this.client.config.emailPass },
|
auth: { user: 'internal', pass: this.client.config.emailPass },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async load() {
|
||||||
|
this.ari = await ARIClient.connect('http://10.8.0.1:8088/ari', 'cr0', this.client.config.ariClientKey);
|
||||||
|
this.ari.start('cr-zero');
|
||||||
|
|
||||||
|
process.env.GOOGLE_APPLICATION_CREDENTIALS = `${__dirname}/../../google.json`;
|
||||||
|
this.tts = new GoogleTTS.TextToSpeechClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
get emojis() {
|
get emojis() {
|
||||||
|
@ -58,6 +75,27 @@ export default class Util {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
public async exec(command: string, options: childProcess.ExecOptions = {}): Promise<string> {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
let output = '';
|
||||||
|
const writeFunction = (data: string|Buffer|Error) => {
|
||||||
|
output += `${data}`;
|
||||||
|
};
|
||||||
|
const cmd = childProcess.exec(command, options);
|
||||||
|
cmd.stdout.on('data', writeFunction);
|
||||||
|
cmd.stderr.on('data', writeFunction);
|
||||||
|
cmd.on('error', writeFunction);
|
||||||
|
cmd.once('close', (code, signal) => {
|
||||||
|
cmd.stdout.off('data', writeFunction);
|
||||||
|
cmd.stderr.off('data', writeFunction);
|
||||||
|
cmd.off('error', writeFunction);
|
||||||
|
setTimeout(() => {}, 1000);
|
||||||
|
if (code !== 0) rej(new Error(`Command failed: ${command}\n${output}`));
|
||||||
|
res(output);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves a command
|
* Resolves a command
|
||||||
* @param query Command input
|
* @param query Command input
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
/* eslint-disable no-case-declarations */
|
/* eslint-disable no-case-declarations */
|
||||||
/* eslint-disable no-continue */
|
/* eslint-disable no-continue */
|
||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import { randomBytes } from 'crypto';
|
||||||
import { Message, TextableChannel } from 'eris';
|
import { Message, TextableChannel } from 'eris';
|
||||||
import { Client, Command, RichEmbed } from '../class';
|
import { Client, Command, RichEmbed } from '../class';
|
||||||
|
|
||||||
|
@ -67,6 +69,18 @@ export default class Page extends Command {
|
||||||
return this.success(message.channel, 'You will now receive notifications by email for pages.');
|
return this.success(message.channel, 'You will now receive notifications by email for pages.');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'phone':
|
||||||
|
if (args[2] === 'off') {
|
||||||
|
if (pager.receivePhone === false) return this.error(message.channel, 'You are already set to not receive PBX calls.');
|
||||||
|
await pager.updateOne({ $set: { receivePhone: false } });
|
||||||
|
return this.success(message.channel, 'You will no longer receive PBX calls for pages.');
|
||||||
|
}
|
||||||
|
if (args[2] === 'on') {
|
||||||
|
if (pager.receivePhone === true) return this.error(message.channel, 'You are already set to receive PBX calls.');
|
||||||
|
await pager.updateOne({ $set: { receivePhone: true } });
|
||||||
|
return this.success(message.channel, 'You will now receive PBX calls for pages.');
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
this.error(message.channel, 'Invalid response provided.');
|
this.error(message.channel, 'Invalid response provided.');
|
||||||
break;
|
break;
|
||||||
|
@ -88,7 +102,7 @@ export default class Page extends Command {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public logPage(sender: { number: string, user?: string }, recipient: { number: string, user?: string }, type: 'discord' | 'email', code: string): void {
|
public logPage(sender: { number: string, user?: string }, recipient: { number: string, user?: string }, type: 'discord' | 'email' | 'phone', code: string): void {
|
||||||
const chan = <TextableChannel> this.mainGuild.channels.get('722636436716781619');
|
const chan = <TextableChannel> this.mainGuild.channels.get('722636436716781619');
|
||||||
chan.createMessage(`***[${type.toUpperCase()}] \`${sender.number} (${sender.user ? sender.user : ''})\` sent a page to \`${recipient.number} (${recipient.user ? recipient.user : ''})\` with code \`${code}\`.***`);
|
chan.createMessage(`***[${type.toUpperCase()}] \`${sender.number} (${sender.user ? sender.user : ''})\` sent a page to \`${recipient.number} (${recipient.user ? recipient.user : ''})\` with code \`${code}\`.***`);
|
||||||
this.client.util.signale.log(`PAGE (${type.toUpperCase()})| TO: ${recipient.number}, FROM: ${sender.number}, CODE: ${code}`);
|
this.client.util.signale.log(`PAGE (${type.toUpperCase()})| TO: ${recipient.number}, FROM: ${sender.number}, CODE: ${code}`);
|
||||||
|
@ -170,6 +184,51 @@ export default class Page extends Command {
|
||||||
html: `<h1>Page</h1>${options?.emergencyNumber ? `<h2>[SEN#${options.emergencyNumber}]` : ''}<strong>Recipient PN:</strong> ${recipientNumber}<br><strong>Sender PN:</strong> ${senderNumber} (${sender ? `${sender.username}#${sender.discriminator}` : ''})<br><strong>Initial Command:</strong> https://discordapp.com/channels/${this.mainGuild.id}/${message.channel.id}/${message.id} (<#${message.channel.id}>)<br><br><strong>Pager Code:</strong> ${code} (${this.local.codeDict.get(code)})${txt ? `<br><strong>Message:</strong> ${txt}` : ''}`,
|
html: `<h1>Page</h1>${options?.emergencyNumber ? `<h2>[SEN#${options.emergencyNumber}]` : ''}<strong>Recipient PN:</strong> ${recipientNumber}<br><strong>Sender PN:</strong> ${senderNumber} (${sender ? `${sender.username}#${sender.discriminator}` : ''})<br><strong>Initial Command:</strong> https://discordapp.com/channels/${this.mainGuild.id}/${message.channel.id}/${message.id} (<#${message.channel.id}>)<br><br><strong>Pager Code:</strong> ${code} (${this.local.codeDict.get(code)})${txt ? `<br><strong>Message:</strong> ${txt}` : ''}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const id of recipientEntry.discordIDs) {
|
||||||
|
const pager = await this.client.db.PagerNumber.findOne({ individualAssignID: id });
|
||||||
|
if (!pager || !pager.receivePhone) continue;
|
||||||
|
const member = await this.client.db.Staff.findOne({ userID: pager.individualAssignID });
|
||||||
|
if (!member || !member.extension) continue;
|
||||||
|
|
||||||
|
const fileExtension = `${randomBytes(10).toString('hex')}`;
|
||||||
|
|
||||||
|
const [response] = await this.client.util.tts.synthesizeSpeech({
|
||||||
|
input: { text: `Hello, this call was automatically dialed by the Library of Code Private Branch Exchange. You have received a page from Pager Number, ${recipientNumber}. The Pager Code is ${code}. Please check your Direct Messages on Discord for further information.` },
|
||||||
|
// Select the language and SSML voice gender (optional)
|
||||||
|
voice: { languageCode: 'en-US', ssmlGender: 'MALE' },
|
||||||
|
// select the type of audio encoding
|
||||||
|
audioConfig: { audioEncoding: 'OGG_OPUS' },
|
||||||
|
});
|
||||||
|
await fs.writeFile(`/tmp/${fileExtension}.ogg`, response.audioContent, 'binary');
|
||||||
|
await this.client.util.exec(`ffmpeg -i /tmp/${fileExtension}.ogg -af "highpass=f=300, lowpass=f=3400" -ar 8000 -ac 1 -ab 64k -f mulaw /tmp/${fileExtension}.ulaw`);
|
||||||
|
|
||||||
|
const chan = await this.client.util.ari.channels.originate({
|
||||||
|
endpoint: `PJSIP/${member.extension}`,
|
||||||
|
extension: `${member.extension}`,
|
||||||
|
callerId: `LOC PBX - PAGE FRM ${senderNumber} <00>`,
|
||||||
|
context: 'from-internal',
|
||||||
|
priority: 1,
|
||||||
|
app: 'cr-zero',
|
||||||
|
});
|
||||||
|
chan.on('StasisStart', async (event, channel) => {
|
||||||
|
const playback = await channel.play({
|
||||||
|
media: `sound:/tmp/${fileExtension}`,
|
||||||
|
}, undefined);
|
||||||
|
playback.on('PlaybackFinished', () => {
|
||||||
|
channel.hangup();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const recipient = this.mainGuild.members.get(recipientEntry.individualAssignID);
|
||||||
|
const sender = this.mainGuild.members.get(senderEntry.individualAssignID);
|
||||||
|
|
||||||
|
if (!recipient || !sender) {
|
||||||
|
this.logPage({ number: senderNumber, user: 'N/A' }, { number: recipientNumber, user: 'N/A' }, 'phone', code);
|
||||||
|
} else {
|
||||||
|
this.logPage({ number: senderNumber, user: `${sender.username}#${sender.discriminator}` }, { number: recipientNumber, user: `${recipient.username}#${recipient.discriminator}` }, 'phone', code);
|
||||||
|
}
|
||||||
|
}
|
||||||
this.client.db.Stat.updateOne({ name: 'pages' }, { $inc: { value: 1 } }).exec();
|
this.client.db.Stat.updateOne({ name: 'pages' }, { $inc: { value: 1 } }).exec();
|
||||||
return { status: true, message: `Page to \`${recipientNumber}\` sent.` };
|
return { status: true, message: `Page to \`${recipientNumber}\` sent.` };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -7,6 +7,7 @@ export interface PagerNumberRaw {
|
||||||
emailAddresses: string[],
|
emailAddresses: string[],
|
||||||
discordIDs: string[],
|
discordIDs: string[],
|
||||||
receiveEmail: boolean,
|
receiveEmail: boolean,
|
||||||
|
receivePhone: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PagerNumberInterface extends Document {
|
export interface PagerNumberInterface extends Document {
|
||||||
|
@ -16,6 +17,7 @@ export interface PagerNumberInterface extends Document {
|
||||||
emailAddresses: string[],
|
emailAddresses: string[],
|
||||||
discordIDs: string[],
|
discordIDs: string[],
|
||||||
receiveEmail: boolean,
|
receiveEmail: boolean,
|
||||||
|
receivePhone: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
const PagerNumber: Schema = new Schema({
|
const PagerNumber: Schema = new Schema({
|
||||||
|
@ -24,6 +26,7 @@ const PagerNumber: Schema = new Schema({
|
||||||
emailAddresses: Array,
|
emailAddresses: Array,
|
||||||
discordIDs: Array,
|
discordIDs: Array,
|
||||||
receiveEmail: Boolean,
|
receiveEmail: Boolean,
|
||||||
|
receivePhone: Boolean,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default model<PagerNumberInterface>('PagerNumber', PagerNumber);
|
export default model<PagerNumberInterface>('PagerNumber', PagerNumber);
|
||||||
|
|
Loading…
Reference in New Issue