import {EventEmitter} from "events"; import {connect} from "puppeteer-real-browser"; import {v4 as uuidV4} from "uuid"; import path from "path"; import {fileURLToPath} from "url"; import {createDirectoryIfNotExists, sleep} from "../utils/cookieUtils.mjs"; import '../proxyAgent.mjs'; import NetworkMonitor from "../networkMonitor.mjs"; import {detectBrowser} from "../utils/browserDetector.mjs"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); class HappyApiProvider { constructor(config) { this.config = config; this.sessions = {}; this.preferredBrowser = 'auto'; this.networkMonitor = new NetworkMonitor(); } async init() { console.log(`本项目依赖 Chrome 或 Edge 浏览器,请勿关闭弹出的浏览器窗口。如果出现错误请检查是否已安装 Chrome 或 Edge 浏览器。`); const browserPath = detectBrowser(this.preferredBrowser); this.sessions = {}; // 手动登录 const currentUsername = 'manual_login'; createDirectoryIfNotExists(path.join(__dirname, "browser_profiles", currentUsername)); try { const response = await connect({ headless: "auto", turnstile: true, customConfig: { userDataDir: path.join(__dirname, "browser_profiles", currentUsername), executablePath: browserPath, }, }); const {page, browser} = response; console.log(`请在打开的浏览器窗口中手动登录 happyapi.org`); await page.goto("https://happyapi.org", {waitUntil: 'networkidle0'}); await sleep(3000); // 等待页面加载完毕 const sessionCookie = await this.waitForManualLogin(page); if (sessionCookie) { this.sessions[currentUsername] = {browser, page, sessionCookie, valid: true}; console.log(`成功获取会话 cookie`); } else { console.error(`未能检测到登录状态,请确保已成功登录`); await browser.close(); } } catch (error) { console.error(`初始化浏览器失败`); console.error(error); } // 开始网络监控 await this.networkMonitor.startMonitoring(); } async waitForManualLogin(page) { return new Promise((resolve) => { const checkLoginStatus = async () => { // 检查是否存在特定的登录成功标识元素 const isLoggedIn = await this.checkLoginStatus(page); if (isLoggedIn) { console.log(`检测到登录成功`); const cookies = await page.cookies(); const sessionCookie = this.extractSessionCookie(cookies); resolve(sessionCookie); } else { setTimeout(checkLoginStatus, 1000); } }; page.on('request', (request) => { if (request.url() === 'https://happyapi.org/api/v1/auths/signin' && request.method() === 'POST') { console.log('检测到登录请求'); checkLoginStatus(); } }); checkLoginStatus(); }); } extractSessionCookie(cookies) { const cfClearance = cookies.find(c => c.name === 'cf_clearance')?.value; const token = cookies.find(c => c.name === 'token')?.value; if (cfClearance && token) { return [ { name: 'cf_clearance', value: cfClearance, domain: 'happyapi.org', path: '/', httpOnly: true, secure: true, sameSite: 'Lax', }, { name: 'token', value: token, domain: 'happyapi.org', path: '/', httpOnly: true, secure: true, sameSite: 'Lax', } ]; } else { console.error('无法提取有效的会话 cookie'); return null; } } async checkLoginStatus(page) { try { const userNameElement = await page.waitForSelector('div.self-center.font-medium', {timeout: 5000}); const userName = await page.evaluate(element => element.textContent, userNameElement); return true; } catch { return false; } } async getCompletion({username, messages, stream = false, proxyModel}) { const session = this.sessions[username]; if (!session || !session.valid) { throw new Error(`用户 ${username} 的会话无效`); } //刷新页面 await session.page.goto("https://happyapi.org", {waitUntil: 'domcontentloaded'}); const {page} = session; const emitter = new EventEmitter(); try { let userQuery = messages.map(msg => msg.content).join("\n\n"); const inputSelector = 'textarea#chat-textarea'; const sendButtonSelector = 'button#send-message-button'; // 输入消息 await page.waitForSelector(inputSelector, {visible: true}); await page.evaluate((selector, message) => { const textarea = document.querySelector(selector); textarea.value = message; // 手动触发事件 textarea.dispatchEvent(new Event('input', {bubbles: true})); textarea.dispatchEvent(new Event('change', {bubbles: true})); }, inputSelector, userQuery.trim()); await page.waitForTimeout(100); // 点击发送按钮 await page.waitForSelector(sendButtonSelector, {visible: true}); await page.click(sendButtonSelector); // 监听请求 const targetUrl = 'https://happyapi.org/api/chat/completions'; const traceId = uuidV4(); let finalResponse = ""; // 存储最终响应 let responseStarted = false; // 是否开始接收 let isEnding = false; // 是否正在结束 // 清理函数 const cleanup = async () => { clearTimeout(responseTimeout); page.removeListener('response', responseHandler); }; const responseTimeout = setTimeout(() => { if (!isEnding) { isEnding = true; emitter.emit('error', new Error('请求超时')); cleanup(); } }, 60000); // 60秒,可根据需要调整 const responseHandler = async (response) => { if (response.url() === targetUrl) { try { const buffer = await response.buffer(); const text = buffer.toString('utf8'); // 处理流式数据 const lines = text.split('\n\n'); for (const line of lines) { if (line.startsWith('data: ')) { const data = line.substring(6).trim(); if (data === '[DONE]') { if (!isEnding) { isEnding = true; console.log("请求结束"); emitter.emit('end'); cleanup(); } return; } if (data.length === 0) { continue; } const parsedData = JSON.parse(data); const contentDelta = parsedData.choices[0].delta.content || ''; if (stream) { emitter.emit('completion', traceId, contentDelta); } else { finalResponse += contentDelta; } } } } catch (e) { if (!isEnding) { isEnding = true; console.error("请求发生错误", e); emitter.emit('error', e); cleanup(); } } } }; // 监听response page.on('response', responseHandler); // 清除计时器和监听器 emitter.on('end', () => { cleanup(); }); emitter.on('error', () => { cleanup(); }); return { completion: emitter, cancel: () => { } }; } catch (error) { emitter.emit('error', error); return { completion: emitter, cancel: () => { } }; } } } export default HappyApiProvider;