github-actions[bot]
Update from GitHub Actions
84ab4d3
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
})