// 反检测:为每个注册账号合成一套「内部自洽」的浏览器/设备指纹 + 随机姓名 + 成年生日。 // 目的:以前所有账号都写死同一 UA(Chrome/145 Windows)、同一屏幕/语言、姓名只在 6×6 里选、 // 年龄挤在 1995~2002,风控极易把这些注册归成同一批机器人。这里让「每账号一套、全程一致、 // 账号间不同」——UA / sec-ch-ua / platform / screen / navigator 全部彼此对齐(否则 sec-ch-ua // 与 UA 不符本身就是强特征)。 import crypto from 'node:crypto'; function pick(arr) { return arr[crypto.randomInt(0, arr.length)]; } // 每个 profile 固定「Chrome 大版本 ↔ 真实的 sec-ch-ua 品牌串」配对:sec-ch-ua 的 GREASE 品牌 // ("Not?A_Brand" 之类)与版本号是浏览器构建时生成的,随便拼会露馅,所以直接采样真实组合。 const CHROME_PROFILES = [ { major: 140, secChUa: '"Chromium";v="140", "Not=A?Brand";v="24", "Google Chrome";v="140"' }, { major: 141, secChUa: '"Google Chrome";v="141", "Not?A_Brand";v="8", "Chromium";v="141"' }, { major: 142, secChUa: '"Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"' }, { major: 143, secChUa: '"Not;A=Brand";v="99", "Google Chrome";v="143", "Chromium";v="143"' }, { major: 144, secChUa: '"Google Chrome";v="144", "Chromium";v="144", "Not.A/Brand";v="24"' }, { major: 145, secChUa: '"Chromium";v="145", "Not)A;Brand";v="8", "Google Chrome";v="145"' }, { major: 138, secChUa: '"Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138"' }, { major: 139, secChUa: '"Chromium";v="139", "Not_A Brand";v="24", "Google Chrome";v="139"' }, ]; // OS 决定 UA 平台段、navigator.platform、sec-ch-ua-platform 三者必须一致。 const OS_PROFILES = [ { uaToken: 'Windows NT 10.0; Win64; x64', platform: 'Win32', chPlatform: '"Windows"' }, { uaToken: 'Macintosh; Intel Mac OS X 10_15_7', platform: 'MacIntel', chPlatform: '"macOS"' }, ]; // 常见桌面分辨率(宽x高);taskbar/书签栏占用后 availHeight 略小于 height,贴近真机。 const SCREENS = [ { width: 1920, height: 1080 }, { width: 2560, height: 1440 }, { width: 1536, height: 864 }, { width: 1440, height: 900 }, { width: 1366, height: 768 }, { width: 1680, height: 1050 }, ]; // 英文母语区:语言/locale 保持在英语圈内,避免 UA 是美式 Chrome 却报 zh-CN 这种矛盾。 const LOCALES = [ { language: 'en-US', accept: 'en-US,en;q=0.9', tz: 'America/New_York' }, { language: 'en-US', accept: 'en-US,en;q=0.9', tz: 'America/Chicago' }, { language: 'en-US', accept: 'en-US,en;q=0.9', tz: 'America/Los_Angeles' }, { language: 'en-GB', accept: 'en-GB,en;q=0.9', tz: 'Europe/London' }, { language: 'en-CA', accept: 'en-CA,en;q=0.9', tz: 'America/Toronto' }, ]; // 生成一套彼此自洽的指纹。同一账号整条注册流程复用同一个对象;不同账号各自独立随机。 export function generateFingerprint() { const chrome = pick(CHROME_PROFILES); const osp = pick(OS_PROFILES); const screen = pick(SCREENS); const loc = pick(LOCALES); const hardwareConcurrency = pick([4, 8, 12, 16]); const deviceMemory = pick([8, 16]); // Chrome 只暴露 8/16 这类粗粒度值 const userAgent = `Mozilla/5.0 (${osp.uaToken}) AppleWebKit/537.36 (KHTML, like Gecko) ` + `Chrome/${chrome.major}.0.0.0 Safari/537.36`; return { userAgent, secChUa: chrome.secChUa, secChUaMobile: '?0', // 桌面固定 ?0,与 UA 无移动标记一致 secChUaPlatform: osp.chPlatform, platform: osp.platform, language: loc.language, acceptLanguage: loc.accept, languages: `${loc.language},${loc.language.split('-')[0]}`, timezone: loc.tz, screen: { width: screen.width, height: screen.height, // availHeight 减去任务栏/系统栏的典型高度,availWidth 通常等于 width。 availWidth: screen.width, availHeight: screen.height - 40, colorDepth: 24, pixelDepth: 24, }, screenStr: `${screen.width}x${screen.height}`, hardwareConcurrency, deviceMemory, }; } // 扩充姓名池:各数十个真实常见英文名,随机组合,避免总是那几对被风控聚类。 const FIRST_NAMES = [ 'James', 'Michael', 'Robert', 'John', 'David', 'William', 'Richard', 'Joseph', 'Thomas', 'Christopher', 'Daniel', 'Matthew', 'Anthony', 'Andrew', 'Joshua', 'Ryan', 'Brandon', 'Justin', 'Benjamin', 'Samuel', 'Nathan', 'Aaron', 'Adam', 'Mary', 'Patricia', 'Jennifer', 'Linda', 'Elizabeth', 'Barbara', 'Susan', 'Jessica', 'Sarah', 'Karen', 'Emily', 'Emma', 'Olivia', 'Sophia', 'Ava', 'Isabella', 'Mia', 'Charlotte', 'Amelia', 'Hannah', 'Grace', 'Rachel', 'Laura', 'Megan', 'Lauren', 'Victoria', ]; const LAST_NAMES = [ 'Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller', 'Davis', 'Rodriguez', 'Martinez', 'Hernandez', 'Lopez', 'Gonzalez', 'Wilson', 'Anderson', 'Thomas', 'Taylor', 'Moore', 'Jackson', 'Martin', 'Lee', 'Perez', 'Thompson', 'White', 'Harris', 'Sanchez', 'Clark', 'Ramirez', 'Lewis', 'Robinson', 'Walker', 'Young', 'Allen', 'King', 'Wright', 'Scott', 'Torres', 'Hill', 'Green', 'Adams', 'Baker', 'Nelson', 'Carter', 'Mitchell', 'Roberts', 'Turner', 'Phillips', 'Campbell', 'Parker', 'Evans', ]; export function randomFullName() { return `${pick(FIRST_NAMES)} ${pick(LAST_NAMES)}`; } // 成年生日:约 18~48 岁均匀取样,月/日在安全范围(1~28)避免非法日期,格式 YYYY-MM-DD 与旧逻辑一致。 export function randomAdultBirthdate() { const nowYear = new Date().getUTCFullYear(); const age = 18 + crypto.randomInt(0, 31); // 18..48 const year = nowYear - age; const month = 1 + crypto.randomInt(0, 12); const day = 1 + crypto.randomInt(0, 28); return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`; }