From ee9a10800d45782da2d37a8a402ca8e9f0bbe386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A2=AB=E9=81=97=E5=BF=98=E7=9A=84=E8=AE=B0=E5=BF=86?= <32097237+YIWANG-sketch@users.noreply.github.com> Date: Fri, 14 Mar 2025 09:46:59 +0800 Subject: [PATCH] Add files via upload --- happyapi_providers/happyApi.mjs | 524 ++++++++++++++++---------------- 1 file changed, 262 insertions(+), 262 deletions(-) diff --git a/happyapi_providers/happyApi.mjs b/happyapi_providers/happyApi.mjs index 02a71da..b27493c 100644 --- a/happyapi_providers/happyApi.mjs +++ b/happyapi_providers/happyApi.mjs @@ -1,262 +1,262 @@ -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.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); - - 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; +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;