voicemail: index messages, live event, email with the recording

services/voicemail.ts rescans a mailbox on Asterisk's MessageWaiting event
(and every mailbox on AMI connect) instead of watching files, upserts
voicemail_messages by msg_id, marks vanished ones deleted, sends
voicemail.new to the owner only, and emails the wav. Delete-after-email
uses AMI VoicemailRemove, and only when the audio was attached. Asterisk
24 has VoicemailMove/VoicemailRemove, so 04 §5.6 drops the renumbering
plan. New VOICEMAIL_DIR setting; e2e m4-t2 checks the email arrives
within 10 s with a playable attachment (SMTP sink shared with m3-t4).

Task: M4-T2
This commit is contained in:
iamdoubz committed 2026-10-17 22:33:52 -05:00
1 parent 0d29131d18
commit 8d752ba705
16 files changed
+642 -58

No files matched your search

+2 -31
View File
@@ -7,10 +7,10 @@
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
import { createServer } from 'node:net';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { launch } from './cdp.mjs';
import { smtpSink } from './smtp-sink.mjs';
const NET = 'sp-live';
const AST = 'sp-live-ast';
@@ -23,36 +23,7 @@ const docker = (...a) => {
const cli = (cmd) => docker('exec', AST, 'asterisk', '-rx', cmd).out;
const [base, email, pw, shotsDir] = process.argv.slice(2);
/** Just enough SMTP for nodemailer: no auth, no TLS, keeps every message. */
const mails = [];
const smtp = createServer((sock) => {
let data = false;
let buf = '';
let msg = '';
sock.write('220 sink ESMTP\r\n');
sock.on('data', (d) => {
buf += d;
let i;
while ((i = buf.indexOf('\r\n')) >= 0) {
const line = buf.slice(0, i);
buf = buf.slice(i + 2);
if (data) {
if (line === '.') {
data = false;
mails.push(msg);
msg = '';
sock.write('250 ok\r\n');
} else msg += line + '\n';
} else if (/^EHLO/i.test(line)) sock.write('250-sink\r\n250 OK\r\n');
else if (/^DATA/i.test(line)) {
data = true;
sock.write('354 go\r\n');
} else if (/^QUIT/i.test(line)) sock.end('221 bye\r\n');
else sock.write('250 ok\r\n');
}
});
});
await new Promise((r) => smtp.listen(2525, '127.0.0.1', r));
const { mails, server: smtp } = await smtpSink();
const login = await fetch(`${base}/api/auth/login`, {
method: 'POST',