chore: CRM-NEW baseline
This commit is contained in:
@@ -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); });
|
||||
Reference in New Issue
Block a user