Files
crm-ai/scripts/e2e-anon-otp.js
T
2026-10-23 10:35:28 +08:00

121 lines
5.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
'use strict';
// 匿名 Live chat:AI 问邮箱 -> 发 OTP -> 读 163 邮箱取码 -> 回码 -> 验证 -> 查订单
const { execFileSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '..');
const env = {};
for (const line of fs.readFileSync(path.join(ROOT, '.env'), 'utf8').split('\n')) {
if (!line.includes('=') || line.trim().startsWith('#')) continue;
const i = line.indexOf('=');
env[line.slice(0, i).trim()] = line.slice(i + 1).trim();
}
const BASE = (env.LIBREDESK_BASE_URL || '').replace(/\/+$/, '');
const INBOX = env.LIBREDESK_INBOX_UUID;
const EMAIL = process.env.OTP_EMAIL || env.SMTP_USER;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function adminCookie() {
const r = await fetch(BASE + '/api/v1/auth/login', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'System', password: env.LIBREDESK_SYSTEM_USER_PASSWORD }),
});
return r.headers.getSetCookie().map((c) => c.split(';')[0]).join('; ');
}
async function adminMessages(cookie, uuid) {
const r = await fetch(`${BASE}/api/v1/conversations/${uuid}/messages`, { headers: { Cookie: cookie } });
if (!r.ok) return [];
const body = await r.json();
const msgs = (body.data && (body.data.results || body.data.messages)) || [];
return msgs.slice().sort((a, b) => Number(a.id) - Number(b.id));
}
async function waitReply(cookie, uuid, afterId, timeoutMs = 90000) {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
await sleep(3000);
const msgs = await adminMessages(cookie, uuid);
const replies = msgs.filter((m) => m.type === 'outgoing' && m.sender_type === 'agent' && !m.private && Number(m.id) > afterId);
if (replies.length) return replies[replies.length - 1];
}
return null;
}
async function sendWidget(uuid, token, message) {
const r = await fetch(`${BASE}/api/v1/widget/chat/conversations/${uuid}/message`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, 'X-Libredesk-Inbox-ID': INBOX },
body: JSON.stringify({ message }),
});
if (!r.ok) throw new Error(`widget send failed: ${r.status} ${(await r.text()).slice(0, 200)}`);
}
function readOtpaCode() {
const py = String.raw`
import json, imaplib, email, re
from email.header import decode_header
imaplib.Commands['ID']=('AUTH',)
cfg=json.load(open('/home/dev/code/crm/crm-ai-demo/bridge/mail.config.json'))
imap=cfg['imap']; auth=imap['auth']
M=imaplib.IMAP4_SSL(imap['host'], imap['port']); M.login(auth['user'],auth['pass'])
M._simple_command('ID','("name" "pycrm" "version" "1.0" "vendor" "python")'); M.select('INBOX')
typ,data=M.search(None,'ALL'); ids=data[0].split()[-10:]
for i in reversed(ids):
typ,md=M.fetch(i,'(RFC822)'); msg=email.message_from_bytes(md[0][1])
subj=str(email.header.make_header(decode_header(msg['Subject'] or '')))
if 'verification code' in subj.lower():
body=''
if msg.is_multipart():
for part in msg.walk():
if part.get_content_type()=='text/plain': body=part.get_payload(decode=True).decode('utf-8','ignore'); break
else: body=msg.get_payload(decode=True).decode('utf-8','ignore')
m=re.search(r'\b(\d{6})\b', body)
if m: print(m.group(1)); break
`;
return execFileSync('python3', ['-c', py], { encoding: 'utf8' }).trim();
}
async function main() {
const cookie = await adminCookie();
const init = await fetch(`${BASE}/api/v1/widget/chat/conversations/init`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Libredesk-Inbox-ID': INBOX },
body: JSON.stringify({ message: 'I want to check my order status' }),
});
const j = await init.json();
if (!init.ok) throw new Error(`init failed: ${JSON.stringify(j).slice(0, 200)}`);
const uuid = j.data.conversation.uuid;
const token = j.data.session_token;
console.log('1) conversation', uuid);
let msgs = await adminMessages(cookie, uuid);
let lastId = msgs.length ? Number(msgs[msgs.length - 1].id) : 0;
const ask = await waitReply(cookie, uuid, lastId);
console.log('2) AI:', ask && ask.text_content);
await sendWidget(uuid, token, EMAIL);
console.log('3) sent email', EMAIL);
msgs = await adminMessages(cookie, uuid);
lastId = Number(msgs[msgs.length - 1].id);
const sent = await waitReply(cookie, uuid, lastId);
console.log('4) AI:', sent && sent.text_content);
const code = readOtpaCode();
if (!code) throw new Error('OTP code not found in mailbox');
console.log('5) code from mailbox', code);
await sendWidget(uuid, token, code);
msgs = await adminMessages(cookie, uuid);
lastId = Number(msgs[msgs.length - 1].id);
const final = await waitReply(cookie, uuid, lastId, 120000);
console.log('6) AI:', final && final.text_content);
if (!final) throw new Error('no final reply');
console.log('\nCONVERSATION', uuid);
}
main().catch((err) => { console.error('[e2e-anon-otp]', err.message); process.exit(1); });