/** * WhatsApp Auth Route — QR 码认证 API * * GET /api/whatsapp/status → 获取 WhatsApp 连接状态 * GET /api/whatsapp/auth → SSE: 启动认证流程,推送 QR 码 * POST /api/whatsapp/logout → 登出并清除凭据 */ const express = require('express'); const path = require('path'); const fs = require('fs'); const router = express.Router(); const AUTH_DIR = path.resolve(__dirname, '../../skills/whatsapp-bot/store/auth'); // ─── 状态查询 ─── router.get('/status', (req, res) => { const credsPath = path.join(AUTH_DIR, 'creds.json'); let authenticated = false; if (fs.existsSync(credsPath)) { try { const creds = JSON.parse(fs.readFileSync(credsPath, 'utf-8')); // Baileys v7 rc.9 有时 registered 不会变为 true, // 但如果 auth 目录有大量文件 (session/key files),说明认证已完成 const authFiles = fs.readdirSync(AUTH_DIR).length; authenticated = creds.registered === true || authFiles > 5; } catch { authenticated = false; } } res.json({ authenticated, authDir: AUTH_DIR, }); }); // ─── SSE 认证流程 ─── router.get('/auth', async (req, res) => { // SSE headers res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive', }); const sendSSE = (event, data) => { res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`); }; sendSSE('status', { message: '正在初始化 WhatsApp 连接...' }); let sock = null; let clientClosed = false; let authDone = false; let retryCount = 0; const MAX_RETRIES = 5; // 客户端断开时清理 req.on('close', () => { clientClosed = true; if (sock) { try { sock.end(undefined); } catch { } sock = null; } }); try { const { default: makeWASocket, useMultiFileAuthState, fetchLatestWaWebVersion, makeCacheableSignalKeyStore, Browsers, DisconnectReason, } = await import('@whiskeysockets/baileys'); fs.mkdirSync(AUTH_DIR, { recursive: true }); // 半静默 logger:仅输出 error/warn const silentLogger = { info: () => { }, error: (...a) => console.error('[WhatsApp Auth][baileys]', ...a), warn: (...a) => console.warn('[WhatsApp Auth][baileys]', ...a), debug: () => { }, trace: () => { }, child: () => silentLogger, level: 'error', }; let version; try { const result = await fetchLatestWaWebVersion({}); version = result.version; } catch { sendSSE('status', { message: '无法获取最新版本号,使用默认版本' }); } // 可重连的 socket 创建函数 (处理 code 515 restart required) async function connectSocket() { if (clientClosed) return; const { state, saveCreds } = await useMultiFileAuthState(AUTH_DIR); sock = makeWASocket({ version, auth: { creds: state.creds, keys: makeCacheableSignalKeyStore(state.keys, silentLogger), }, printQRInTerminal: false, browser: Browsers.macOS('Chrome'), logger: silentLogger, }); sock.ev.on('connection.update', (update) => { const { connection, lastDisconnect, qr } = update; console.log('[WhatsApp Auth] connection.update:', JSON.stringify({ connection, qr: !!qr, lastDisconnect: lastDisconnect?.error?.message })); if (qr) { sendSSE('qr', { qrCode: qr }); } if (connection === 'close') { if (authDone) { // 认证已完成后的断开是预期行为 (sock.end) console.log('[WhatsApp Auth] Post-auth disconnect, ignoring.'); return; } const reason = lastDisconnect?.error?.output?.statusCode; console.log('[WhatsApp Auth] Connection closed, reason:', reason); if (reason === DisconnectReason.loggedOut) { sendSSE('error', { message: '认证失败: 已被登出。请重试。' }); if (!clientClosed) res.end(); } else if (reason === DisconnectReason.restartRequired || reason === 515) { // WhatsApp 要求重启流 — QR 扫码后的正常行为 retryCount++; console.log(`[WhatsApp Auth] Restart required, reconnecting... (${retryCount}/${MAX_RETRIES})`); if (retryCount <= MAX_RETRIES && !clientClosed) { sendSSE('status', { message: '正在完成认证...' }); setTimeout(() => connectSocket(), 1000); } else { sendSSE('error', { message: '重连次数过多,请刷新重试' }); if (!clientClosed) res.end(); } } else { sendSSE('status', { message: `连接断开 (${reason}),等待重试...` }); // 对其他断线原因也尝试重连 retryCount++; if (retryCount <= MAX_RETRIES && !clientClosed) { setTimeout(() => connectSocket(), 2000); } else { sendSSE('error', { message: '重连失败,请刷新重试' }); if (!clientClosed) res.end(); } } } else if (connection === 'open') { authDone = true; // 认证完成后静默 logger,避免 sock.end() 后 Baileys 内部错误刷屏 silentLogger.error = () => { }; silentLogger.warn = () => { }; const phoneNumber = sock.user?.id?.split(':')[0]?.split('@')[0]; console.log('[WhatsApp Auth] ✅ Authenticated! Phone:', phoneNumber); sendSSE('authenticated', { message: `认证成功! 手机号: ${phoneNumber || '未知'}`, phone: phoneNumber, }); // 认证成功后等待 creds 完全写入再断开(Bot 单独运行) setTimeout(() => { try { sock.end(undefined); } catch { } sock = null; if (!clientClosed) res.end(); }, 5000); } }); sock.ev.on('creds.update', saveCreds); } await connectSocket(); } catch (err) { console.error('[WhatsApp Auth] Error:', err); sendSSE('error', { message: err.message }); if (!clientClosed) res.end(); } }); // ─── 登出 ─── router.post('/logout', (req, res) => { try { if (fs.existsSync(AUTH_DIR)) { fs.rmSync(AUTH_DIR, { recursive: true, force: true }); } res.json({ success: true, message: 'WhatsApp 凭据已清除' }); } catch (err) { res.status(500).json({ error: err.message }); } }); module.exports = router;