// ==UserScript== // @name 防 Claude 检测(模拟新加坡) // @namespace http://tampermonkey.net/ // @version 1.0 // @description 伪装新加坡浏览器:隐藏中文字体 + 伪造区域 + 指纹噪声 // @author aidenlee // @match *://*/* // @run-at document-start // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; // ============================================================ // 第一部分:伪造 navigator 和 Intl 为新加坡 / English (Singapore) // ============================================================ // --- 伪造 navigator.language 和 languages --- try { Object.defineProperty(navigator, 'language', { get: () => 'en-SG', configurable: true }); Object.defineProperty(navigator, 'languages', { get: () => ['en-SG', 'en'], configurable: true }); } catch(e) {} // --- 伪造 Intl.DateTimeFormat locale --- const OrigDTF = Intl.DateTimeFormat; Intl.DateTimeFormat = function() { const instance = new OrigDTF(...arguments); const origResolved = instance.resolvedOptions.bind(instance); instance.resolvedOptions = function() { const opts = origResolved(); opts.locale = 'en-SG'; opts.timeZone = 'Asia/Singapore'; return opts; }; return instance; }; Intl.DateTimeFormat.prototype = OrigDTF.prototype; // --- 伪造 Intl.NumberFormat locale --- const OrigNF = Intl.NumberFormat; Intl.NumberFormat = function() { const instance = new OrigNF(...arguments); const origResolved = instance.resolvedOptions.bind(instance); instance.resolvedOptions = function() { const opts = origResolved(); opts.locale = 'en-SG'; return opts; }; return instance; }; Intl.NumberFormat.prototype = OrigNF.prototype; // --- 伪造 Intl.Collator locale --- const OrigCol = Intl.Collator; Intl.Collator = function() { const instance = new OrigCol(...arguments); const origResolved = instance.resolvedOptions.bind(instance); instance.resolvedOptions = function() { const opts = origResolved(); opts.locale = 'en-SG'; return opts; }; return instance; }; Intl.Collator.prototype = OrigCol.prototype; // ============================================================ // 第二部分:隐藏中文字体 + Canvas 噪声 // ============================================================ const CJK_FONT_RE = /("|')?(Songti|Microsoft\s*YaHei(\s*UI)?|微软雅黑|SimSun|宋体|NSimSun|新宋体|SimHei|黑体|FangSong|仿宋|KaiTi|楷体|PingFang\s*(SC|TC|HK)|苹方[-\s]*(简|繁|港)|Hiragino\s*Sans\s*(GB|CNS)|STSong|华文宋体|STKaiti|华文楷体|STHeiti|华文黑体|STFangsong|华文仿宋|WenQuanYi|文泉驿|Noto\s*Sans\s*(SC|TC|JP|KR)|Noto\s*Serif\s*(SC|TC|JP|KR)|Source\s*Han|思源|MiSans|HarmonyOS|Droid\s*Sans\s*Fallback)(\1)?/gi; function censorFont(val) { val = val.replace(CJK_FONT_RE, ''); val = val.replace(/,\s*,/g, ',').replace(/^,\s*/, '').replace(/,\s*$/, ''); return val || '16px Arial, sans-serif'; } // --- 拦截 Canvas.font --- try { const desc = Object.getOwnPropertyDescriptor(CanvasRenderingContext2D.prototype, 'font'); if (desc && desc.set) { const origSet = desc.set; Object.defineProperty(CanvasRenderingContext2D.prototype, 'font', { get: desc.get, set: function(v) { origSet.call(this, censorFont(String(v))); }, configurable: true, enumerable: true }); } } catch(e) {} // --- getImageData 噪声 --- const _getImageData = CanvasRenderingContext2D.prototype.getImageData; CanvasRenderingContext2D.prototype.getImageData = function() { const d = _getImageData.apply(this, arguments); try { for (let i = 0; i < d.data.length; i += 4) { if (Math.random() < 0.0001) d.data[i] ^= 1; } } catch(e) {} return d; }; // --- toDataURL / toBlob / toImageBitmap 噪声 --- function addNoise(canvas) { try { const ctx = canvas.getContext('2d', { willReadFrequently: true }); if (ctx && canvas.width > 0 && canvas.height > 0) { const img = ctx.getImageData(0, 0, canvas.width, canvas.height); for (let i = 0; i < img.data.length; i += 4) { if (Math.random() < 0.0001) img.data[i] ^= 1; } ctx.putImageData(img, 0, 0); } } catch(e) {} } const _toDataURL = HTMLCanvasElement.prototype.toDataURL; HTMLCanvasElement.prototype.toDataURL = function() { addNoise(this); return _toDataURL.apply(this, arguments); }; const _toBlob = HTMLCanvasElement.prototype.toBlob; HTMLCanvasElement.prototype.toBlob = function() { addNoise(this); return _toBlob.apply(this, arguments); }; // --- 拦截 CSS 字体检测 --- if (typeof FontFaceSet !== 'undefined') { const _check = FontFaceSet.prototype.check; FontFaceSet.prototype.check = function(font) { if (CJK_FONT_RE.test(String(font))) return false; return _check.call(this, font); }; } // ============================================================ // 第三部分:屏蔽事件里的时区 / locale 指纹泄漏 // ============================================================ // --- 伪造 getTimezoneOffset 为 UTC+8 (新加坡) --- const _getTimezoneOffset = Date.prototype.getTimezoneOffset; Date.prototype.getTimezoneOffset = function() { return -480; }; const _toLocaleString = Date.prototype.toLocaleString; Date.prototype.toLocaleString = function() { if (arguments.length === 0) return _toLocaleString.call(this, 'en-SG', { timeZone: 'Asia/Singapore' }); return _toLocaleString.apply(this, arguments); }; const _toLocaleDateString = Date.prototype.toLocaleDateString; Date.prototype.toLocaleDateString = function() { if (arguments.length === 0) return _toLocaleDateString.call(this, 'en-SG', { timeZone: 'Asia/Singapore' }); return _toLocaleDateString.apply(this, arguments); }; const _toLocaleTimeString = Date.prototype.toLocaleTimeString; Date.prototype.toLocaleTimeString = function() { if (arguments.length === 0) return _toLocaleTimeString.call(this, 'en-SG', { timeZone: 'Asia/Singapore' }); return _toLocaleTimeString.apply(this, arguments); }; })();