Add files via upload

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