From 8dafe72fd7e0a1c4b1e6fdf8ec8fb010b6fee016 Mon Sep 17 00:00:00 2001 From: foxhui Date: Fri, 26 Dec 2025 02:11:22 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8C=87=E7=BA=B9=E6=8C=81=E4=B9=85?= =?UTF-8?q?=E5=8C=96=E6=B7=BB=E5=8A=A0=20WebGL=20=E5=92=8C=20Canvas=20?= =?UTF-8?q?=E5=99=AA=E7=82=B9=E7=AD=89=EF=BC=8C=E5=85=B3=E9=97=AD=E9=83=A8?= =?UTF-8?q?=E5=88=86=E7=BD=91=E9=A1=B5=E5=8A=A8=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 10 +++ src/backend/engine/launcher.js | 152 +++++++++++++++++++++++++-------- 2 files changed, 126 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 245ae94..b52a065 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.4.2] - 2025-12-25 + +### 🔄 Changed +- **浏览器指纹** + - 增加 WebGL 和 Canvas 噪点的持久化,防止频繁变化 + - 清洗插件列表,防止出现 FireFox 中有 Chrome 内置的 PDF 阅读器插件 + - 清洗 UA 标识,防止出现未来浏览器版本,导致某些网站报错403 (如:aistudio) +- **关闭动画** + - 通过 about:config 中的设置禁用背景高斯模糊 CSS 和减少动画,节省资源占用 + ## [3.4.1] - 2025-12-24 ### ✨ Added diff --git a/src/backend/engine/launcher.js b/src/backend/engine/launcher.js index 9d45266..8a070f5 100644 --- a/src/backend/engine/launcher.js +++ b/src/backend/engine/launcher.js @@ -9,6 +9,7 @@ */ import { Camoufox } from 'camoufox-js'; +import { sampleWebGL } from 'camoufox-js/dist/webgl/sample.js'; import { FingerprintGenerator } from 'fingerprint-generator'; import fs from 'fs'; import path from 'path'; @@ -115,59 +116,126 @@ function getCurrentOS() { } /** - * 获取或生成持久化指纹 + * 获取 WebGL 平台标识 + * 将操作系统名称转换为 sampleWebGL 支持的格式 + */ +function getWebGLPlatform(osName) { + if (osName === 'windows') return 'win'; + if (osName === 'macos') return 'mac'; + return 'lin'; +} + +/** + * 获取或生成持久化指纹 (含 WebGL 配置校验) * @param {string} filePath - JSON文件保存路径 */ -function getPersistentFingerprint(filePath) { +async function getPersistentFingerprint(filePath) { // 确保 data 目录存在 const dir = path.dirname(filePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } - // 尝试读取现有指纹 + let fingerprintData = null; + let webglPair = null; + let shouldSave = false; + const currentOS = getCurrentOS(); + const targetWebGLOS = getWebGLPlatform(currentOS); + + // 1. 尝试读取现有指纹 if (fs.existsSync(filePath)) { try { const fileContent = fs.readFileSync(filePath, 'utf8'); - const savedData = JSON.parse(fileContent); - - // 简单校验:确保读取的是一个对象 - if (savedData && typeof savedData === 'object') { - return savedData; - } + fingerprintData = JSON.parse(fileContent); } catch (e) { - logger.warn('浏览器', `读取指纹文件失败,将重新生成: ${e.message}`); + logger.warn('浏览器', `指纹文件损坏: ${e.message}`); } } - // 生成新指纹 - const currentOS = getCurrentOS(); - logger.info('浏览器', `正在为系统 [${currentOS}] 生成新指纹...`); + // 2. 校验 WebGL 配置的有效性 (从 videoCard 读取) + if (fingerprintData?.videoCard?.['webGl:vendor'] && fingerprintData?.videoCard?.['webGl:renderer']) { + const savedVendor = fingerprintData.videoCard['webGl:vendor']; + const savedRenderer = fingerprintData.videoCard['webGl:renderer']; + try { + // 拿着保存的配置,去数据库里"试探"一下是否存在 + await sampleWebGL(targetWebGLOS, savedVendor, savedRenderer); - // 为不同系统使用不同的配置策略 - const generatorOptions = { - browsers: ['firefox'], - operatingSystems: [currentOS], - devices: ['desktop'], - locales: ['en-US'], - screen: { - minWidth: 1280, maxWidth: 1366, - minHeight: 720, maxHeight: 768 + // 如果没报错,说明配置有效,保留使用 + webglPair = [savedVendor, savedRenderer]; + logger.debug('浏览器', `加载 WebGL 配置成功: ${savedRenderer}`); + } catch (e) { + // 数据库里没找到 -> 配置失效 + logger.warn('浏览器', `保存的 WebGL 配置与当前系统(${targetWebGLOS})不匹配,将重新生成`); + webglPair = null; + shouldSave = true; } - }; + } - const generator = new FingerprintGenerator(generatorOptions); + // 3. 如果指纹完全不存在,生成新的基础指纹 + if (!fingerprintData) { + logger.info('浏览器', `正在为系统 [${currentOS}] 生成新指纹...`); + const generatorOptions = { + browsers: ['firefox'], + operatingSystems: [currentOS], + devices: ['desktop'], + locales: ['en-US'], + screen: { minWidth: 1280, maxWidth: 1366, minHeight: 720, maxHeight: 768 } + }; + const generator = new FingerprintGenerator(generatorOptions); + fingerprintData = generator.getFingerprint().fingerprint; - const result = generator.getFingerprint(); + // 清洗 UA 版本 + if (fingerprintData.navigator) { + let ua = fingerprintData.navigator.userAgent; + const TARGET_VERSION = "135.0"; + ua = ua.replace(/rv:[\d\.]+/g, `rv:${TARGET_VERSION}`); + ua = ua.replace(/Firefox\/[\d\.]+/g, `Firefox/${TARGET_VERSION}`); + fingerprintData.navigator.userAgent = ua; + } - // 关键点:我们只需要 result.fingerprint 部分 - const fingerprintToSave = result.fingerprint; + // 清洗插件数据 + if (fingerprintData.pluginsData) { + fingerprintData.pluginsData.plugins = []; + fingerprintData.pluginsData.mimeTypes = []; + } - // 保存到文件 - fs.writeFileSync(filePath, JSON.stringify(fingerprintToSave, null, 2)); - logger.info('浏览器', `新指纹已保存至: ${filePath}`); + shouldSave = true; + } - return fingerprintToSave; + // 4. 如果 WebGL 配置为空,重新生成 + if (!webglPair) { + try { + logger.info('浏览器', `正在生成新的 WebGL 配置 (${targetWebGLOS})...`); + const webglData = await sampleWebGL(targetWebGLOS); + webglPair = [webglData['webGl:vendor'], webglData['webGl:renderer']]; + + // 覆盖 videoCard + fingerprintData.videoCard = { + 'webGl:vendor': webglPair[0], + 'webGl:renderer': webglPair[1] + }; + + shouldSave = true; + } catch (e) { + logger.error('浏览器', `致命错误:无法生成 WebGL 配置: ${e.message}`); + } + } + + // 5. 如果 Canvas 噪点不存在,生成新的 + if (fingerprintData.canvasOffset === undefined) { + const offset = Math.floor(Math.random() * 41) - 20; + fingerprintData.canvasOffset = offset; + logger.info('浏览器', `已生成 Canvas 噪点偏移: ${offset}`); + shouldSave = true; + } + + // 5. 如果有变动,保存回文件 + if (shouldSave) { + fs.writeFileSync(filePath, JSON.stringify(fingerprintData, null, 2)); + logger.info('浏览器', `指纹已更新并保存至: ${filePath}`); + } + + return fingerprintData; } /** @@ -211,7 +279,7 @@ export async function initBrowserBase(config, options = {}) { // 获取指纹对象(指纹文件放在对应的 userDataDir 内) const fingerprintPath = path.join(userDataDir, 'fingerprint.json'); - const myFingerprint = getPersistentFingerprint(fingerprintPath); + const myFingerprint = await getPersistentFingerprint(fingerprintPath); // 构造 Camoufox 启动选项 const currentOS = getCurrentOS(); @@ -219,16 +287,26 @@ export async function initBrowserBase(config, options = {}) { executable_path: browserConfig.path || undefined, headless: headlessMode, user_data_dir: userDataDir, - window: [1366, 768], ff_version: 135, fingerprint: myFingerprint, os: currentOS, i_know_what_im_doing: true, + webgl_config: myFingerprint.videoCard ? [myFingerprint.videoCard['webGl:vendor'], myFingerprint.videoCard['webGl:renderer']] : undefined, block_webrtc: true, exclude_addons: ['UBO'], geoip: true, config: { - forceScopeAccess: true + forceScopeAccess: true, + // Canvas 抗指纹:注入固定噪点偏移 + 'canvas:aaOffset': myFingerprint.canvasOffset ?? 0, + 'canvas:aaCapOffset': true + }, + // 关闭动画减轻资源压力 + firefox_user_prefs: { + // 禁用背景模糊滤镜 (高 CPU 消耗) + 'layout.css.backdrop-filter.enabled': false, + // 告诉网页用户倾向于减少动画 (触发网页自身的优化) + 'ui.prefersReducedMotion': 1 } }; @@ -267,8 +345,10 @@ export async function initBrowserBase(config, options = {}) { page = await context.newPage(); } - // 强制刷新视口大小 - await page.setViewportSize({ width: 1366, height: 768 }); + // 强制刷新视口大小 (使用指纹中的屏幕尺寸) + const screenWidth = myFingerprint.screen?.availWidth || 1366; + const screenHeight = myFingerprint.screen?.availHeight || 768; + await page.setViewportSize({ width: screenWidth, height: screenHeight }); // 返回 context 和 page(导航、预热、cursor 初始化由工作池负责) return {