Spaces:
Running
Running
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/58.0.3029.110 Safari/537.3'; | |
// 初始化浏览器 | |
async function initBrowser() { | |
if (!browser) { | |
browser = await chromium.launch({ | |
headless: true, | |
args: [ | |
'--no-sandbox', | |
'--disable-setuid-sandbox', | |
'--disable-dev-shm-usage', | |
'--disable-blink-features=AutomationControlled', // 禁用自动化特征 | |
'--disable-infobars', | |
'--window-size=1920,1080' | |
], | |
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: userAgent, | |
viewport: { width: 1920, height: 1080 }, | |
extraHTTPHeaders: { | |
'Accept-Language': 'en-US,en;q=0.9' | |
}, | |
deviceScaleFactor: 1, | |
locale: 'en-US', | |
timezoneId: 'America/New_York', | |
geolocation: { longitude: -73.935242, latitude: 40.730610 }, // 纽约坐标,可根据需要调整 | |
permissions: ['geolocation'], | |
javaScriptEnabled: true, | |
bypassCSP: true, // 绕过内容安全策略 | |
colorScheme: 'light', | |
acceptDownloads: true, | |
}) | |
// 注入反检测脚本 | |
await gensparkContext.addInitScript(() => { | |
Object.defineProperty(navigator, 'webdriver', { get: () => false }) | |
Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5] }) | |
//@ts-ignore | |
window.navigator.chrome = { runtime: {} } | |
Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }) | |
}) | |
} | |
return gensparkContext | |
} | |
// 验证响应头值是否有效 | |
function isValidHeaderValue(value: string): boolean { | |
// 检查值是否为空或包含无效字符 | |
if (!value || typeof value !== 'string') return false; | |
// 检查是否包含换行符或回车符 | |
if (/[\r\n]/.test(value)) return false; | |
return true; | |
} | |
// 处理请求转发 | |
async function handleRequest(url: string, method: string, headers: any, body?: any) { | |
const browser = await initBrowser() | |
const page = await browser.newPage() | |
try { | |
// 只移除确实需要移除的请求头 | |
delete headers['host'] | |
delete headers['connection'] | |
delete headers['content-length'] | |
delete headers['accept-encoding'] | |
// 移除cf相关的头 | |
delete headers['cdn-loop'] | |
delete headers['cf-connecting-ip'] | |
delete headers['cf-connecting-o2o'] | |
delete headers['cf-ew-via'] | |
delete headers['cf-ray'] | |
delete headers['cf-visitor'] | |
delete headers['cf-worker'] | |
//移除其他无效的请求头 | |
delete headers['x-direct-url'] | |
delete headers['x-forwarded-for'] | |
delete headers['x-forwarded-port'] | |
delete headers['x-forwarded-proto'] | |
headers['user-agent'] = userAgent | |
console.log('处理请求:', method, url, headers, body) | |
// 设置请求拦截器 | |
await page.route('**/*', async (route: Route) => { | |
const request = route.request() | |
if (request.url() === url) { | |
await route.continue({ | |
method: method, | |
headers: { | |
...request.headers(), | |
...headers | |
}, | |
postData: body | |
}) | |
} else { | |
// 允许其他资源加载 | |
await route.continue() | |
} | |
}) | |
// 配置页面请求选项 | |
const response = await page.goto(url, { | |
waitUntil: 'domcontentloaded', // 改为更快的加载策略 | |
timeout: 600000 | |
}) | |
if (!response) { | |
throw new Error('未收到响应') | |
} | |
// 等待页面加载完成 | |
await page.waitForLoadState('networkidle', { timeout: 600000 }).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, responseBody.toString()) | |
await page.close() | |
return { | |
status, | |
headers: validHeaders, | |
body: responseBody | |
} | |
} catch (error: any) { | |
await page.close() | |
console.error('请求处理错误:', error) | |
throw new Error(`请求失败: ${error.message}`) | |
} | |
} | |
// 添加静态文件服务 | |
app.use('/public/*', serveStatic({ root: './' })) | |
// 修改点 1: 处理根路由直接返回 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) | |
} | |
}) | |
// 修改点 2: 添加 /genspark 路由来获取reCAPTCHA令牌 | |
app.get('/genspark', async (c) => { | |
const headers = Object.fromEntries(c.req.raw.headers) | |
// Get the cookie string from headers | |
const cookieString = headers.cookie || ''; | |
// Parse cookies into an array of objects with name and value properties | |
const cookies = cookieString.split(';').map(cookie => { | |
const [name, value] = cookie.trim().split('='); | |
if (name.startsWith("_ga")) { | |
return { name, value, domain: 'genspark.ai', path: '/' }; | |
} | |
return { name, value, domain: 'www.genspark.ai', path: '/' }; | |
}).filter(cookie => cookie.name && cookie.value); | |
gensparkContext = await initGensparkContext() | |
console.log('Cookies:', cookies) | |
if (cookies && cookies.length > 0) { | |
await gensparkContext.clearCookies() | |
await gensparkContext.addCookies(cookies); | |
} | |
const gensparkPage = await gensparkContext.newPage() | |
try { | |
//刷新页面以确保获取新令牌 | |
await gensparkPage.goto('https://www.genspark.ai/agents?type=moa_chat', { | |
waitUntil: 'networkidle', | |
timeout: 3600000 | |
}) | |
// 执行脚本获取令牌 | |
const token = await gensparkPage.evaluate(() => { | |
return new Promise((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); | |
}); | |
}).catch(error => { | |
return c.json({ code: 500, message: '获取令牌失败' }) | |
}); | |
console.log('token:', token) | |
return c.json({ code: 200, message: '获取令牌成功', token: token }) | |
} | |
catch (error) { | |
console.error('获取令牌失败:', error) | |
if (gensparkContext) { | |
await gensparkContext.close().catch(() => { }); | |
gensparkContext = null; | |
} | |
} | |
finally { | |
await gensparkPage.close().catch(() => { }); | |
} | |
console.log('token:', "获取令牌失败") | |
return c.json({ code: 500, message: '获取令牌失败' }) | |
}) | |
// 处理所有 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 | |
}) |