Bridge worker updates
This commit is contained in:
parent
a445762a37
commit
f93c4ad317
33 changed files with 17584 additions and 161 deletions
101
apps/bridge-worker/tasks/voice/twilio-recording.ts
Normal file
101
apps/bridge-worker/tasks/voice/twilio-recording.ts
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import Wreck from "@hapi/wreck";
|
||||
import { withDb, AppDatabase } from "../../lib/db";
|
||||
import { twilioClientFor } from "../../lib/common";
|
||||
import { CallInstance } from "twilio/lib/rest/api/v2010/account/call";
|
||||
import workerUtils from "../../lib/utils";
|
||||
|
||||
interface WebhookPayload {
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
to: string;
|
||||
from: string;
|
||||
duration: string;
|
||||
callSid: string;
|
||||
recording: string;
|
||||
mimeType: string;
|
||||
}
|
||||
|
||||
const getTwilioRecording = async (url: string) => {
|
||||
try {
|
||||
const { payload } = await Wreck.get(url);
|
||||
return { recording: payload as Buffer };
|
||||
} catch (error: any) {
|
||||
console.error(error.output);
|
||||
return { error: error.output };
|
||||
}
|
||||
};
|
||||
|
||||
const formatPayload = (
|
||||
call: CallInstance,
|
||||
recording: Buffer,
|
||||
): WebhookPayload => {
|
||||
return {
|
||||
startTime: call.startTime.toISOString(),
|
||||
endTime: call.endTime.toISOString(),
|
||||
to: call.toFormatted,
|
||||
from: call.fromFormatted,
|
||||
duration: call.duration,
|
||||
callSid: call.sid,
|
||||
recording: recording.toString("base64"),
|
||||
mimeType: "audio/mpeg",
|
||||
};
|
||||
};
|
||||
|
||||
const notifyWebhooks = async (
|
||||
db: AppDatabase,
|
||||
voiceLineId: string,
|
||||
call: CallInstance,
|
||||
recording: Buffer,
|
||||
) => {
|
||||
const webhooks = await db.webhooks.findAllByBackendId("voice", voiceLineId);
|
||||
if (webhooks && webhooks.length === 0) return;
|
||||
|
||||
webhooks.forEach(({ id }) => {
|
||||
const payload = formatPayload(call, recording);
|
||||
workerUtils.addJob(
|
||||
"notify-webhook",
|
||||
{
|
||||
payload,
|
||||
webhookId: id,
|
||||
},
|
||||
{
|
||||
// this de-depuplicates the job
|
||||
jobKey: `webhook-${id}-call-${call.sid}`,
|
||||
},
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
interface TwilioRecordingTaskOptions {
|
||||
accountSid: string;
|
||||
callSid: string;
|
||||
recordingSid: string;
|
||||
voiceLineId: string;
|
||||
}
|
||||
|
||||
const twilioRecordingTask = async (
|
||||
options: TwilioRecordingTaskOptions,
|
||||
): Promise<void> =>
|
||||
withDb(async (db: AppDatabase) => {
|
||||
const { voiceLineId, accountSid, callSid, recordingSid } = options;
|
||||
|
||||
const voiceLine = await db.voiceLines.findById({ id: voiceLineId });
|
||||
if (!voiceLine) return;
|
||||
|
||||
const provider = await db.voiceProviders.findByTwilioAccountSid(accountSid);
|
||||
if (!provider) return;
|
||||
|
||||
const client = twilioClientFor(provider);
|
||||
const meta = await client.recordings(recordingSid).fetch();
|
||||
|
||||
const mp3Url = "https://api.twilio.com/" + meta.uri.slice(0, -4) + "mp3";
|
||||
const { recording, error } = await getTwilioRecording(mp3Url);
|
||||
if (error) {
|
||||
throw new Error(`failed to get recording for call ${callSid}`);
|
||||
}
|
||||
|
||||
const call = await client.calls(callSid).fetch();
|
||||
await notifyWebhooks(db, voiceLineId, call, recording!);
|
||||
});
|
||||
|
||||
export default twilioRecordingTask;
|
||||
48
apps/bridge-worker/tasks/voice/voice-line-audio-update.ts
Normal file
48
apps/bridge-worker/tasks/voice/voice-line-audio-update.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import { createHash } from "crypto";
|
||||
import { withDb, AppDatabase } from "../../lib/db";
|
||||
import { convert } from "../../lib/media-convert";
|
||||
|
||||
interface VoiceLineAudioUpdateTaskOptions {
|
||||
voiceLineId: string;
|
||||
}
|
||||
|
||||
const sha1sum = (v: any) => {
|
||||
const shasum = createHash("sha1");
|
||||
shasum.update(v);
|
||||
return shasum.digest("hex");
|
||||
};
|
||||
|
||||
const voiceLineAudioUpdateTask = async (
|
||||
payload: VoiceLineAudioUpdateTaskOptions,
|
||||
): Promise<void> =>
|
||||
withDb(async (db: AppDatabase) => {
|
||||
const { voiceLineId } = payload;
|
||||
const voiceLine = await db.voiceLines.findById({ id: voiceLineId });
|
||||
if (!voiceLine) return;
|
||||
if (!voiceLine?.promptAudio?.["audio/webm"]) return;
|
||||
|
||||
const webm = Buffer.from(voiceLine.promptAudio["audio/webm"], "base64");
|
||||
const webmSha1 = sha1sum(webm);
|
||||
|
||||
if (
|
||||
voiceLine.promptAudio.checksum &&
|
||||
voiceLine.promptAudio.checksum === webmSha1
|
||||
) {
|
||||
// already converted
|
||||
return;
|
||||
}
|
||||
|
||||
const mp3 = await convert(webm);
|
||||
await db.voiceLines.updateById(
|
||||
{ id: voiceLine.id },
|
||||
{
|
||||
promptAudio: {
|
||||
...voiceLine.promptAudio,
|
||||
"audio/mpeg": mp3.toString("base64"),
|
||||
checksum: webmSha1,
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
export default voiceLineAudioUpdateTask;
|
||||
43
apps/bridge-worker/tasks/voice/voice-line-delete.ts
Normal file
43
apps/bridge-worker/tasks/voice/voice-line-delete.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import Twilio from "twilio";
|
||||
// import config from "@digiresilience/bridge-config";
|
||||
import { withDb, AppDatabase } from "../../lib/db";
|
||||
|
||||
const config: any = {};
|
||||
|
||||
interface VoiceLineDeleteTaskOptions {
|
||||
voiceLineId: string;
|
||||
providerId: string;
|
||||
providerLineSid: string;
|
||||
}
|
||||
|
||||
const voiceLineDeleteTask = async (
|
||||
payload: VoiceLineDeleteTaskOptions,
|
||||
): Promise<void> =>
|
||||
withDb(async (db: AppDatabase) => {
|
||||
const { voiceLineId, providerId, providerLineSid } = payload;
|
||||
const provider = await db.voiceProviders.findById({ id: providerId });
|
||||
if (!provider) return;
|
||||
|
||||
const { accountSid, apiKeySid, apiKeySecret } = provider.credentials;
|
||||
if (!accountSid || !apiKeySid || !apiKeySecret)
|
||||
throw new Error(
|
||||
`twilio provider ${provider.name} does not have credentials`,
|
||||
);
|
||||
|
||||
const client = Twilio(apiKeySid, apiKeySecret, {
|
||||
accountSid,
|
||||
});
|
||||
|
||||
const number = await client.incomingPhoneNumbers(providerLineSid).fetch();
|
||||
if (
|
||||
number &&
|
||||
number.voiceUrl ===
|
||||
`${config.frontend.url}/api/v1/voice/twilio/record/${voiceLineId}`
|
||||
)
|
||||
await client.incomingPhoneNumbers(providerLineSid).update({
|
||||
voiceUrl: "",
|
||||
voiceMethod: "POST",
|
||||
});
|
||||
});
|
||||
|
||||
export default voiceLineDeleteTask;
|
||||
40
apps/bridge-worker/tasks/voice/voice-line-provider-update.ts
Normal file
40
apps/bridge-worker/tasks/voice/voice-line-provider-update.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import Twilio from "twilio";
|
||||
// import config from "@digiresilience/bridge-config";
|
||||
import { withDb, AppDatabase } from "../../lib/db";
|
||||
|
||||
const config: any = {};
|
||||
|
||||
interface VoiceLineUpdateTaskOptions {
|
||||
voiceLineId: string;
|
||||
}
|
||||
|
||||
const voiceLineUpdateTask = async (
|
||||
payload: VoiceLineUpdateTaskOptions,
|
||||
): Promise<void> =>
|
||||
withDb(async (db: AppDatabase) => {
|
||||
const { voiceLineId } = payload;
|
||||
const voiceLine = await db.voiceLines.findById({ id: voiceLineId });
|
||||
if (!voiceLine) return;
|
||||
|
||||
const provider = await db.voiceProviders.findById({
|
||||
id: voiceLine.providerId,
|
||||
});
|
||||
if (!provider) return;
|
||||
|
||||
const { accountSid, apiKeySid, apiKeySecret } = provider.credentials;
|
||||
if (!accountSid || !apiKeySid || !apiKeySecret)
|
||||
throw new Error(
|
||||
`twilio provider ${provider.name} does not have credentials`,
|
||||
);
|
||||
|
||||
const client = Twilio(apiKeySid, apiKeySecret, {
|
||||
accountSid,
|
||||
});
|
||||
|
||||
await client.incomingPhoneNumbers(voiceLine.providerLineSid).update({
|
||||
voiceUrl: `${config.frontend.url}/api/v1/voice/twilio/record/${voiceLineId}`,
|
||||
voiceMethod: "POST",
|
||||
});
|
||||
});
|
||||
|
||||
export default voiceLineUpdateTask;
|
||||
Loading…
Add table
Add a link
Reference in a new issue