Add files via upload
This commit is contained in:
+262
-262
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user