github-actions[bot]
Update from GitHub Actions
2496079
raw
history blame
9.15 kB
import { Hono } from 'hono'
import { serve } from '@hono/node-server'
import { serveStatic } from '@hono/node-server/serve-static'
import { chromium, type Browser, type BrowserContext, type Route, type Page } from 'playwright'
import process from 'process'
import fs from 'fs'
const app = new Hono()
// 浏览器实例
let browser: Browser | null = null
let gensparkContext: BrowserContext | null = null
const userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
const RECAPTCHA_SITE_KEY = "6Leq7KYqAAAAAGdd1NaUBJF9dHTPAKP7DcnaRc66";
const GENSPARK_URL = 'https://www.genspark.ai/agents?type=moa_chat';
// 初始化浏览器
async function initBrowser() {
if (!browser) {
browser = await chromium.launch({
headless: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
],
executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH, // 使用系统 Chromium
})
}
return browser
}
// 初始化genspark页面
async function initGensparkContext() {
const browser = await initBrowser()
if (!gensparkContext) {
gensparkContext = await browser.newContext({
userAgent,
viewport: { width: 1920, height: 1080 },
deviceScaleFactor: 1,
hasTouch: false,
locale: 'en-US',
timezoneId: 'America/New_York',
geolocation: { longitude: -73.935242, latitude: 40.730610 }, // 纽约坐标,可根据需要调整
permissions: ['geolocation'],
javaScriptEnabled: true,
bypassCSP: true, // 绕过内容安全策略
colorScheme: 'light',
acceptDownloads: true,
})
}
return gensparkContext
}
// 验证响应头值是否有效
function isValidHeaderValue(value: string): boolean {
// 检查值是否为空或包含无效字符
if (!value || typeof value !== 'string') return false;
// 检查是否包含换行符或回车符
if (/[\r\n]/.test(value)) return false;
return true;
}
// 获取reCAPTCHA令牌
async function getReCaptchaToken(page: Page): Promise<string> {
return page.evaluate(() => {
return new Promise<string>((resolve, reject) => {
// @ts-ignore
window.grecaptcha.ready(function () {
// @ts-ignore
grecaptcha.execute(
"6Leq7KYqAAAAAGdd1NaUBJF9dHTPAKP7DcnaRc66",
{ action: 'copilot' },
).then(function (token: string) {
resolve(token)
}).catch(function (error: Error) {
reject(error)
});
});
// 设置超时
setTimeout(() => reject(new Error("获取令牌超时")), 10000);
});
});
}
// 处理请求转发
async function handleRequest(url: string, method: string, headers: any, body?: any) {
const browser = await initBrowser()
const page = await browser.newPage()
try {
// 只移除确实需要移除的请求头
const filteredHeaders = { ...headers };
const headersToRemove = [
'host', 'connection', 'content-length', 'accept-encoding',
'cdn-loop', 'cf-connecting-ip', 'cf-connecting-o2o', 'cf-ew-via',
'cf-ray', 'cf-visitor', 'cf-worker', 'x-direct-url',
'x-forwarded-for', 'x-forwarded-port', 'x-forwarded-proto'
];
headersToRemove.forEach(header => delete filteredHeaders[header]);
filteredHeaders['user-agent'] = userAgent;
console.log('处理请求:', method, url, filteredHeaders, body);
// 设置请求拦截器
await page.route('**/*', async (route: Route) => {
const request = route.request();
if (request.url() === url) {
await route.continue({
method: method,
headers: {
...request.headers(),
...filteredHeaders
},
postData: body
});
} else {
// 允许其他资源加载
await route.continue();
}
});
// 配置页面请求选项
const response = await page.goto(url, {
waitUntil: 'domcontentloaded', // 改为更快的加载策略
timeout: 600000 // 60秒超时,更合理的值
});
if (!response) {
throw new Error('未收到响应');
}
// 等待页面加载完成,使用更短的超时时间
await page.waitForLoadState('networkidle', { timeout: 5000 }).catch(() => {
console.log('等待页面加载超时,继续处理');
});
// 获取响应数据
const status = response.status();
const responseHeaders = response.headers();
// 确保移除可能导致解码问题的响应头
delete responseHeaders['content-encoding'];
delete responseHeaders['content-length'];
// 过滤无效的响应头
const validHeaders: Record<string, string> = {};
for (const [key, value] of Object.entries(responseHeaders)) {
if (isValidHeaderValue(value as string)) {
validHeaders[key] = value as string;
} else {
console.warn(`跳过无效的响应头: ${key}: ${value}`);
}
}
// 直接获取响应体的二进制数据
const responseBody = await response.body();
console.log('请求处理完成:', status);
await page.close();
return {
status,
headers: validHeaders,
body: responseBody
};
} catch (error: any) {
await page.close();
console.error('请求处理错误:', error);
throw new Error(`请求失败: ${error.message}`);
}
}
// 解析cookie字符串为对象数组
function parseCookies(cookieString: string) {
return cookieString.split(';')
.map(cookie => {
const [name, value] = cookie.trim().split('=');
return {
name,
value,
domain: 'www.genspark.ai',
path: '/'
};
})
.filter(cookie => cookie.name && cookie.value);
}
// 添加静态文件服务
app.use('/public/*', serveStatic({ root: './' }))
// 处理根路由直接返回 index.html 内容,而不是重定向
app.get('/', async (c) => {
try {
const htmlContent = fs.readFileSync('./index.html', 'utf-8')
return c.html(htmlContent)
} catch (error) {
console.error('读取index.html失败:', error)
return c.text('无法读取主页', 500)
}
})
// 获取reCAPTCHA令牌的路由
app.get('/genspark', async (c) => {
const headers = Object.fromEntries(c.req.raw.headers);
const cookieString = headers.cookie || '';
const cookies = parseCookies(cookieString);
let page = null;
let error = null;
try {
gensparkContext = await initGensparkContext();
// 设置cookies
if (cookies.length > 0) {
await gensparkContext.clearCookies();
await gensparkContext.addCookies(cookies);
}
page = await gensparkContext.newPage();
// 导航到Genspark页面
await page.goto(GENSPARK_URL, {
waitUntil: 'networkidle',
timeout: 30000 // 30秒超时,更合理
});
// 等待页面加载完成
await page.waitForTimeout(1000);
// 获取reCAPTCHA令牌
const token = await getReCaptchaToken(page);
if (!token) {
return c.json({ code: 500, message: '获取令牌失败:令牌为空' });
}
return c.json({
code: 200,
message: '获取令牌成功',
token: token
});
} catch (e) {
error = e
console.error('获取令牌失败:', e);
return c.json({
code: 500,
message: `获取令牌失败: ${e instanceof Error ? e.message : '未知错误'}`
});
} finally {
if (page) {
await page.close().catch(() => { });
}
// 不要在每次请求后关闭上下文,保持它以便重用
// 只有在出错时才重置上下文
if (error && gensparkContext) {
await gensparkContext.close().catch(() => { });
gensparkContext = null;
}
}
});
// 处理所有 HTTP 方法
app.all('*', async (c) => {
const url = c.req.query('url')
if (!url) {
return c.text('Missing url parameter', 400)
}
try {
const method = c.req.method
const headers = Object.fromEntries(c.req.raw.headers)
const body = method !== 'GET' ? await c.req.text() : undefined
const result = await handleRequest(url, method, headers, body)
// 创建标准响应
const response = new Response(result.body, {
status: result.status,
headers: new Headers({
...result.headers,
'content-encoding': 'identity' // 显式设置不使用压缩
})
})
return response
} catch (error) {
console.error('Error:', error)
return new Response('Internal Server Error', {
status: 500,
headers: new Headers({
'content-type': 'text/plain'
})
})
}
})
// 清理函数
async function cleanup() {
if (gensparkContext) {
await gensparkContext.close().catch(() => { });
gensparkContext = null;
}
if (browser) {
await browser.close().catch(() => { });
browser = null;
}
process.exit(0)
}
// 监听进程退出信号
process.on('SIGINT', cleanup)
process.on('SIGTERM', cleanup)
const port = Number(process.env.PORT || '7860');
console.log(`Server is running on port http://localhost:${port}`)
// 启动服务器
serve({
fetch: app.fetch,
port: port
})