chore: CRM-NEW baseline

This commit is contained in:
wangliang
2026-10-23 10:35:28 +08:00
commit aa499f6dbf
111 changed files with 15126 additions and 0 deletions
+120
View File
@@ -0,0 +1,120 @@
#!/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); });