diff --git a/you_providers/browserDetector.mjs b/you_providers/browserDetector.mjs new file mode 100644 index 0000000..6b6c5a1 --- /dev/null +++ b/you_providers/browserDetector.mjs @@ -0,0 +1,115 @@ +import os from 'os'; +import fs from 'fs'; +import path from 'path'; +import {execSync} from 'child_process'; + +export function detectBrowser(preferredBrowser = 'auto') { + const platform = os.platform(); + let browsers = { + 'chrome': null, + 'edge': null + }; + + if (platform === 'win32') { + browsers.chrome = findWindowsBrowser('Chrome'); + browsers.edge = findWindowsBrowser('Edge'); + } else if (platform === 'darwin') { + browsers.chrome = findMacOSBrowser('Google Chrome'); + browsers.edge = findMacOSBrowser('Microsoft Edge'); + } else if (platform === 'linux') { + browsers.chrome = findLinuxBrowser('google-chrome'); + + //Arch下AUR安装的chrome为google-chrome-stable + if (browsers.chrome == null) { + browsers.chrome = findLinuxBrowser('google-chrome-stable'); + } + + browsers.edge = findLinuxBrowser('microsoft-edge'); + } + + if (preferredBrowser === 'auto' || preferredBrowser === undefined) { + if (browsers.chrome) { + return browsers.chrome; + } else if (browsers.edge) { + return browsers.edge; + } + } else if (browsers[preferredBrowser]) { + console.log(`使用${preferredBrowser === 'chrome' ? 'Chrome' : 'Edge'}浏览器`); + return browsers[preferredBrowser]; + } + + console.error('未找到Chrome或Edge浏览器,请确保已安装其中之一'); + process.exit(1); +} + +function findWindowsBrowser(browserName) { + const regKeys = { + 'Chrome': ['chrome.exe', 'Google\\Chrome'], + 'Edge': ['msedge.exe', 'Microsoft\\Edge'] + }; + const [exeName, folderName] = regKeys[browserName]; + + const regQuery = (key) => { + try { + return execSync(`reg query "${key}" /ve`).toString().trim().split('\r\n').pop().split(' ').pop(); + } catch (error) { + return null; + } + }; + + let browserPath = regQuery(`HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\${exeName}`) || + regQuery(`HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\${exeName}`); + + if (browserPath && fs.existsSync(browserPath)) { + return browserPath; + } + + const commonPaths = [ + `C:\\Program Files\\${browserName}\\Application\\${exeName}`, + `C:\\Program Files (x86)\\${browserName}\\Application\\${exeName}`, + `C:\\Program Files (x86)\\Microsoft\\${browserName}\\Application\\${exeName}`, + `${process.env.LOCALAPPDATA}\\${browserName}\\Application\\${exeName}`, + `${process.env.USERPROFILE}\\AppData\\Local\\${browserName}\\Application\\${exeName}`, + ]; + + const foundPath = commonPaths.find(path => fs.existsSync(path)); + if (foundPath) { + return foundPath; + } + + const userAppDataPath = process.env.LOCALAPPDATA || `${process.env.USERPROFILE}\\AppData\\Local`; + const appDataPath = path.join(userAppDataPath, folderName, 'Application'); + + if (fs.existsSync(appDataPath)) { + const files = fs.readdirSync(appDataPath); + const exePath = files.find(file => file.toLowerCase() === exeName.toLowerCase()); + if (exePath) { + return path.join(appDataPath, exePath); + } + } + + return null; +} + +function findMacOSBrowser(browserName) { + const paths = [ + `/Applications/${browserName}.app/Contents/MacOS/${browserName}`, + `${os.homedir()}/Applications/${browserName}.app/Contents/MacOS/${browserName}`, + ]; + + for (const path of paths) { + if (fs.existsSync(path)) { + return path; + } + } + + return null; +} + +function findLinuxBrowser(browserName) { + try { + return execSync(`which ${browserName}`).toString().trim(); + } catch (error) { + return null; + } +} \ No newline at end of file diff --git a/you_providers/browserDisplayFixer.mjs b/you_providers/browserDisplayFixer.mjs new file mode 100644 index 0000000..31acb35 --- /dev/null +++ b/you_providers/browserDisplayFixer.mjs @@ -0,0 +1,206 @@ +/** + * @param {Object} page - Puppeteer + * @param {Object} options - 配置选项 + * @param {number} options.width - 视口宽度 + * @param {number} options.height - 视口高度 + * @param {number} options.deviceScaleFactor - 设备缩放因子 + * @param {boolean} options.isMobile - 模拟移动设备 + * @param {boolean} options.hasTouch - 支持触摸 + * @param {boolean} options.isLandscape - 横屏 + * @returns {Promise} + */ +export async function fixBrowserDisplay(page, options = {}) { + if (!page) { + console.error('页面对象为空,无法修复显示'); + return; + } + + const defaultOptions = { + width: 1280, + height: 800, + deviceScaleFactor: 1, + isMobile: false, + hasTouch: false, + isLandscape: true + }; + + const settings = {...defaultOptions, ...options}; + + try { + // 设置视口大小和设备比例 + await page.setViewport({ + width: settings.width, + height: settings.height, + deviceScaleFactor: settings.deviceScaleFactor, + isMobile: settings.isMobile, + hasTouch: settings.hasTouch, + isLandscape: settings.isLandscape + }); + + // 尝试调整窗口大小 + const session = await page.target().createCDPSession(); + await session.send('Emulation.setDeviceMetricsOverride', { + width: settings.width, + height: settings.height, + deviceScaleFactor: settings.deviceScaleFactor, + mobile: settings.isMobile, + screenWidth: settings.width, + screenHeight: settings.height + }); + + // 重置页面缩放 + await page.evaluate(() => { + document.body.style.zoom = '100%'; + document.body.style.transform = 'scale(1)'; + document.body.style.transformOrigin = '0 0'; + + // 尝试修复可能存在的CSS + const styleElement = document.createElement('style'); + styleElement.textContent = ` + html, body { + width: 100% !important; + height: 100% !important; + overflow: auto !important; + } + + .container, .main, #app, #root { + max-width: 100% !important; + width: auto !important; + } + `; + document.head.appendChild(styleElement); + + window.dispatchEvent(new Event('resize')); + }); + + } catch (error) { + console.error('修复浏览器显示时出错:', error); + } +} + +/** + * 调整CSS比例 + * @param {Object} page - Puppeteer + * @param {number} scale - 缩放比例 + * @returns {Promise} + */ +export async function adjustCssScaling(page, scale = 1) { + if (!page) return; + + try { + await page.evaluate((scale) => { + const styleElem = document.createElement('style'); + styleElem.id = 'puppeteer-display-fix'; + styleElem.textContent = ` + html { + transform: scale(${scale}); + transform-origin: top left; + width: ${100 / scale}% !important; + height: ${100 / scale}% !important; + } + `; + document.head.appendChild(styleElem); + + // 重新计算布局 + window.dispatchEvent(new Event('resize')); + }, scale); + } catch (error) { + console.error('调整CSS比例时出错:', error); + } +} + +/** + * 修复高DPI + * @param {Object} page - Puppeteer + * @returns {Promise} + */ +export async function fixHighDpiDisplay(page) { + if (!page) return; + + try { + // 检测设备像素比 + const devicePixelRatio = await page.evaluate(() => window.devicePixelRatio); + + if (devicePixelRatio > 1) { + await page.setViewport({ + width: 1280, + height: 800, + deviceScaleFactor: devicePixelRatio + }); + + await page.evaluate((dpr) => { + const meta = document.createElement('meta'); + meta.setAttribute('name', 'viewport'); + meta.setAttribute('content', `initial-scale=1, minimum-scale=1, maximum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi, user-scalable=no`); + document.head.appendChild(meta); + }, devicePixelRatio); + } + } catch (error) { + console.error('修复高DPI显示时出错:', error); + } +} + +/** + * 完整浏览器显示优化 + * @param {Object} page - Puppeteer + * @param {Object} options - 配置 + * @returns {Promise} + */ +export async function optimizeBrowserDisplay(page, options = {}) { + const defaultOptions = { + width: 1280, + height: 800, + deviceScaleFactor: 1, + cssScale: null, + fixHighDpi: true, + forceResize: true + }; + + const config = {...defaultOptions, ...options}; + + try { + // 基本显示修复 + await fixBrowserDisplay(page, { + width: config.width, + height: config.height, + deviceScaleFactor: config.deviceScaleFactor + }); + + // 修复高DPI显示 + if (config.fixHighDpi) { + await fixHighDpiDisplay(page); + } + + if (config.cssScale !== null) { + await adjustCssScaling(page, config.cssScale); + } + + // 如果强制调整窗口大小 + if (config.forceResize && !config.isHeadless) { + try { + const client = await page.target().createCDPSession(); + await client.send('Browser.getWindowForTarget'); + await client.send('Browser.setWindowBounds', { + windowId: 1, + bounds: { + width: config.width, + height: config.height + } + }); + } catch (resizeError) { + // console.log('无法调整窗口大小:', resizeError.message); + + try { + await page.evaluate((width, height) => { + window.resizeTo(width, height); + }, config.width, config.height); + } catch (altError) { + console.log('失败:', altError.message); + } + } + } + + } catch (error) { + console.error('浏览器显示优化失败:', error); + } +} diff --git a/you_providers/browserFingerprint.mjs b/you_providers/browserFingerprint.mjs new file mode 100644 index 0000000..d219382 --- /dev/null +++ b/you_providers/browserFingerprint.mjs @@ -0,0 +1,1626 @@ +import path from 'path'; +import {fileURLToPath} from 'url'; +import crypto from 'crypto'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// 操作系统 +const osList = [ + { + name: 'Windows', + versions: ['10.0', '11.0'], + platforms: ['Win32', 'Win64', 'x64'] + }, + { + name: 'Macintosh', + versions: ['Intel Mac OS X 10_15_7', 'Intel Mac OS X 11_6_0', 'Intel Mac OS X 12_3_1', 'Apple Mac OS X 13_2_1'], + platforms: ['MacIntel'] + }, + { + name: 'Linux', + versions: ['x86_64', 'i686'], + platforms: ['Linux x86_64', 'Linux i686'] + }, + { + name: 'Android', + versions: ['11', '12', '13', '14'], + platforms: ['Android'] + }, + { + name: 'iOS', + versions: ['15_4', '16_2', '17_0'], + platforms: ['iPhone', 'iPad'] + } +]; + +// 浏览器 +const browserVersions = { + 'chrome': { + name: 'Chrome', + // 主版本.次版本.构建号.补丁号 + majorVersions: [120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134], + minorVersions: [0, 1, 2, 3], + buildVersions: [5000, 5500, 6000, 6500, 6700, 6800, 6900, 7000, 7100], + patchVersions: [80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200], + brandName: "Google Chrome", + brandVersion: "1.0.0.0", + fullVersion: "1.0.0.0", + }, + 'edge': { + name: 'Edge', + majorVersions: [120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134], + minorVersions: [0, 1, 2, 3], + buildVersions: [5000, 5500, 6000, 6500, 6700, 6800, 6900, 7000, 7100], + patchVersions: [80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200], + brandName: "Microsoft Edge", + brandVersion: "1.0.0.0", + fullVersion: "1.0.0.0", + }, + 'firefox': { + name: 'Firefox', + majorVersions: [120, 121, 122, 123, 124], + minorVersions: [0, 1, 2], + buildVersions: [], + patchVersions: [], + brandName: "Firefox", + brandVersion: "1.0", + fullVersion: "1.0", + }, + 'safari': { + name: 'Safari', + majorVersions: [15, 16, 17], + minorVersions: [0, 1, 2, 3, 4, 5, 6], + buildVersions: [], + patchVersions: [], + brandName: "Safari", + brandVersion: "1.0.0", + fullVersion: "1.0.0", + }, + 'opera': { + name: 'Opera', + majorVersions: [96, 97, 98, 99, 100, 101], + minorVersions: [0, 1, 2, 3], + buildVersions: [1000, 2000, 3000, 4000], + patchVersions: [10, 20, 30, 40, 50], + brandName: "Opera", + brandVersion: "1.0.0.0", + fullVersion: "1.0.0.0", + } +}; + +// WebGL 供应商和渲染器映射表 +const gpuInfo = { + 'NVIDIA': [ + 'ANGLE (NVIDIA, NVIDIA GeForce GTX 1650 Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (NVIDIA, NVIDIA GeForce RTX 2060 Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (NVIDIA, NVIDIA GeForce RTX 2070 Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (NVIDIA, NVIDIA GeForce RTX 3050 Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (NVIDIA, NVIDIA GeForce RTX 3070 Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (NVIDIA, NVIDIA GeForce RTX 3080 Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (NVIDIA, NVIDIA GeForce RTX 3090 Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (NVIDIA, NVIDIA GeForce RTX 4060 Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (NVIDIA, NVIDIA GeForce RTX 4080 Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (NVIDIA, NVIDIA GeForce RTX 4090 Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 SUPER Direct3D11 vs_5_0 ps_5_0, D3D11)', + ], + 'AMD': [ + 'ANGLE (AMD, AMD Radeon RX 570 Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (AMD, AMD Radeon RX 580 Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (AMD, AMD Radeon RX 5500 XT Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (AMD, AMD Radeon RX 5600 XT Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (AMD, AMD Radeon RX 5700 XT Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (AMD, AMD Radeon RX 6600 Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (AMD, AMD Radeon RX 6700 XT Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (AMD, AMD Radeon RX 6800 XT Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (AMD, AMD Radeon RX 6900 XT Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (AMD, AMD Radeon RX 7600 Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (AMD, AMD Radeon RX 7700 XT Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (AMD, AMD Radeon RX 7800 XT Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (AMD, AMD Radeon RX 7900 XT Direct3D11 vs_5_0 ps_5_0, D3D11)', + ], + 'Intel': [ + 'ANGLE (Intel, Intel(R) UHD Graphics 620 Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (Intel, Intel(R) UHD Graphics 630 Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (Intel, Intel(R) UHD Graphics 730 Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (Intel, Intel(R) UHD Graphics 750 Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (Intel, Intel(R) Iris(TM) Xe Graphics Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (Intel, Intel(R) Iris(TM) Plus Graphics 655 Direct3D11 vs_5_0 ps_5_0, D3D11)', + 'ANGLE (Intel, Intel(R) Arc(TM) A380 Graphics Direct3D11 vs_5_0 ps_5_0, D3D11)', + ], + 'Apple': [ + 'Apple M1', + 'Apple M1 Pro', + 'Apple M1 Max', + 'Apple M1 Ultra', + 'Apple M2', + 'Apple M2 Pro', + 'Apple M2 Max', + 'Apple M3', + 'Apple M3 Pro', + 'Apple M3 Max', + ], + 'Mobile': [ + 'Mali-G78 MP12', + 'Adreno 650', + 'Adreno 660', + 'Adreno 730', + 'Apple GPU (Metal)', + ] +}; + +// 设备名称列表 +const deviceNames = [ + // Windows + 'DESKTOP-', 'LAPTOP-', 'PC-', 'WIN-', 'WORKSTATION-', + // Mac + 'MacBook-Pro', 'MacBook-Air', 'iMac-Pro', 'Mac-mini', 'Mac-Studio', + // 通用 + 'DELL-', 'HP-', 'LENOVO-', 'ASUS-', 'ACER-', 'MSI-', 'ALIENWARE-', 'GIGABYTE-' +]; + +// 生成随机设备名称 +function generateDeviceNameSuffix() { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + let result = ''; + const length = Math.floor(Math.random() * 6) + 4; // 4-9位 + + for (let i = 0; i < length; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } + + return result; +} + +// MAC地址 +const macPrefixes = [ + 'E8-2A-EA', '00-1A-2B', 'AC-DE-48', 'B8-27-EB', 'DC-A6-32', + '00-50-56', '00-0C-29', '00-05-69', '00-25-90', 'BC-5F-F4', + '48-45-20', '6C-4B-90', '94-E9-79', '5C-F9-38', '64-BC-0C', + 'B4-2E-99', '8C-85-90', '34-97-F6', 'A4-83-E7', '78-7B-8A' +]; + +// 区域语言 +const localeSettings = { + 'en-US': { + languages: ['en-US', 'en'], + timeZones: ['America/New_York', 'America/Chicago', 'America/Denver', 'America/Los_Angeles'] + }, + 'en-GB': { + languages: ['en-GB', 'en-US', 'en'], + timeZones: ['Europe/London', 'Europe/Dublin'] + }, + 'zh-CN': { + languages: ['zh-CN', 'zh', 'en-US', 'en'], + timeZones: ['Asia/Shanghai', 'Asia/Hong_Kong'] + }, + 'zh-TW': { + languages: ['zh-TW', 'zh', 'en-US', 'en'], + timeZones: ['Asia/Taipei'] + }, + 'ja-JP': { + languages: ['ja-JP', 'ja', 'en-US', 'en'], + timeZones: ['Asia/Tokyo'] + }, + 'ko-KR': { + languages: ['ko-KR', 'ko', 'en-US', 'en'], + timeZones: ['Asia/Seoul'] + }, + 'fr-FR': { + languages: ['fr-FR', 'fr', 'en-US', 'en'], + timeZones: ['Europe/Paris'] + }, + 'de-DE': { + languages: ['de-DE', 'de', 'en-US', 'en'], + timeZones: ['Europe/Berlin'] + }, + 'es-ES': { + languages: ['es-ES', 'es', 'en-US', 'en'], + timeZones: ['Europe/Madrid'] + }, + 'ru-RU': { + languages: ['ru-RU', 'ru', 'en-US', 'en'], + timeZones: ['Europe/Moscow'] + }, + 'pt-BR': { + languages: ['pt-BR', 'pt', 'en-US', 'en'], + timeZones: ['America/Sao_Paulo'] + }, + 'nl-NL': { + languages: ['nl-NL', 'nl', 'en-US', 'en'], + timeZones: ['Europe/Amsterdam'] + }, + 'it-IT': { + languages: ['it-IT', 'it', 'en-US', 'en'], + timeZones: ['Europe/Rome'] + }, + 'pl-PL': { + languages: ['pl-PL', 'pl', 'en-US', 'en'], + timeZones: ['Europe/Warsaw'] + }, + 'tr-TR': { + languages: ['tr-TR', 'tr', 'en-US', 'en'], + timeZones: ['Europe/Istanbul'] + } +}; + +// CPU核心 +const computerSpecs = [ + {cores: 2, ram: [2, 4]}, + {cores: 4, ram: [4, 8, 16]}, + {cores: 6, ram: [8, 16, 32]}, + {cores: 8, ram: [8, 16, 32, 64]}, + {cores: 10, ram: [16, 32, 64]}, + {cores: 12, ram: [16, 32, 64, 128]}, + {cores: 16, ram: [32, 64, 128]}, + {cores: 24, ram: [32, 64, 128]}, + {cores: 32, ram: [64, 128, 256]} +]; + +// 插件 +const browserPlugins = { + 'chrome': [ + { + name: 'Chrome PDF Plugin', + description: 'Portable Document Format', + filename: 'internal-pdf-viewer', + mimeTypes: ['application/pdf'] + }, + { + name: 'Chrome PDF Viewer', + description: '', + filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', + mimeTypes: ['application/pdf'] + }, + { + name: 'Native Client', + description: '', + filename: 'internal-nacl-plugin', + mimeTypes: ['application/x-nacl', 'application/x-pnacl'] + } + ], + 'edge': [ + { + name: 'Microsoft Edge PDF Plugin', + description: 'Portable Document Format', + filename: 'internal-pdf-viewer', + mimeTypes: ['application/pdf'] + }, + { + name: 'Microsoft Edge PDF Viewer', + description: '', + filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', + mimeTypes: ['application/pdf'] + }, + { + name: 'Native Client', + description: '', + filename: 'internal-nacl-plugin', + mimeTypes: ['application/x-nacl', 'application/x-pnacl'] + } + ], + 'firefox': [ + {name: 'Firefox PDF Viewer', description: 'PDF Viewer', filename: 'pdf.js', mimeTypes: ['application/pdf']} + ], + 'safari': [ + { + name: 'QuickTime Plugin', + description: 'QuickTime Plug-in', + filename: 'QuickTime Plugin.plugin', + mimeTypes: ['video/quicktime'] + }, + { + name: 'WebKit built-in PDF', + description: 'PDF Viewer', + filename: 'internal-pdf-viewer', + mimeTypes: ['application/pdf'] + } + ] +}; + +/** + * 随机整数 + * @param {number} min + * @param {number} max + * @returns {number} + */ +function getRandomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +/** + * @param {Array} array - 选项数组 + * @returns {*} - 随机选择的项 + */ +function randomChoice(array) { + if (!Array.isArray(array) || array.length === 0) { + return null; + } + return array[Math.floor(Math.random() * array.length)]; +} + +/** + * @param {number} percentChance - true概率百分比 (0-100) + * @returns {boolean} - 随机布尔值 + */ +function randomChance(percentChance) { + return Math.random() * 100 < percentChance; +} + +/** + * @returns {string} - 随机种子 + */ +function createRandomSeed() { + return crypto.randomBytes(16).toString('hex'); +} + +/** + * @param {string} seed - 种子字符串 + * @param {number} min - 最小值 + * @param {number} max - 最大值 + * @returns {number} - 伪随机数 + */ +function seededRandom(seed, min, max) { + const hash = crypto.createHash('sha256').update(seed).digest('hex'); + const decimal = parseInt(hash.substring(0, 8), 16) / 0xffffffff; + return Math.floor(decimal * (max - min + 1)) + min; +} + +/** + * 随机MAC地址 + * @returns {string} + */ +function generateRandomMAC() { + const prefix = randomChoice(macPrefixes); + const bytes = []; + for (let i = 0; i < 3; i++) { + bytes.push(Math.floor(Math.random() * 256).toString(16).padStart(2, '0').toUpperCase()); + } + return `${prefix}-${bytes.join('-')}`; +} + +/** + * 详细版本号 + * @param {Object} browser - 浏览器 + * @returns {string} - 如 "133.0.6834.110" + */ +function generateRealisticVersion(browser) { + if (!browser || !browser.majorVersions) { + return "100.0.0.0"; // 默认值 + } + + const majorVersion = randomChoice(browser.majorVersions); + const minorVersion = randomChoice(browser.minorVersions || [0]); + + // Chrome/Edge 风格: 133.0.6834.110 + if (browser.buildVersions && browser.buildVersions.length > 0 && browser.patchVersions && browser.patchVersions.length > 0) { + const buildVersion = randomChoice(browser.buildVersions); + const patchVersion = randomChoice(browser.patchVersions); + return `${majorVersion}.${minorVersion}.${buildVersion}.${patchVersion}`; + } + // Firefox 风格: 123.0.1 + else if (browser.minorVersions && browser.minorVersions.length > 0) { + return `${majorVersion}.${minorVersion}`; + } + // 简单: 15.4 + else { + return `${majorVersion}.${getRandomInt(0, 9)}`; + } +} + +/** + * 随机用户代理 + * @param {string} browserType - 浏览器类型 + * @returns {string} + */ +function generateRealisticUserAgent(browserType = null) { + let browser; + if (browserType && browserVersions[browserType.toLowerCase()]) { + browser = browserVersions[browserType.toLowerCase()]; + } else { + const browserKeys = Object.keys(browserVersions); + browser = browserVersions[randomChoice(browserKeys)]; + } + + // 系统 + const os = randomChoice(osList); + const osVersion = randomChoice(os.versions); + const platform = randomChoice(os.platforms); + + // 版本号 + const version = generateRealisticVersion(browser); + browser.fullVersion = version; + + // 分离版本号 + const majorVersionPart = version.split('.')[0]; + + let userAgent; + + if (browser.name === 'Chrome' || browser.name === 'Edge') { + if (os.name === 'Windows') { + userAgent = `Mozilla/5.0 (Windows NT ${osVersion}; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version} Safari/537.36`; + if (browser.name === 'Edge') { + userAgent += ` Edg/${majorVersionPart}.0.${getRandomInt(1000, 2000)}.${getRandomInt(10, 200)}`; + } + } else if (os.name === 'Macintosh') { + userAgent = `Mozilla/5.0 (${os.name}; ${osVersion}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version} Safari/537.36`; + if (browser.name === 'Edge') { + userAgent += ` Edg/${majorVersionPart}.0.${getRandomInt(1000, 2000)}.${getRandomInt(10, 200)}`; + } + } else if (os.name === 'Linux') { + userAgent = `Mozilla/5.0 (X11; Linux ${osVersion}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version} Safari/537.36`; + if (browser.name === 'Edge') { + userAgent += ` Edg/${majorVersionPart}.0.${getRandomInt(1000, 2000)}.${getRandomInt(10, 200)}`; + } + } else if (os.name === 'Android') { + userAgent = `Mozilla/5.0 (Linux; Android ${osVersion}; SM-${getRandomString(3, true)}${getRandomInt(10, 99)}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version} Mobile Safari/537.36`; + if (browser.name === 'Edge') { + userAgent += ` EdgA/${majorVersionPart}.0.${getRandomInt(1000, 2000)}.${getRandomInt(10, 200)}`; + } + } + } else if (browser.name === 'Firefox') { + if (os.name === 'Windows') { + userAgent = `Mozilla/5.0 (Windows NT ${osVersion}; Win64; x64; rv:${majorVersionPart}.0) Gecko/20100101 Firefox/${version}`; + } else if (os.name === 'Macintosh') { + userAgent = `Mozilla/5.0 (Macintosh; ${osVersion}; rv:${majorVersionPart}.0) Gecko/20100101 Firefox/${version}`; + } else if (os.name === 'Linux') { + userAgent = `Mozilla/5.0 (X11; Linux ${osVersion}; rv:${majorVersionPart}.0) Gecko/20100101 Firefox/${version}`; + } else if (os.name === 'Android') { + userAgent = `Mozilla/5.0 (Android ${osVersion}; Mobile; rv:${majorVersionPart}.0) Gecko/20100101 Firefox/${version}`; + } + } else if (browser.name === 'Safari') { + if (os.name === 'Macintosh') { + const webkitVersion = (parseInt(majorVersionPart) + 500) + `.${getRandomInt(1, 36)}.${getRandomInt(1, 15)}`; + userAgent = `Mozilla/5.0 (Macintosh; ${osVersion}) AppleWebKit/${webkitVersion} (KHTML, like Gecko) Version/${version} Safari/${webkitVersion}`; + } else if (os.name === 'iOS') { + const webkitVersion = (parseInt(majorVersionPart) + 500) + `.${getRandomInt(1, 36)}.${getRandomInt(1, 15)}`; + const device = randomChance(70) ? 'iPhone' : 'iPad'; + userAgent = `Mozilla/5.0 (${device}; CPU OS ${osVersion.replace(/_/g, '_')} like Mac OS X) AppleWebKit/${webkitVersion} (KHTML, like Gecko) Version/${version} Mobile/15E148 Safari/${webkitVersion}`; + } + } else if (browser.name === 'Opera') { + if (os.name === 'Windows') { + userAgent = `Mozilla/5.0 (Windows NT ${osVersion}; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version} Safari/537.36 OPR/${majorVersionPart}.0.${getRandomInt(2000, 5000)}.${getRandomInt(10, 200)}`; + } else if (os.name === 'Macintosh') { + userAgent = `Mozilla/5.0 (Macintosh; ${osVersion}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version} Safari/537.36 OPR/${majorVersionPart}.0.${getRandomInt(2000, 5000)}.${getRandomInt(10, 200)}`; + } else if (os.name === 'Linux') { + userAgent = `Mozilla/5.0 (X11; Linux ${osVersion}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version} Safari/537.36 OPR/${majorVersionPart}.0.${getRandomInt(2000, 5000)}.${getRandomInt(10, 200)}`; + } + } + + // 如果没有匹配到任何合适的组合,提供一个默认UA + if (!userAgent) { + userAgent = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36`; + } + + return userAgent; +} + +/** + * 随机字符串 + * @param {number} length - 长度 + * @param {boolean} upperOnly - 是否仅大写字母 + * @returns {string} + */ +function getRandomString(length, upperOnly = false) { + const chars = upperOnly + ? 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + let result = ''; + for (let i = 0; i < length; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return result; +} + +/** + * 一致浏览器指纹 + * @param {Object} options - 配置选项 + * @returns {Object} - 指纹数据 + */ +export function generateFingerprint(options = {}) { + const seed = options.seed || createRandomSeed(); + + let browserType = options.browserType || null; + if (browserType && typeof browserType === 'string') { + browserType = browserType.toLowerCase(); + if (!browserVersions[browserType]) { + console.warn(`未支持的浏览器类型: ${browserType},将使用随机浏览器类型`); + browserType = null; + } + } + + if (!browserType) { + const browserKeys = Object.keys(browserVersions); + browserType = browserKeys[Math.floor(Math.random() * browserKeys.length)]; + } + + // 生成用户代理 + const userAgent = options.userAgent || generateRealisticUserAgent(browserType); + + // 选择地区/语言 + const locale = options.locale || randomChoice(Object.keys(localeSettings)); + const localeData = localeSettings[locale]; + + // 计算机规格 + const computerSpec = options.computerSpec || randomChoice(computerSpecs); + + // 选择GPU信息 + const gpuVendor = options.gpuVendor || randomChoice(Object.keys(gpuInfo)); + const gpuRenderer = options.gpuRenderer || randomChoice(gpuInfo[gpuVendor] || ['']); + + // 设备名称 + const deviceNameBase = options.deviceNameBase || randomChoice(deviceNames); + const deviceName = options.deviceName || + (deviceNameBase.endsWith('-') ? + `${deviceNameBase}${generateDeviceNameSuffix()}` : + deviceNameBase); + + let platform = options.platform; + if (!platform) { + if (userAgent.includes('Windows')) { + platform = 'Win32'; + } else if (userAgent.includes('Macintosh') || userAgent.includes('Mac OS')) { + platform = 'MacIntel'; + } else if (userAgent.includes('Linux')) { + platform = 'Linux x86_64'; + } else if (userAgent.includes('Android')) { + platform = 'Android'; + } else if (userAgent.includes('iPhone') || userAgent.includes('iPad')) { + platform = userAgent.includes('iPad') ? 'iPad' : 'iPhone'; + } else { + platform = 'Win32'; // 默认 + } + } + + // 插件列表 + const plugins = options.plugins || (browserPlugins[browserType] || []); + + // 创建 + return { + seed, + userAgent, + browserType, + platform, + osInfo: determineOsInfo(userAgent), + + webRTC: options.webRTC !== undefined ? options.webRTC : false, + timezone: options.timezone || randomChoice(localeData.timeZones || ['UTC']), + geolocation: options.geolocation || 'prompt', + + // 语言和区域设置 + language: options.language || locale, + languages: options.languages || localeData.languages || [locale, 'en-US'], + + // 指纹保护 + canvas: options.canvas || 'noise', + webGL: options.webGL || 'noise', + audioContext: options.audioContext || 'noise', + mediaDevices: options.mediaDevices || 'noise', + + // 硬件信息 + webGLMetadata: { + vendor: `Google Inc. (${gpuVendor})`, + renderer: gpuRenderer, + vendorUnmasked: gpuVendor, + rendererUnmasked: gpuRenderer + }, + + // 系统资源 + cpu: { + cores: Number(options.cpuCores || computerSpec.cores), + architecture: options.cpuArchitecture || 'x86-64' + }, + ram: options.ram || randomChoice(computerSpec.ram), + deviceName: deviceName, + macAddress: options.macAddress || generateRandomMAC(), + + // 其他设置 + doNotTrack: options.doNotTrack !== undefined ? options.doNotTrack : randomChoice([null, '0', '1']), + hardwareAcceleration: options.hardwareAcceleration || 'default', + plugins: plugins, + screenOrientation: options.screenOrientation || 'landscape-primary', + + // 版本信息 + browserVersion: getBrowserVersionFromUA(userAgent), + + // 指纹强度 + noiseLevel: options.noiseLevel || 'medium', // low, medium, high + consistencyLevel: options.consistencyLevel || 'high', // low, medium, high + + touchSupport: options.touchSupport !== undefined + ? options.touchSupport + : (userAgent.includes('Mobile') || userAgent.includes('Android') || platform === 'iPhone' || platform === 'iPad'), + maxTouchPoints: options.maxTouchPoints || (userAgent.includes('Mobile') ? getRandomInt(1, 5) : 0), + pdfViewerEnabled: options.pdfViewerEnabled !== undefined ? options.pdfViewerEnabled : true + }; +} + +/** + * 从用户代理确定操作系统 + * @param {string} userAgent - 用户代理 + * @returns {Object} - 操作系统信息 + */ +function determineOsInfo(userAgent) { + let name, version, archType; + + if (userAgent.includes('Windows')) { + name = 'Windows'; + if (userAgent.includes('Windows NT 10.0')) { + version = '10'; + } else if (userAgent.includes('Windows NT 11.0')) { + version = '11'; + } else { + version = '10'; // 默认Windows 10 + } + archType = userAgent.includes('Win64') || userAgent.includes('x64') ? 'x64' : 'x86'; + } else if (userAgent.includes('Mac OS X') || userAgent.includes('Macintosh')) { + name = 'Mac OS'; + const macOSMatch = userAgent.match(/Mac OS X ([0-9_]+)/) || + userAgent.match(/Macintosh; Intel Mac OS X ([0-9_]+)/); + version = macOSMatch ? macOSMatch[1].replace(/_/g, '.') : '10.15'; + archType = userAgent.includes('Intel') ? 'x64' : 'arm64'; + } else if (userAgent.includes('Linux')) { + name = 'Linux'; + version = userAgent.match(/Linux ([^;)]+)/) ? userAgent.match(/Linux ([^;)]+)/)[1] : 'x86_64'; + archType = userAgent.includes('x86_64') ? 'x64' : 'x86'; + } else if (userAgent.includes('Android')) { + name = 'Android'; + const androidMatch = userAgent.match(/Android ([0-9.]+)/); + version = androidMatch ? androidMatch[1] : '11'; + archType = 'arm64'; + } else if (userAgent.includes('iPhone') || userAgent.includes('iPad')) { + name = 'iOS'; + const iosMatch = userAgent.match(/OS ([0-9_]+)/); + version = iosMatch ? iosMatch[1].replace(/_/g, '.') : '15.0'; + archType = 'arm64'; + } else { + name = 'Unknown'; + version = 'Unknown'; + archType = 'Unknown'; + } + + return {name, version, archType}; +} + +/** + * 从用户代理提取浏览器版本 + * @param {string} userAgent - 用户代理 + * @returns {Object} - 浏览器版本 + */ +function getBrowserVersionFromUA(userAgent) { + let name, version, fullVersion; + + if (userAgent.includes('Firefox/')) { + name = 'Firefox'; + const match = userAgent.match(/Firefox\/([0-9.]+)/); + fullVersion = match ? match[1] : '100.0'; + } else if (userAgent.includes('Edg/')) { + name = 'Edge'; + const match = userAgent.match(/Edg\/([0-9.]+)/); + fullVersion = match ? match[1] : '100.0.0.0'; + } else if (userAgent.includes('OPR/') || userAgent.includes('Opera/')) { + name = 'Opera'; + const match = userAgent.match(/OPR\/([0-9.]+)/) || userAgent.match(/Opera\/([0-9.]+)/); + fullVersion = match ? match[1] : '100.0.0.0'; + } else if (userAgent.includes('Safari/') && !userAgent.includes('Chrome/')) { + name = 'Safari'; + const match = userAgent.match(/Version\/([0-9.]+)/); + fullVersion = match ? match[1] : '15.0'; + } else if (userAgent.includes('Chrome/')) { + name = 'Chrome'; + const match = userAgent.match(/Chrome\/([0-9.]+)/); + fullVersion = match ? match[1] : '100.0.0.0'; + } else { + name = 'Unknown'; + fullVersion = '1.0.0'; + } + + version = fullVersion.split('.')[0]; // 主版本号 + + return {name, version, fullVersion}; +} + +/** + * 指纹应用到浏览器页面 + * @param {Object} page - Puppeteer页面对象 + * @param {Object} fingerprint - 指纹对象 + * @returns {Promise} + */ +export async function applyFingerprint(page, fingerprint) { + try { + // 设置用户代理 + await page.setUserAgent(fingerprint.userAgent); + + // 设置语言和时区 + await page.setExtraHTTPHeaders({ + 'Accept-Language': fingerprint.languages.join(',') + }); + + await page.emulateTimezone(fingerprint.timezone); + + await page.evaluateOnNewDocument((fp) => { + const deepClone = (obj) => { + if (obj === null || typeof obj !== 'object') { + return obj; + } + + if (obj instanceof Date) { + return new Date(obj); + } + + if (obj instanceof RegExp) { + return new RegExp(obj); + } + + if (obj instanceof Array) { + return obj.reduce((arr, item, i) => { + arr[i] = deepClone(item); + return arr; + }, []); + } + + if (obj instanceof Object) { + return Object.keys(obj).reduce((newObj, key) => { + newObj[key] = deepClone(obj[key]); + return newObj; + }, {}); + } + }; + + // 保存原始navigator + const originalNavigator = window.navigator; + const properties = Object.getOwnPropertyDescriptors(window.navigator); + const resultNavigator = {}; + + // 允许的可写属性 + const allowedToWrite = [ + 'userAgent', 'appVersion', 'platform', 'language', 'languages', + 'deviceMemory', 'hardwareConcurrency', 'doNotTrack', 'webdriver', + 'maxTouchPoints' + ]; + + // 适当UA模拟 + const browserInfo = fp.browserVersion; + + for (const key in properties) { + let overrideValue; + + switch (key) { + case 'userAgent': + overrideValue = fp.userAgent; + break; + case 'appVersion': + overrideValue = fp.userAgent.replace('Mozilla/', ''); + break; + case 'platform': + overrideValue = fp.platform; + break; + case 'language': + overrideValue = fp.language; + break; + case 'languages': + overrideValue = [...fp.languages]; + break; + case 'deviceMemory': + overrideValue = fp.ram; + break; + case 'hardwareConcurrency': + overrideValue = Number(fp.cpu.cores); + break; + case 'doNotTrack': + overrideValue = fp.doNotTrack; + break; + case 'webdriver': + overrideValue = false; + break; + case 'maxTouchPoints': + overrideValue = fp.maxTouchPoints || 0; + break; + case 'vendor': + overrideValue = browserInfo.name === 'Chrome' || browserInfo.name === 'Edge' ? 'Google Inc.' : ''; + break; + case 'appName': + overrideValue = 'Netscape'; + break; + case 'appCodeName': + overrideValue = 'Mozilla'; + break; + } + + // 如果有覆盖值 + if (overrideValue !== undefined) { + Object.defineProperty(resultNavigator, key, { + value: overrideValue, + configurable: false, + enumerable: true, + writable: false + }); + } else if (properties[key].configurable) { + // 从原始 navigator 获取 + Object.defineProperty(resultNavigator, key, { + get: function () { + try { + return originalNavigator[key]; + } catch (e) { + return properties[key].value; + } + }, + enumerable: properties[key].enumerable, + configurable: false + }); + } else { + // 不可配置的属性,保持原样 + if (properties[key].writable) { + resultNavigator[key] = originalNavigator[key]; + } else { + Object.defineProperty(resultNavigator, key, { + value: originalNavigator[key], + writable: properties[key].writable, + enumerable: properties[key].enumerable, + configurable: properties[key].configurable + }); + } + } + } + + // 创建代理以拦截任何新添加的属性 + const navigatorProxy = new Proxy(resultNavigator, { + has: (target, key) => key in target || key in originalNavigator, + get: (target, key) => { + if (key in target) { + return target[key]; + } + // 使用原始值 + return originalNavigator[key]; + }, + set: (target, key, value) => { + if (allowedToWrite.includes(key)) { + target[key] = value; + return true; + } + return false; + }, + getOwnPropertyDescriptor: (target, key) => { + return Object.getOwnPropertyDescriptor(target, key) || + Object.getOwnPropertyDescriptor(originalNavigator, key); + }, + defineProperty: (target, key, descriptor) => { + if (allowedToWrite.includes(key)) { + Object.defineProperty(target, key, descriptor); + return true; + } + return false; + } + }); + + // 替换 navigator + Object.defineProperty(window, 'navigator', { + value: navigatorProxy, + writable: false, + configurable: false, + enumerable: true + }); + + if ('userAgentData' in originalNavigator) { + // 创建伪造userAgentData + const brandsList = []; + + if (fp.browserType === 'chrome') { + brandsList.push({brand: "Chromium", version: fp.browserVersion.version}); + brandsList.push({brand: "Google Chrome", version: fp.browserVersion.version}); + brandsList.push({brand: "Not;A=Brand", version: "99.0.0.0"}); + } else if (fp.browserType === 'edge') { + brandsList.push({brand: "Microsoft Edge", version: fp.browserVersion.version}); + brandsList.push({brand: "Chromium", version: fp.browserVersion.version}); + brandsList.push({brand: "Not;A=Brand", version: "99.0.0.0"}); + } else if (fp.browserType === 'firefox') { + brandsList.push({brand: "Firefox", version: fp.browserVersion.version}); + brandsList.push({brand: "Not;A=Brand", version: "99.0.0.0"}); + } + + const uaDataValues = { + brands: brandsList, + mobile: fp.userAgent.includes('Mobile'), + platform: fp.osInfo.name + }; + + const platformVersion = [fp.osInfo.version, 0, 0, 0]; + + const uaData = { + brands: brandsList, + mobile: fp.userAgent.includes('Mobile'), + platform: fp.osInfo.name, + architecture: fp.cpu.architecture, + bitness: "64", + model: "", + platformVersion: platformVersion.join('.'), + getHighEntropyValues: function (hints) { + return new Promise(resolve => { + const result = {}; + + if (hints.includes('architecture')) { + result.architecture = fp.cpu.architecture; + } + if (hints.includes('bitness')) { + result.bitness = "64"; + } + if (hints.includes('brands')) { + result.brands = deepClone(brandsList); + } + if (hints.includes('mobile')) { + result.mobile = fp.userAgent.includes('Mobile'); + } + if (hints.includes('model')) { + result.model = ""; + } + if (hints.includes('platform')) { + result.platform = fp.osInfo.name; + } + if (hints.includes('platformVersion')) { + result.platformVersion = platformVersion.join('.'); + } + if (hints.includes('uaFullVersion')) { + result.uaFullVersion = fp.browserVersion.fullVersion; + } + if (hints.includes('fullVersionList')) { + result.fullVersionList = deepClone(brandsList); + } + + resolve(result); + }); + }, + toJSON: function () { + return { + brands: this.brands, + mobile: this.mobile, + platform: this.platform + }; + } + }; + + // 添加到 navigator + Object.defineProperty(navigatorProxy, 'userAgentData', { + value: uaData, + writable: false, + enumerable: true, + configurable: false + }); + } + + if (fp.webRTC === false) { + // 阻止 WebRTC 泄露 + const origRTCPeerConnection = window.RTCPeerConnection || + window.webkitRTCPeerConnection || + window.mozRTCPeerConnection; + + if (origRTCPeerConnection) { + class CustomRTCPeerConnection extends origRTCPeerConnection { + constructor(configuration) { + // 过滤掉ICE服务器 + if (configuration && configuration.iceServers) { + configuration = { + ...configuration, + iceServers: [] + }; + } + super(configuration); + } + + createOffer(...args) { + // 拦截createOffer + return new Promise((resolve, reject) => { + super.createOffer(...args) + .then(offer => { + if (offer && offer.sdp) { + offer.sdp = offer.sdp.replace(/IP4 \d+\.\d+\.\d+\.\d+/g, 'IP4 0.0.0.0'); + } + resolve(offer); + }) + .catch(reject); + }); + } + + createAnswer(...args) { + return new Promise((resolve, reject) => { + super.createAnswer(...args) + .then(answer => { + if (answer && answer.sdp) { + answer.sdp = answer.sdp.replace(/IP4 \d+\.\d+\.\d+\.\d+/g, 'IP4 0.0.0.0'); + } + resolve(answer); + }) + .catch(reject); + }); + } + } + + window.RTCPeerConnection = CustomRTCPeerConnection; + window.webkitRTCPeerConnection = CustomRTCPeerConnection; + window.mozRTCPeerConnection = CustomRTCPeerConnection; + } + + // 禁用媒体设备 + const safeMediaDevices = { + enumerateDevices: function () { + return Promise.resolve([]); + }, + getSupportedConstraints: function () { + return {}; + }, + getUserMedia: function () { + return Promise.reject(new Error('Permission denied')); + }, + getDisplayMedia: function () { + return Promise.reject(new Error('Permission denied')); + } + }; + + if (originalNavigator.mediaDevices) { + Object.defineProperty(navigatorProxy, 'mediaDevices', { + value: safeMediaDevices, + writable: false, + enumerable: true, + configurable: false + }); + } + } + + if (fp.canvas === 'noise' || fp.canvas === 'block') { + const originalGetContext = HTMLCanvasElement.prototype.getContext; + HTMLCanvasElement.prototype.getContext = function (type, attributes) { + if (fp.canvas === 'block' && (type === '2d' || type.includes('webgl'))) { + return null; + } + + const context = originalGetContext.call(this, type, attributes); + + if (!context) return null; + + if (type === '2d') { + // 2D Canvas指纹保护 + const origGetImageData = context.getImageData; + const origPutImageData = context.putImageData; + const origToDataURL = this.toDataURL; + const origToBlob = this.toBlob; + + // 添加微小噪声的函数 + const addNoise = function (data) { + const noise = Math.floor(Math.random() * 10) / 255; + for (let i = 0; i < data.data.length; i += 4) { + if (data.data[i + 3] > 0) { + if (Math.random() > 0.5) { + data.data[i + 3] -= noise; + } else { + data.data[i + 3] += noise; + } + } + } + return data; + }; + + context.getImageData = function (sx, sy, sw, sh) { + const imageData = origGetImageData.call(this, sx, sy, sw, sh); + return addNoise(imageData); + }; + + this.toDataURL = function (...args) { + const dataURL = origToDataURL.apply(this, args); + if (!dataURL) return dataURL; + + // URL添加微小噪声 (改变最后几个字符) + const lastCommaIndex = dataURL.lastIndexOf(','); + if (lastCommaIndex !== -1) { + const prefix = dataURL.substring(0, lastCommaIndex + 1); + const data = dataURL.substring(lastCommaIndex + 1); + const noisyChar = String.fromCharCode( + data.charCodeAt(data.length - 2) + Math.round(Math.random() * 2 - 1) + ); + return prefix + data.substring(0, data.length - 2) + noisyChar + data.substring(data.length - 1); + } + return dataURL; + }; + + this.toBlob = function (callback, ...args) { + origToBlob.call(this, (blob) => { + if (!blob) { + callback(blob); + return; + } + + const reader = new FileReader(); + reader.readAsDataURL(blob); + reader.onloadend = function () { + // 修改dataURL + const dataURL = reader.result; + const lastCommaIndex = dataURL.lastIndexOf(','); + if (lastCommaIndex !== -1) { + const prefix = dataURL.substring(0, lastCommaIndex + 1); + const data = dataURL.substring(lastCommaIndex + 1); + const noisyChar = String.fromCharCode( + data.charCodeAt(data.length - 2) + Math.round(Math.random() * 2 - 1) + ); + const newDataURL = prefix + data.substring(0, data.length - 2) + + noisyChar + data.substring(data.length - 1); + + const byteString = atob(newDataURL.split(',')[1]); + const mimeString = newDataURL.split(',')[0].split(':')[1].split(';')[0]; + const ab = new ArrayBuffer(byteString.length); + const ia = new Uint8Array(ab); + + for (let i = 0; i < byteString.length; i++) { + ia[i] = byteString.charCodeAt(i); + } + + callback(new Blob([ab], {type: mimeString})); + } + }; + + callback(blob); + }, ...args); + }; + } else if (type.includes('webgl') || type.includes('experimental-webgl')) { + // WebGL Canvas指纹保护 + const origGetParameter = context.getParameter; + + context.getParameter = function (parameter) { + // UNMASKED_VENDOR_WEBGL + if (parameter === 37445) { + return fp.webGLMetadata.vendorUnmasked; + } + // UNMASKED_RENDERER_WEBGL + if (parameter === 37446) { + return fp.webGLMetadata.rendererUnmasked; + } + + return origGetParameter.call(this, parameter); + }; + } + + return context; + }; + } + + if (fp.audioContext === 'noise') { + const AudioContext = window.AudioContext || window.webkitAudioContext; + + if (AudioContext) { + const origAudioContext = AudioContext; + + // 创建带噪声AudioContext + window.AudioContext = window.webkitAudioContext = function () { + const ctx = new origAudioContext(); + + const origGetChannelData = ctx.createAnalyser().getFloatFrequencyData; + if (origGetChannelData) { + ctx.createAnalyser().getFloatFrequencyData = function (array) { + origGetChannelData.call(this, array); + // 添加微小噪声 + for (let i = 0; i < array.length; i += 50) { + if (array[i]) { + array[i] += (Math.random() * 0.0001) - 0.00005; + } + } + }; + } + + return ctx; + }; + } + } + + // 创建自定义插件列表 + const mimeTypeArray = []; + const pluginArray = []; + + if (fp.plugins && Array.isArray(fp.plugins)) { + fp.plugins.forEach((plugin, pluginIndex) => { + if (!plugin || !plugin.name) return; + + // 创建MimeTypes + const mimeTypes = {}; + let mimeTypeCount = 0; + + if (plugin.mimeTypes && Array.isArray(plugin.mimeTypes)) { + plugin.mimeTypes.forEach((type, index) => { + const mimeType = { + type, + description: plugin.description || '', + suffixes: plugin.name.toLowerCase().replace(/[^a-z0-9]/g, ''), + enabledPlugin: null + }; + + mimeTypes[mimeTypeCount] = mimeType; + mimeTypes[type] = mimeType; + mimeTypeCount++; + + mimeTypeArray.push(mimeType); + }); + } + + // 创建插件对象 + const pluginObj = { + name: plugin.name, + filename: plugin.filename || '', + description: plugin.description || '', + length: mimeTypeCount, + item: function (index) { + return this[index]; + }, + namedItem: function (name) { + return this[name]; + } + }; + + // 扩展插件对象 + for (let i = 0; i < mimeTypeCount; i++) { + pluginObj[i] = mimeTypes[i]; + } + + // mime设置enabledPlugin + Object.values(mimeTypes).forEach(mime => { + mime.enabledPlugin = pluginObj; + }); + + pluginArray.push(pluginObj); + }); + + // 自定义的navigator.plugins + const pluginsObj = { + length: pluginArray.length, + item: function (index) { + return this[index]; + }, + namedItem: function (name) { + return this[name] || null; + }, + refresh: function () { + } + }; + + for (let i = 0; i < pluginArray.length; i++) { + const plugin = pluginArray[i]; + pluginsObj[i] = plugin; + pluginsObj[plugin.name] = plugin; + } + + // 自定义navigator.mimeTypes + const mimeTypesObj = { + length: mimeTypeArray.length, + item: function (index) { + return this[index]; + }, + namedItem: function (name) { + return this[name] || null; + } + }; + + for (let i = 0; i < mimeTypeArray.length; i++) { + const mimeType = mimeTypeArray[i]; + mimeTypesObj[i] = mimeType; + mimeTypesObj[mimeType.type] = mimeType; + } + + // plugins和mimeTypes附加到navigator + Object.defineProperty(navigatorProxy, 'plugins', { + value: pluginsObj, + writable: false, + enumerable: true, + configurable: false + }); + + Object.defineProperty(navigatorProxy, 'mimeTypes', { + value: mimeTypesObj, + writable: false, + enumerable: true, + configurable: false + }); + } + + // 隐藏自动化 + Object.defineProperty(navigatorProxy, 'webdriver', { + get: () => false, + enumerable: true, + configurable: false + }); + + // 修复Chrome特征 + if (window.chrome) { + const chromeObj = {}; + const originalChrome = window.chrome; + + // 复制原始chrome + for (const key in originalChrome) { + try { + if (key === 'runtime' && originalChrome.runtime) { + // 处理chrome.runtime + const runtimeObj = {}; + for (const rKey in originalChrome.runtime) { + try { + runtimeObj[rKey] = originalChrome.runtime[rKey]; + } catch (e) { + } + } + chromeObj.runtime = runtimeObj; + } else { + chromeObj[key] = originalChrome[key]; + } + } catch (e) { + } + } + + // 创建chrome.app + chromeObj.app = { + isInstalled: false, + InstallState: { + DISABLED: 'disabled', + INSTALLED: 'installed', + NOT_INSTALLED: 'not_installed' + }, + RunningState: { + CANNOT_RUN: 'cannot_run', + READY_TO_RUN: 'ready_to_run', + RUNNING: 'running' + } + }; + + // 替换chrome + Object.defineProperty(window, 'chrome', { + value: chromeObj, + writable: false, + enumerable: true, + configurable: false + }); + } + + // 添加PDF查看器 + if (fp.pdfViewerEnabled) { + for (const mimeType of ['application/pdf', 'text/pdf']) { + const pdfMime = { + type: mimeType, + suffixes: 'pdf', + description: 'Portable Document Format' + }; + + if (navigatorProxy.mimeTypes) { + const mimeTypesObj = navigatorProxy.mimeTypes; + const index = mimeTypesObj.length; + pdfMime.enabledPlugin = navigatorProxy.plugins[0]; + mimeTypesObj[index] = pdfMime; + mimeTypesObj[mimeType] = pdfMime; + mimeTypesObj.length++; + } + } + } + + // 噪声 + if (fp.noiseLevel && (fp.noiseLevel === 'medium' || fp.noiseLevel === 'high')) { + const addNoise = (value, scale) => { + if (typeof value !== 'number') return value; + const noise = (Math.random() - 0.5) * scale; + return value + noise; + }; + } + + // 清理webdriver + delete window.__nightmare; + delete window.cdc_adoQpoasnfa76pfcZLmcfl_Array; + delete window.cdc_adoQpoasnfa76pfcZLmcfl_Promise; + delete window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol; + + const originalToString = Function.prototype.toString; + Function.prototype.toString = function () { + if (this === Function.prototype.toString) { + return originalToString.call(this); + } + + const fnName = this.name; + if (fnName === 'getParameter' || fnName === 'getChannelData' || fnName === 'toDataURL' || + fnName === 'toBlob' || fnName === 'getImageData') { + return "function " + fnName + "() { [native code] }"; + } + + return originalToString.call(this); + }; + + // 创建隐藏的指纹验证 + window._fingerprintId = fp.seed; + + const event = new CustomEvent('fingerprintApplied', { + detail: {success: true, fingerprintId: fp.seed} + }); + document.dispatchEvent(event); + + }, fingerprint); + + const debugInfo = await page.evaluate((fp) => { + return { + hardwareConcurrency: navigator.hardwareConcurrency, + hardwareConcurrencyType: typeof navigator.hardwareConcurrency, + expectedCores: fp.cpu.cores, + expectedCoresType: typeof fp.cpu.cores + }; + }, fingerprint); + + // console.log('指纹应用调试信息:', debugInfo); + console.log(`已应用自定义浏览器指纹: ${fingerprint.userAgent}`); + return true; + } catch (error) { + console.error('应用浏览器指纹时出错:', error); + return false; + } +} + +/** + * 为特定浏览器创建并应用指纹 + * @param {Object} page - Puppeteer + * @param {string|Object} options - 浏览器或完整配置 + * @returns {Promise} - 应用指纹 + */ +export async function setupBrowserFingerprint(page, options = {}) { + try { + if (typeof options === 'string') { + options = {browserType: options}; + } + + // 生成完整的指纹 + const fingerprint = generateFingerprint(options); + + // 应用指纹 + const success = await applyFingerprint(page, fingerprint); + + // 验证指纹应用 + if (success) { + try { + const appliedUserAgent = await page.evaluate(() => navigator.userAgent); + if (appliedUserAgent !== fingerprint.userAgent) { + console.warn('用户代理未正确应用:', { + expected: fingerprint.userAgent, + applied: appliedUserAgent + }); + } + + // 验证CPU核心数 + // const hardwareConcurrency = await page.evaluate(() => navigator.hardwareConcurrency); + // if (Number(hardwareConcurrency) !== Number(fingerprint.cpu.cores)) { + // console.warn('CPU核心数未正确应用:', { + // expected: fingerprint.cpu.cores, + // applied: hardwareConcurrency, + // expectedType: typeof fingerprint.cpu.cores, + // appliedType: typeof hardwareConcurrency + // }); + // } + + // 验证WebGL + if (fingerprint.webGL !== 'block') { + const webglVendor = await page.evaluate(() => { + try { + const canvas = document.createElement('canvas'); + const gl = canvas.getContext('webgl'); + return gl ? gl.getParameter(gl.getParameter(37445)) : null; + } catch (e) { + return null; + } + }); + + if (webglVendor && !webglVendor.includes(fingerprint.webGLMetadata.vendorUnmasked)) { + console.warn('WebGL供应商信息未正确应用'); + } + } + + console.log('指纹验证成功'); + } catch (verifyError) { + console.warn('指纹验证时出错:', verifyError); + } + } + + return fingerprint; + } catch (error) { + console.error('设置浏览器指纹时出错:', error); + throw error; + } +} + +/** + * 验证页面指纹是否正确应用 + * @param {Object} page - Puppeteer + * @param {Object} fingerprint - 指定指纹 + * @returns {Promise} + */ +export async function verifyFingerprint(page, fingerprint) { + try { + const results = await page.evaluate((fp) => { + const checks = { + userAgent: navigator.userAgent === fp.userAgent, + platform: navigator.platform === fp.platform, + hardwareConcurrency: Number(navigator.hardwareConcurrency) === Number(fp.cpu.cores), + language: navigator.language === fp.language, + deviceMemory: navigator.deviceMemory === fp.ram, + doNotTrack: navigator.doNotTrack === fp.doNotTrack + }; + + return { + success: Object.values(checks).every(v => v), + details: checks + }; + }, fingerprint); + + console.log('指纹验证结果:', results); + return results.success; + } catch (error) { + console.error('验证指纹时出错:', error); + return false; + } +} + +/** + * 获取随机指纹 + * @param {string} browserFamily ('chrome', 'edge', 'firefox', 'safari') + * @returns {Object} - 指纹 + */ +export function getRealisticFingerprint(browserFamily = 'chrome') { + // 标准化浏览器 + browserFamily = browserFamily.toLowerCase(); + + // 选择合适操作系统 + let osFamily; + const browserType = browserFamily; + + if (browserFamily === 'safari') { + osFamily = Math.random() < 0.8 ? 'Macintosh' : 'iOS'; + } else { + const osDistribution = { + 'chrome': {'Windows': 0.65, 'Macintosh': 0.25, 'Linux': 0.08, 'Android': 0.02}, + 'edge': {'Windows': 0.80, 'Macintosh': 0.18, 'Linux': 0.02}, + 'firefox': {'Windows': 0.55, 'Macintosh': 0.25, 'Linux': 0.20} + }; + + const distribution = osDistribution[browserFamily] || {'Windows': 0.7, 'Macintosh': 0.25, 'Linux': 0.05}; + const rand = Math.random(); + let cumulative = 0; + + for (const [os, probability] of Object.entries(distribution)) { + cumulative += probability; + if (rand <= cumulative) { + osFamily = os; + break; + } + } + } + + // 选择规格 + let computerSpec; + if (osFamily === 'Windows' || osFamily === 'Macintosh') { + computerSpec = { + cores: [4, 6, 8, 12, 16][Math.floor(Math.random() * 5)], + ram: [8, 16, 32, 64][Math.floor(Math.random() * 4)] + }; + } else if (osFamily === 'Linux') { + computerSpec = { + cores: [4, 8, 16, 24, 32][Math.floor(Math.random() * 5)], + ram: [8, 16, 32, 64, 128][Math.floor(Math.random() * 5)] + }; + } else { + computerSpec = { + cores: [2, 4, 6, 8][Math.floor(Math.random() * 4)], + ram: [4, 6, 8, 12][Math.floor(Math.random() * 4)] + }; + } + + // 语言 + const commonLanguages = ['en-US', 'en-GB', 'zh-CN', 'es-ES', 'fr-FR', 'de-DE', 'ja-JP', 'ru-RU']; + const locale = randomChoice(commonLanguages); + + return { + browserType, + userAgent: null, + osFamily, + computerSpec, + locale, + timezone: null, + webRTC: false, + noiseLevel: 'medium', + consistencyLevel: 'high' + }; +} diff --git a/you_providers/cookieUtils.mjs b/you_providers/cookieUtils.mjs new file mode 100644 index 0000000..73c759b --- /dev/null +++ b/you_providers/cookieUtils.mjs @@ -0,0 +1,242 @@ +import * as docx from "docx"; +import cookie from "cookie"; +import fs from "fs"; +import {execSync} from "child_process"; + +function getGitRevision() { + // get git revision and branch + try { + const revision = execSync("git rev-parse --short HEAD", {stdio: "pipe"}).toString().trim(); + const branch = execSync("git rev-parse --abbrev-ref HEAD", {stdio: "pipe"}).toString().trim(); + return {revision, branch}; + } catch (e) { + return {revision: "unknown", branch: "unknown"}; + } +} + +// 创建目录 +function createDirectoryIfNotExists(dirPath) { + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, {recursive: true}); + } +} + +function extractCookie(cookies) { + let jwtSession = null; + let jwtToken = null; + let ds = null; + let dsr = null; + let you_subscription = null; + let youpro_subscription = null; + + cookies = cookie.parse(cookies); + if (cookies["stytch_session"]) jwtSession = cookies["stytch_session"]; + if (cookies["stytch_session_jwt"]) jwtToken = cookies["stytch_session_jwt"]; + if (cookies["DS"]) ds = cookies["DS"]; + if (cookies["DSR"]) dsr = cookies["DSR"]; + if (cookies["you_subscription"]) you_subscription = cookies["you_subscription"]; + if (cookies["youpro_subscription"]) youpro_subscription = cookies["youpro_subscription"]; + + return {jwtSession, jwtToken, ds, dsr, you_subscription, youpro_subscription}; +} + +function getSessionCookie(jwtSession, jwtToken, ds, dsr, you_subscription, youpro_subscription) { + let sessionCookie = []; + + // 处理旧版 cookie + if (jwtSession && jwtToken) { + sessionCookie = [ + { + name: "stytch_session", + value: jwtSession, + domain: "you.com", + path: "/", + expires: 1800000000, + httpOnly: false, + secure: true, + sameSite: "Lax", + }, + { + name: "ydc_stytch_session", + value: jwtSession, + domain: "you.com", + path: "/", + expires: 1800000000, + httpOnly: true, + secure: true, + sameSite: "Lax", + }, + { + name: "stytch_session_jwt", + value: jwtToken, + domain: "you.com", + path: "/", + expires: 1800000000, + httpOnly: false, + secure: true, + sameSite: "Lax", + }, + { + name: "ydc_stytch_session_jwt", + value: jwtToken, + domain: "you.com", + path: "/", + expires: 1800000000, + httpOnly: true, + secure: true, + sameSite: "Lax", + } + ]; + } + + // 处理新版 cookie + if (ds) { + sessionCookie.push({ + name: "DS", + value: ds, + domain: "you.com", + path: "/", + expires: 1800000000, + httpOnly: false, + secure: true, + sameSite: "Lax", + }); + } + if (dsr) { + sessionCookie.push({ + name: "DSR", + value: dsr, + domain: "you.com", + path: "/", + expires: 1800000000, + httpOnly: false, + secure: true, + sameSite: "Lax", + }); + } + + if (you_subscription) { + sessionCookie.push({ + name: "you_subscription", + value: you_subscription, + domain: "you.com", + path: "/", + expires: 1800000000, + httpOnly: false, + secure: true, + sameSite: "Lax", + }); + } + + if (youpro_subscription) { + sessionCookie.push({ + name: "youpro_subscription", + value: youpro_subscription, + domain: "you.com", + path: "/", + expires: 1800000000, + httpOnly: false, + secure: true, + sameSite: "Lax", + }); + } + + // 添加隐身模式 cookie(如果启用) + if (process.env.INCOGNITO_MODE === "true") { + sessionCookie.push({ + name: "incognito", + value: "true", + domain: "you.com", + path: "/", + expires: 1800000000, + secure: true, + }); + } + return sessionCookie; +} + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +function createDocx(content) { + let paragraphs = []; + content.split("\n").forEach((line) => { + paragraphs.push( + new docx.Paragraph({ + children: [new docx.TextRun(line)], + }) + ); + }); + let doc = new docx.Document({ + sections: [ + { + properties: {}, + children: paragraphs, + }, + ], + }); + return docx.Packer.toBuffer(doc).then((buffer) => buffer); +} + +// eventStream util +function createEvent(event, data) { + // if data is object, stringify it + if (typeof data === "object") { + data = JSON.stringify(data); + } + return `event: ${event}\ndata: ${data}\n\n`; +} + +function extractPerplexityCookie(cookieString) { + const cookies = cookie.parse(cookieString); + return { + sessionToken: cookies['__Secure-next-auth.session-token'], + isIncognito: cookies['pplx.is-incognito'] === 'true' + }; +} + +function getPerplexitySessionCookie(extractedCookie) { + let sessionCookie = []; + + if (extractedCookie.sessionToken) { + sessionCookie.push({ + name: "__Secure-next-auth.session-token", + value: extractedCookie.sessionToken, + domain: "www.perplexity.ai", + path: "/", + expires: 1800000000, + httpOnly: true, + secure: true, + sameSite: "Lax", + }); + } + + // 添加无痕模式 cookie (如果启用) + if (process.env.INCOGNITO_MODE === "true") { + sessionCookie.push({ + name: "pplx.is-incognito", + value: "true", + domain: "www.perplexity.ai", + path: "/", + expires: 1800000000, + httpOnly: false, + secure: true, + sameSite: "Lax", + }); + } + + return sessionCookie; +} + +export { + createEvent, + createDirectoryIfNotExists, + sleep, + extractCookie, + getSessionCookie, + createDocx, + getGitRevision, + extractPerplexityCookie, + getPerplexitySessionCookie +}; \ No newline at end of file diff --git a/you_providers/edgeLauncher.mjs b/you_providers/edgeLauncher.mjs new file mode 100644 index 0000000..b39e9a9 --- /dev/null +++ b/you_providers/edgeLauncher.mjs @@ -0,0 +1,153 @@ +import {exec, execSync} from 'child_process'; +import {promisify} from 'util'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import {fileURLToPath} from 'url'; +import {setupBrowserFingerprint} from './browserFingerprint.mjs'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const execPromise = promisify(exec); + +/** + * @param {string} userDataDir - 用户数据目录 + * @param {string} edgePath - Edge浏览器路径 + * @param {number} debugPort - 调试端口 + * @returns {Promise} - browser和page + */ +export async function launchEdgeBrowser(userDataDir, edgePath, debugPort = 9222) { + if (!fs.existsSync(userDataDir)) { + fs.mkdirSync(userDataDir, {recursive: true}); + } + + // 关闭可能的Edge进程 + try { + if (os.platform() === 'win32') { + await execPromise('taskkill /f /im msedge.exe').catch(() => { + }); + } else if (os.platform() === 'darwin') { + // macOS + await execPromise('pkill -f "Microsoft Edge"').catch(() => { + }); + } else { + // Linux + await execPromise('pkill -f "microsoft-edge"').catch(() => { + }); + } + } catch (e) { + } + + const remoteDebuggingPort = debugPort; + let edgeProcess; + + const args = [ + `--remote-debugging-port=${remoteDebuggingPort}`, + `--user-data-dir="${userDataDir}"`, + '--no-first-run', + '--no-default-browser-check', + '--disable-popup-blocking', + '--disable-infobars', + '--disable-translate', + '--disable-sync', + '--window-size=1280,850', + '--force-device-scale-factor=1', + 'about:blank' // 打开空白页 + ]; + + try { + // 启动Edge浏览器 + const cmdArgs = args.join(' '); + const cmd = `"${edgePath}" ${cmdArgs}`; + edgeProcess = exec(cmd); + + console.log(`等待Edge浏览器启动...`); + await new Promise(resolve => setTimeout(resolve, 3000)); + + const puppeteer = await import('puppeteer-core'); + + // 连接到浏览器 + const browser = await puppeteer.connect({ + browserURL: `http://127.0.0.1:${remoteDebuggingPort}`, + defaultViewport: {width: 1280, height: 850} + }); + + // 获取第一个页面 + const pages = await browser.pages(); + let page = pages[0]; + if (!page) { + page = await browser.newPage(); + } + + const originalUserAgent = await page.evaluate(() => navigator.userAgent); + // console.log(`Edge浏览器原始用户代理: ${originalUserAgent}`); + + // 应用随机指纹 + const fingerprint = await setupBrowserFingerprint(page, 'edge'); + + // 验证指纹是否成功应用 + const newUserAgent = await page.evaluate(() => navigator.userAgent); + // console.log(`Edge浏览器应用指纹后用户代理: ${newUserAgent}`); + + if (!newUserAgent.includes('Edg')) { + console.warn(`警告: 浏览器可能不是Edge。`); + } + + return { + browser, + page, + process: edgeProcess, + fingerprint: fingerprint + }; + } catch (error) { + console.error(`启动Edge浏览器失败:`, error); + + if (edgeProcess) { + try { + edgeProcess.kill(); + } catch (e) { + } + } + + throw error; + } +} + +/** + * 查找Edge浏览器路径 + * @returns {string|null} + */ +export function findEdgePath() { + const platform = os.platform(); + + if (platform === 'win32') { + const commonPaths = [ + `${process.env['ProgramFiles(x86)']}\\Microsoft\\Edge\\Application\\msedge.exe`, + `${process.env.ProgramFiles}\\Microsoft\\Edge\\Application\\msedge.exe`, + `${process.env.LOCALAPPDATA}\\Microsoft\\Edge\\Application\\msedge.exe`, + `C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe`, + `C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe` + ]; + + for (const path of commonPaths) { + if (fs.existsSync(path)) { + return path; + } + } + } else if (platform === 'darwin') { + const macPath = '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge'; + if (fs.existsSync(macPath)) { + return macPath; + } + } else { + // Linux + try { + const {stdout} = execSync('which microsoft-edge'); + if (stdout && stdout.trim()) { + return stdout.trim(); + } + } catch (e) { + } + } + return null; +} \ No newline at end of file