fix: 修复无需鉴权的 Socks5 代理无法使用的问题

This commit is contained in:
foxhui
2026-01-25 03:54:29 +08:00
Unverified
parent 5b7d1e9322
commit 02847223dc
11 changed files with 650 additions and 180 deletions
+2 -2
View File
@@ -317,12 +317,12 @@ export const manifest = {
models: [
{ id: 'gemini-3-pro', codeName: 'gemini-3-pro-preview', imagePolicy: 'optional', type: 'text' },
{ id: 'gemini-2.5-pro', codeName: 'gemini-2.5pro', imagePolicy: 'optional', type: 'text' },
{ id: 'gemini-3-flash-preview', codeName: 'gemini-3-pro-preview', imagePolicy: 'optional', type: 'text' },
{ id: 'gemini-3-flash', codeName: 'gemini-3-pro-preview', imagePolicy: 'optional', type: 'text' },
{ id: 'gemini-2.5-flash', codeName: 'gemini-2.5-flash', imagePolicy: 'optional', type: 'text' },
{ id: 'gemini-3-pro-grounding', codeName: 'gemini-3-pro-preview', imagePolicy: 'optional', type: 'text' },
{ id: 'gemini-2.5-pro-grounding', codeName: 'gemini-2.5-pro', imagePolicy: 'optional', type: 'text' },
{ id: 'gemini-2.5-flash-grounding', codeName: 'gemini-2.5-flash', imagePolicy: 'optional', type: 'text' },
{ id: 'gemini-3-flash-preview-grounding', codeName: 'gemini-3-flash-preview', imagePolicy: 'optional', type: 'text' },
{ id: 'gemini-3-flash-grounding', codeName: 'gemini-3-flash-preview', imagePolicy: 'optional', type: 'text' },
],
// 导航处理器
+9 -2
View File
@@ -226,12 +226,20 @@ async function waitForElementStable(element, stableFrames = 10, timeout = 2000)
*/
export async function safeClick(page, target, options = {}) {
const clickCount = options.clickCount || 1;
const timeout = options.timeout || TIMEOUTS.ELEMENT_CLICK;
const waitStable = options.waitStable !== false; // 默认 true
const selector = typeof target === 'string' ? target : '元素';
// humanizeCursorMode: false=禁用, true=ghost-cursor, "camou"=Camoufox内置
// 只有 true 时才使用 ghost-cursor,其他情况都使用原生点击
const useGhostCursor = page?._humanizeCursorMode === true && page?.cursor;
const cursorSpeed = options.cursorSpeed ?? 40;
// 动态计算超时时间:使用 ghost-cursor 时,速度越慢超时越长
// 公式:基础超时 + 额外时间(50000ms / 速度)
// 速度40时额外1.25s,速度10时额外5s,速度5时额外10s
const baseTimeout = options.timeout || TIMEOUTS.ELEMENT_CLICK;
const timeout = useGhostCursor
? baseTimeout + Math.ceil(50000 / cursorSpeed)
: baseTimeout;
const doClick = async () => {
let el;
@@ -270,7 +278,6 @@ export async function safeClick(page, target, options = {}) {
if (box) {
const { x, y } = getHumanClickPoint(box, options.bias || 'random');
logger.debug('浏览器', `[safeClick] 移动鼠标到 (${x.toFixed(0)}, ${y.toFixed(0)})...`);
const cursorSpeed = options.cursorSpeed ?? 40;
await page.cursor.moveTo({ x, y }, { moveSpeed: cursorSpeed });
logger.debug('浏览器', `[safeClick] 执行点击...`);
await page.mouse.click(x, y, { clickCount });
+2 -2
View File
@@ -91,9 +91,9 @@ export class Worker {
logger.info('工作池', `[${this.name}] 正在初始化浏览器...`);
if (this.proxyConfig) {
logger.debug('工作池', `[${this.name}] 使用代理: ${this.proxyConfig.type}://${this.proxyConfig.host}:${this.proxyConfig.port}`);
logger.info('工作池', `[${this.name}] 使用代理: ${this.proxyConfig.type}://${this.proxyConfig.host}:${this.proxyConfig.port}`);
} else {
logger.debug('工作池', `[${this.name}] 直连模式(无代理)`);
logger.info('工作池', `[${this.name}] 直连模式(无代理)`);
}
if (sharedBrowser) {
+3 -7
View File
@@ -8,6 +8,7 @@ import path from 'path';
import os from 'os';
import crypto from 'crypto';
import { logger } from '../utils/logger.js';
import { CAMOUFOX_PATCHES } from '../../scripts/postinstall.js';
const PROJECT_ROOT = process.cwd();
@@ -53,16 +54,11 @@ export function preflight() {
errors.push('better-sqlite3 预编译文件缺失,请运行: npm run init');
}
// 2. 检查 camoufox-js 补丁(通过 MD5 对比)
// 2. 检查 camoufox-js 补丁(通过 MD5 对比,使用 postinstall.js 导出的补丁列表
const patchDir = path.join(PROJECT_ROOT, 'patches');
const targetDir = path.join(PROJECT_ROOT, 'node_modules', 'camoufox-js', 'dist');
const patchFiles = {
'camoufox-js@0.8.3.locale.patched.js': 'locale.js',
'camoufox-js@0.8.3.pkgman.patched.js': 'pkgman.js'
};
for (const [patchName, targetName] of Object.entries(patchFiles)) {
for (const [patchName, targetName] of Object.entries(CAMOUFOX_PATCHES)) {
const patchPath = path.join(patchDir, patchName);
const targetPath = path.join(targetDir, targetName);
+17 -34
View File
@@ -94,44 +94,27 @@ export async function getBrowserProxy(proxyConfig) {
const { type, host, port, user, passwd } = proxyConfig;
// 对于 SOCKS5 + 认证,需要转换为 HTTP 代理
if (type === 'socks5' && user && passwd) {
try {
const originalUrl = buildProxyUrl(proxyConfig);
logger.info('代理器', `检测到需鉴权的 SOCKS5 代理,正在创建本地代理桥接: ${host}:${port}`);
const httpProxyUrl = await anonymizeProxy(originalUrl);
// 保存状态用于后续清理
proxyState.anonymizedProxyUrl = httpProxyUrl;
proxyState.originalProxyUrl = originalUrl;
logger.info('代理器', `本地代理桥接已建立: ${httpProxyUrl} -> ${host}:${port}`);
return {
server: httpProxyUrl
};
} catch (error) {
logger.error('代理器', `本地代理桥接创建失败: ${error.message}`);
throw error;
// 构建代理 URL 字符串
// 注意:Camoufox 对 socks5:// 协议使用 new URL().origin 会返回 null
// 因此我们直接返回完整的 URL 字符串,让 Camoufox 使用 new URL().href
let proxyUrl;
if (user && passwd) {
// 带认证的代理格式: protocol://user:passwd@host:port
const protocol = type === 'socks5' ? 'socks5' : 'http';
proxyUrl = `${protocol}://${encodeURIComponent(user)}:${encodeURIComponent(passwd)}@${host}:${port}`;
} else {
// 不带认证的代理格式: protocol://host:port
if (type === 'socks5') {
proxyUrl = `socks5://${host}:${port}`;
} else {
proxyUrl = `http://${host}:${port}`;
}
}
// 对于其他情况(HTTP 代理、不带认证的 SOCKS5
const proxyUrl = type === 'socks5' ? `socks5://${host}:${port}` : `${host}:${port}`;
logger.info('代理器', `代理配置: ${type}://${host}:${port}${user ? ' (带认证)' : ''}`);
const proxyObject = {
server: proxyUrl
};
// 如果有认证信息,添加到代理对象
if (user && passwd) {
proxyObject.username = user;
proxyObject.password = passwd;
}
logger.info('代理器', `代理配置: ${type}://${host}:${port}`);
return proxyObject;
// 直接返回字符串格式,Camoufox 会正确解析
return proxyUrl;
}
/**