add intercom capabilities and tidy up PBX stuff
parent
a47355c13c
commit
92c29ed50a
|
@ -0,0 +1,28 @@
|
||||||
|
import ARI from 'ari-client';
|
||||||
|
import { PBX } from '.';
|
||||||
|
|
||||||
|
export default class Handler {
|
||||||
|
public pbx: PBX;
|
||||||
|
|
||||||
|
public app: string;
|
||||||
|
|
||||||
|
public options: {
|
||||||
|
available?: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(pbx: PBX) {
|
||||||
|
this.pbx = pbx;
|
||||||
|
this.options = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
get client() { return this.pbx.client; }
|
||||||
|
|
||||||
|
public run(event: ARI.Event, channel: ARI.Channel): Promise<any> { return Promise.resolve(); }
|
||||||
|
|
||||||
|
public async unavailable(event: ARI.Event, channel: ARI.Channel) {
|
||||||
|
const playback = await channel.play({
|
||||||
|
media: 'sound:all-outgoing-lines-unavailable',
|
||||||
|
}, undefined);
|
||||||
|
playback.once('PlaybackFinished', () => channel.hangup());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,79 +1,42 @@
|
||||||
/* eslint-disable consistent-return */
|
/* eslint-disable no-continue */
|
||||||
import { TextableChannel } from 'eris';
|
import { Client, Collection, Handler } from '.';
|
||||||
import { Client } from '.';
|
|
||||||
import PageCommand from '../commands/page';
|
|
||||||
|
|
||||||
export default class PBX {
|
export default class PBX {
|
||||||
public client: Client;
|
public client: Client;
|
||||||
|
|
||||||
|
public handlers: Collection<Handler>;
|
||||||
|
|
||||||
constructor(client: Client) {
|
constructor(client: Client) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
|
||||||
this.pageDTMF();
|
this.handlers = new Collection<Handler>();
|
||||||
|
|
||||||
|
this.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public pageDTMF() {
|
public start() {
|
||||||
|
this.client.util.ari.on('ChannelHangupRequest', (_, channel) => channel.hangup());
|
||||||
|
const handlers = Object.values<typeof Handler>(require(`${__dirname}/../pbx`));
|
||||||
|
for (const HandlerFile of handlers) {
|
||||||
|
const handler = new HandlerFile(this);
|
||||||
|
if (!handler.app) continue;
|
||||||
|
if (!handler.options?.available) {
|
||||||
this.client.util.ari.on('StasisStart', async (event, channel) => {
|
this.client.util.ari.on('StasisStart', async (event, channel) => {
|
||||||
if (event.application !== 'page-dtmf') return;
|
if (event.application !== handler.app) return;
|
||||||
const message = await (<TextableChannel> this.client.guilds.get(this.client.config.guildID).channels.get('501089664040697858')).getMessage('775604192013320203');
|
await handler.unavailable(event, channel);
|
||||||
if (!message) return channel.hangup();
|
});
|
||||||
const member = await this.client.db.Staff.findOne({ extension: channel.caller.number }).lean().exec();
|
} else {
|
||||||
if (!member) return channel.hangup();
|
this.client.util.ari.on('StasisStart', async (event, channel) => {
|
||||||
const pager = await this.client.db.PagerNumber.findOne({ individualAssignID: member.userID }).lean().exec();
|
if (event.application !== handler.app) return;
|
||||||
if (!pager) return channel.hangup();
|
|
||||||
let status = 0;
|
|
||||||
const pagerNumber: string[] = [];
|
|
||||||
const pagerCode: string[] = [];
|
|
||||||
channel.answer();
|
|
||||||
const pnPlayback = await channel.play({
|
|
||||||
media: 'sound:please-enter-the-pn',
|
|
||||||
}, undefined);
|
|
||||||
channel.on('ChannelDtmfReceived', async (ev) => {
|
|
||||||
if (status === 0) {
|
|
||||||
if (ev.digit === '#') {
|
|
||||||
pnPlayback.stop();
|
|
||||||
await channel.play({
|
|
||||||
media: 'sound:please-enter-the-pc',
|
|
||||||
}, undefined);
|
|
||||||
status = 1;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
pagerNumber.push(ev.digit);
|
|
||||||
} else if (status === 1) {
|
|
||||||
const processingPlayback = this.client.util.ari.Playback();
|
|
||||||
if (ev.digit === '#') {
|
|
||||||
await channel.play({
|
|
||||||
media: 'sound:pls-hold-process-tx',
|
|
||||||
}, processingPlayback);
|
|
||||||
|
|
||||||
const Page = <PageCommand> this.client.commands.get('page');
|
|
||||||
const page = await Page.page(pagerNumber.join(''), pager.num, pagerCode.join(''), message);
|
|
||||||
if (page.status === true) {
|
|
||||||
processingPlayback.stop();
|
|
||||||
const playback = await channel.play({
|
|
||||||
media: 'sound:page-delivered',
|
|
||||||
}, undefined);
|
|
||||||
playback.on('PlaybackFinished', () => channel.hangup());
|
|
||||||
} else if (page.status === false) {
|
|
||||||
try {
|
try {
|
||||||
const ch = await this.client.getDMChannel(member.userID);
|
await handler.run(event, channel);
|
||||||
if (ch) {
|
} catch (err) {
|
||||||
ch.createMessage(`***An error has occurred while trying to process your page request over the PBX.***\n*${page.message}*\n\nPlease check your parameters and try again.`);
|
this.client.util.handleError(err);
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
this.client.util.handleError(new Error(page.message));
|
|
||||||
}
|
|
||||||
processingPlayback.stop();
|
|
||||||
const playback = await channel.play({
|
|
||||||
media: 'sound:request-error',
|
|
||||||
}, undefined);
|
|
||||||
playback.on('PlaybackFinished', () => channel.hangup());
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
pagerCode.push(ev.digit);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
this.handlers.add(handler.app, handler);
|
||||||
|
this.client.util.signale.success(`Successfully loaded PBX Handler ${handler.app}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ export { default as Client } from './Client';
|
||||||
export { default as Collection } from './Collection';
|
export { default as Collection } from './Collection';
|
||||||
export { default as Command } from './Command';
|
export { default as Command } from './Command';
|
||||||
export { default as Event } from './Event';
|
export { default as Event } from './Event';
|
||||||
|
export { default as Handler } from './Handler';
|
||||||
export { default as LocalStorage } from './LocalStorage';
|
export { default as LocalStorage } from './LocalStorage';
|
||||||
export { default as Moderation } from './Moderation';
|
export { default as Moderation } from './Moderation';
|
||||||
export { default as PBX } from './PBX';
|
export { default as PBX } from './PBX';
|
||||||
|
|
|
@ -18,6 +18,7 @@ export { default as eval } from './eval';
|
||||||
export { default as game } from './game';
|
export { default as game } from './game';
|
||||||
export { default as help } from './help';
|
export { default as help } from './help';
|
||||||
export { default as info } from './info';
|
export { default as info } from './info';
|
||||||
|
export { default as intercom } from './intercom';
|
||||||
export { default as kick } from './kick';
|
export { default as kick } from './kick';
|
||||||
export { default as listredirects } from './listredirects';
|
export { default as listredirects } from './listredirects';
|
||||||
export { default as members } from './members';
|
export { default as members } from './members';
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { Message } from 'eris';
|
||||||
|
import { Client, Command } from '../class';
|
||||||
|
import { Misc as MiscPBXActions } from '../pbx';
|
||||||
|
|
||||||
|
export default class Intercom extends Command {
|
||||||
|
constructor(client: Client) {
|
||||||
|
super(client);
|
||||||
|
this.name = 'intercom';
|
||||||
|
this.description = 'Will synthesize inputted text to a recording and dial an intercom to the extension specified, then play the recording.';
|
||||||
|
this.usage = `${this.client.config.prefix}intercom <extension> <text>`;
|
||||||
|
this.permissions = 1;
|
||||||
|
this.guildOnly = true;
|
||||||
|
this.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async run(message: Message, args: string[]) {
|
||||||
|
try {
|
||||||
|
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
|
||||||
|
const loading = await this.loading(message.channel, 'Synthesizing text...');
|
||||||
|
|
||||||
|
const recordingLocation = await MiscPBXActions.TTS(this.client.util.pbx, `Hello, this is the Library of Code Private Branch Exchange dialing you at the request of ${message.author.username} to deliver you a message. Playing message: ${args.slice(1).join(' ')}`, 'MALE');
|
||||||
|
await loading.edit(`***${this.client.util.emojis.LOADING} Preparing to dial...***`);
|
||||||
|
const channel = await this.client.util.ari.channels.originate({
|
||||||
|
endpoint: `PJSIP/${args[0]}`,
|
||||||
|
extension: args[0],
|
||||||
|
callerId: `TTS PAGE FRM ${message.author.username} <00>`,
|
||||||
|
priority: 1,
|
||||||
|
app: 'cr-zero',
|
||||||
|
variables: {
|
||||||
|
'PJSIP_HEADER(add,Call-Info)': '<uri>;answer-after=0',
|
||||||
|
'PJSIP_HEADER(add,Alert-Info)': 'Ring Answer',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await loading.edit(`***${this.client.util.emojis.LOADING} Dialing call...***`);
|
||||||
|
channel.once('StasisStart', async (_, chan) => {
|
||||||
|
chan.answer();
|
||||||
|
await loading.edit(`***${this.client.util.emojis.LOADING} Answer received, starting playback...***`);
|
||||||
|
const playback = await chan.play({
|
||||||
|
media: ['sound:beep', recordingLocation],
|
||||||
|
}, undefined);
|
||||||
|
playback.once('PlaybackFinished', async () => {
|
||||||
|
chan.hangup();
|
||||||
|
await loading.edit(`***${this.client.util.emojis.LOADING} Successfully delivered intercom message to EXT \`${args[0]}\`.***`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return undefined;
|
||||||
|
} catch (err) {
|
||||||
|
return this.client.util.handleError(err, message, this, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
import ARI from 'ari-client';
|
||||||
|
import { randomBytes } from 'crypto';
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import { PBX } from '../../class';
|
||||||
|
|
||||||
|
export default class Misc {
|
||||||
|
public static async accessDenied(channel: ARI.Channel) {
|
||||||
|
const playback = await channel.play({
|
||||||
|
media: 'sound:access-denied',
|
||||||
|
}, undefined);
|
||||||
|
playback.once('PlaybackFinished', () => channel.hangup());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async TTS(pbx: PBX, text: string, gender: string | any): Promise<string> {
|
||||||
|
const fileExtension = `${randomBytes(10).toString('hex')}`;
|
||||||
|
|
||||||
|
const [response] = await pbx.client.util.tts.synthesizeSpeech({
|
||||||
|
input: { text },
|
||||||
|
voice: { languageCode: 'en-US', ssmlGender: gender },
|
||||||
|
audioConfig: { audioEncoding: 'OGG_OPUS' },
|
||||||
|
});
|
||||||
|
await fs.writeFile(`/tmp/${fileExtension}.ogg`, response.audioContent, 'binary');
|
||||||
|
await pbx.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`);
|
||||||
|
return `sound:${fileExtension}`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
/* eslint-disable consistent-return */
|
||||||
|
import ARI from 'ari-client';
|
||||||
|
import { Handler, PBX } from '../../class';
|
||||||
|
|
||||||
|
export default class CRZero extends Handler {
|
||||||
|
constructor(pbx: PBX) {
|
||||||
|
super(pbx);
|
||||||
|
this.app = 'cr-zero';
|
||||||
|
this.options = { available: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async run(event: ARI.Event, channel: ARI.Channel) {
|
||||||
|
const playback = await channel.play({
|
||||||
|
media: 'sound:pbx-transfer',
|
||||||
|
}, undefined);
|
||||||
|
playback.once('PlaybackFinished', () => channel.move({ app: 'page-dtmf' }));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
/* eslint-disable consistent-return */
|
||||||
|
import ARI from 'ari-client';
|
||||||
|
import { TextableChannel } from 'eris';
|
||||||
|
import PageCommand from '../../commands/page';
|
||||||
|
import { Handler, PBX } from '../../class';
|
||||||
|
import { Misc } from '..';
|
||||||
|
|
||||||
|
export default class PageDTMF extends Handler {
|
||||||
|
constructor(pbx: PBX) {
|
||||||
|
super(pbx);
|
||||||
|
this.app = 'page-dtmf';
|
||||||
|
this.options.available = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async run(event: ARI.Event, channel: ARI.Channel) {
|
||||||
|
if (event.application !== 'page-dtmf') return;
|
||||||
|
const message = await (<TextableChannel> this.client.guilds.get(this.client.config.guildID).channels.get('501089664040697858')).getMessage('775604192013320203');
|
||||||
|
if (!message) return Misc.accessDenied(channel);
|
||||||
|
const member = await this.client.db.Staff.findOne({ extension: channel.caller.number }).lean().exec();
|
||||||
|
if (!member) return Misc.accessDenied(channel);
|
||||||
|
const pager = await this.client.db.PagerNumber.findOne({ individualAssignID: member.userID }).lean().exec();
|
||||||
|
if (!pager) return Misc.accessDenied(channel);
|
||||||
|
let status = 0;
|
||||||
|
const pagerNumber: string[] = [];
|
||||||
|
const pagerCode: string[] = [];
|
||||||
|
channel.answer();
|
||||||
|
const pnPlayback = await channel.play({
|
||||||
|
media: 'sound:please-enter-the-pn',
|
||||||
|
}, undefined);
|
||||||
|
channel.on('ChannelDtmfReceived', async (ev) => {
|
||||||
|
if (status === 0) {
|
||||||
|
if (ev.digit === '#') {
|
||||||
|
pnPlayback.stop();
|
||||||
|
await channel.play({
|
||||||
|
media: 'sound:please-enter-the-pc',
|
||||||
|
}, undefined);
|
||||||
|
status = 1;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
pagerNumber.push(ev.digit);
|
||||||
|
} else if (status === 1) {
|
||||||
|
const processingPlayback = this.client.util.ari.Playback();
|
||||||
|
if (ev.digit === '#') {
|
||||||
|
await channel.play({
|
||||||
|
media: 'sound:pls-hold-process-tx',
|
||||||
|
}, processingPlayback);
|
||||||
|
|
||||||
|
const Page = <PageCommand> this.client.commands.get('page');
|
||||||
|
const page = await Page.page(pagerNumber.join(''), pager.num, pagerCode.join(''), message);
|
||||||
|
if (page.status === true) {
|
||||||
|
processingPlayback.stop();
|
||||||
|
const playback = await channel.play({
|
||||||
|
media: 'sound:page-delivered',
|
||||||
|
}, undefined);
|
||||||
|
playback.on('PlaybackFinished', () => channel.hangup());
|
||||||
|
} else if (page.status === false) {
|
||||||
|
try {
|
||||||
|
const ch = await this.client.getDMChannel(member.userID);
|
||||||
|
if (ch) {
|
||||||
|
ch.createMessage(`***An error has occurred while trying to process your page request over the PBX.***\n*${page.message}*\n\nPlease check your parameters and try again.`);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
this.client.util.handleError(new Error(page.message));
|
||||||
|
}
|
||||||
|
processingPlayback.stop();
|
||||||
|
const errorPlayback = await channel.play({
|
||||||
|
media: 'sound:request-error',
|
||||||
|
}, undefined);
|
||||||
|
errorPlayback.on('PlaybackFinished', () => channel.hangup());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
pagerCode.push(ev.digit);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
// Actions
|
||||||
|
export { default as Misc } from './actions/Misc';
|
||||||
|
|
||||||
|
// Handlers
|
||||||
|
export { default as CRZero } from './handlers/CRZero';
|
||||||
|
export { default as PageDTMF } from './handlers/PageDTMF';
|
Loading…
Reference in New Issue