// ==UserScript== // @name 网页广告拦截器 // @namespace http://tampermonkey.net/ // @version 5.9.9 // @author DeepSeek&Gemini // @description 一个手机端浏览器能用的强大的广告拦截器 // @match *://*/* // @license MIT // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_registerMenuCommand // @grant GM_notification // @grant GM_listValues // @grant GM_xmlhttpRequest // @run-at document-start // ==/UserScript== (function () { 'use strict'; // ======================== 初始样式注入 ======================== // 注入 ._uh 隐藏类样式(visibility:hidden + pointer-events:none):所有被拦截元素统一加该类隐藏, // 保留布局尺寸(不引发页面跳动),且不可点击。 (function () { try { var s = document.createElement('style'); s.setAttribute('data-hs', 'true'); s.textContent = '._uh{visibility:hidden!important;opacity:0!important;pointer-events:none!important}'; (document.documentElement || document).appendChild(s); } catch(e) { _debugCatch(e, "s.setAttribute('data-hs'"); } })(); // ======================== 调试辅助 ======================== // 使用:开启 设置→高级→调试日志(DEBUG) 后,运行异常会输出到 F12→Console(_debugCatch 统一入口)。 // 查看拦截日志:打开脚本设置面板 →「拦截日志」页(各模块的拦截/放行记录都在这里,可对条目加白)。 const _debugCatch = function(e, ctx) { if (_debugCatch._isRunning) return; _debugCatch._isRunning = true; try { if (typeof currentConfig !== 'undefined' && currentConfig.DEBUG && typeof console !== 'undefined') { console.warn('[AB]', ctx || '', e && e.message ? e.message : e); } } catch(_) { try { console.error('[AB] _debugCatch error:', _ && _.message ? _.message : _); } catch(__) {} } finally { _debugCatch._isRunning = false; } }; // ======================== 默认配置 ======================== // 模块默认开关/阈值均在此;面板中的开关修改后写入 GM 存储,刷新后生效。 const DEFAULT_MODULE_STATE = { removeInlineScripts: false, removeExternalScripts: false, interceptThirdParty: false, blockDynamicScripts: false, manageCSP: false, overlayIntercept: false, scriptBlacklistMode: true, }; const DEFAULT_CONFIG_STATE = { inlineScriptStrictMode: false, dynamicScriptStrictMode: false, thirdPartyStrictMode: false, thirdPartyStrictMethod: false, spoofUAEnabled: false, simplePlatformSpoof: false, residualCleanupEnabled: false, iframeUIFix: false, redirectBlockerEnabled: true, builtinBlacklistEnabled: true, redirectBlockerCompatibilityMode: true, heuristicBlacklistEnabled: false, crossTagPatternThreshold: 3, trackingParamsCleanerEnabled: true, redirectTitleBlacklistEnabled: true, redirectTitleWhitelistEnabled: true, redirectBlockAllEnabled: false, redirectWhitelistBlockMode: false, DEBUG: false, }; const DEFAULT_CSP_RULES_TEMPLATE = [ { id: 1, name: '只允许同源外部脚本', rule: "script-src 'self'", enabled: false }, { id: 2, name: '只允许同源外部样式', rule: "style-src 'self'", enabled: false }, { id: 3, name: '只允许同源图片', rule: "img-src 'self'", enabled: false }, { id: 4, name: '只允许同源框架', rule: "frame-src 'self'", enabled: false }, { id: 5, name: '只允许同源媒体', rule: "media-src 'self'", enabled: false }, { id: 6, name: '只允许同源对象与嵌入', rule: "object-src 'self'", enabled: false }, { id: 7, name: '只允许同源网络请求', rule: "connect-src 'self'", enabled: false }, ]; const BUILTIN_BLACKLIST_KEYWORDS = [ 'hm.baidu.com', 'tongji()', 'tj()', 'push.js', 'cnzz', 'histats.com', 'https://hongosi.xn--', '{return void 0!==b[a]?b[a]:a}).join("")}', '${scripts[randomIndex]}', '${scripts[Math.random()', 'https://"+Date.parse(new Date())+', 'https://"+(new Date().getDate())+"', 'https://{randomstr}.', 'new Function(t)()', 'new Function(b)()', 'new Function(c)()', 'new Function(t);', 'new Function(b);', 'new Function(c);', "new Function('d',e)", 'new Function(document[', 'new Function(function(p,a,c,k,e,d)', 'function a(a){', 'function b(b){', 'function c(c){', 'Math.floor(2147483648 * Math.random());', 'Math.floor(Math.random()*url.length)', 'Math.floor(Math.random() * urls.length)', "new Date()['getTime']()", 'newDate=new window', 'Math.floor(((new Date()).getTime()', '&&navigator[', '=navigator;', 'navigator.platform){setTimeout(function', 'disableDebugger', 'blockDeveloperTools', '["Date"]())[\'getTime\']()', '\')', '<\\/\'+\'s\'+\'c\'+\'ri\'+\'pt\'+\'>\')', '(\'#htmlContenthtml\').html', 'D.createElement(\'span\');', 'window.$m(', '{win:false,mac:false,xll:false}', 'function|getDate', 'parseInt(Math[\'random\'' ]; var _heuristicRegexGroups = null; function getHeuristicRegexGroups() { if (_heuristicRegexGroups !== null) return _heuristicRegexGroups; if (!currentConfig.heuristicBlacklistEnabled) return null; try { const hexGroup = [ '(?:_0x[a-zA-Z0-9]{4,}.*){5,}', 'parseInt\\s*\\(\\s*[\'"\\x60]0x[0-9a-fA-F]{2,}[\'"\\x60]?\\s*\\)\\s*(?:\\||\\^|&)', '(?:\\\\x[0-9a-fA-F]{2}){10,}', '(?:\\\\u[0-9a-fA-F]{4}){10,}', '\\\\x[0-9a-fA-F]{2}(?:\\\\x[0-9a-fA-F]{2}){20,}', '(?:\\^|\\||~)\\s*0x[0-9a-fA-F]{2,}\\s*(?:\\^|\\||~)', 'parseInt\\s*\\([^)]+\\)\\s*\\^\\s*0x[0-9a-fA-F]{2,}', ]; const domGroup = [ '(?:window|document|navigator|location)\\s*\\[\\s*(?![\'"](?:getElementById|querySelector|addEventListener|removeEventListener|createElement|getElementsByClassName|getElementsByTagName|getComputedStyle|matchMedia|requestAnimationFrame|cancelAnimationFrame|fetch|XMLHttpRequest))[\'"]\\s*[^\\+]+?\\]\\s*\\(', '\\bwith\\s*\\(\\s*(?:document|window)\\s*\\)\\s*\\{', '(?:window|document)\\s*\\[\\s*[\'"][^\'"]+[\'"]\\s*\\+\\s*[\'"][^\'"]+[\'"]\\s*\\]\\s*[=;(]', ]; const controlGroup = [ 'blockDeveloperTools|disableDebugger|devtoolschange', 'setTimeout\\s*\\(\\s*[\'"\\x60](?:javascript:|eval\\s*\\(|Function\\s*\\()', 'setInterval\\s*\\(\\s*[\'"\\x60](?:javascript:|eval\\s*\\(|Function\\s*\\()', 'setTimeout\\s*\\(\\s*(?:function|\\(\\)\\s*=>)\\s*\\{[^}]*?location\\.(?:href|replace|assign)', ]; const cipherGroup = [ 'atob\\s*\\([^)]*\\)[^;]{0,100}?(?:eval|Function)\\s*\\(', '(?:eval|Function|document\\.write)\\s*\\([^)]{0,200}?atob\\s*\\(', 'atob\\s*\\(.+?\\.split\\s*\\(\\s*[\'"][\'"]\\s*\\)\\s*\\.map', '\\batob\\s*\\(\\s*[\'"][A-Za-z0-9+/=]{80,}[\'"]\\s*\\)[^;]{0,200}?(?:eval|Function|document\\.write|\\.src\\s*=|\\.href\\s*=)', 'atob\\s*\\(.*?\\).*?atob\\s*\\(', 'unescape\\s*\\(.*?\\)\\s*\\+\\s*unescape', '(?:eval|Function)\\s*\\([^)]{0,200}?unescape\\s*\\(', 'unescape\\s*\\([^)]*\\)[^;]{0,100}?(?:eval|Function)\\s*\\(', '(?:eval|Function|JSON\\.parse)\\s*\\([^)]{0,200}?decodeURIComponent\\s*\\(', 'decodeURIComponent\\s*\\([^)]*\\)[^;]{0,100}?(?:eval|Function)\\s*\\(', 'JSON\\s*\\.\\s*parse\\s*\\(\\s*(?:atob|decodeURIComponent|unescape)\\s*\\(', '"[\\w\\-~!@#$%^&*()+=\\[\\]{}|\\\\:;\'<>,.?/` ]{60,}".*?"[\\w\\-~!@#$%^&*()+=\\[\\]{}|\\\\:;\'<>,.?/` ]{60,}"', '\\.replace\\s*\\(\\s*\\/\\\\\\\\[a-f0-9]+\\/g\\s*,\\s*[\'"\\x60]\\\\\\\\x[\'"\\x60]\\s*\\).*?eval', '(?:\\\\x[0-9a-fA-F]{2}){10,}[^;]{0,100}?eval', '(?:\\\\u[0-9a-fA-F]{4}){5,}[^;]{0,100}?eval', '(?:decodeURIComponent|atob|unescape)\\s*\\([^)]{5,}\\).*?(?:decodeURIComponent|atob|unescape)\\s*\\(', '(?:var|let|const)\\s+\\w+\\s*=\\s*[\'"][A-Za-z0-9+/=]{300,}[\'"][^;]{0,80}?(?:eval|Function)\\s*\\(', '(?:String\\.fromCharCode|fromCodePoint)\\s*\\([^)]{20,}\\)[^;]{0,100}?(?:eval|Function)\\s*\\(', '\\[\\d{2,3}(?:,\\d{2,3}){5,}\\][^;]{0,50}?(?:eval|Function|String\\.fromCharCode)\\s*\\(', '\\.split\\s*\\(\\s*""\\s*\\)\\s*\\.\\s*map\\s*\\([^)]{0,1000}\\)[^;]{0,1000}?new\\s+Function', '\\.split\\s*\\(\\s*[\'"][\'"]\\s*\\)\\.reverse\\s*\\(\\s*\\)\\.join', '\\.split\\s*\\(\\s*[\'"][^\'"]{1,5}[\'"]\\s*\\)\\s*\\.reverse\\s*\\(\\s*\\)\\s*\\.join', '\\.split\\s*\\(\\s*[\'"][^\'"]{1,5}[\'"]\\s*\\)\\s*\\.map\\s*\\([^)]{0,1000}\\)[^;]{0,500}?\\.(?:join|toString)', 'String\\.fromCharCode\\.apply\\s*\\(null,\\s*\\[', '\\(function\\s*\\([^)]*\\)\\s*\\{[^}]{0,800}?(?:atob[^}]{0,800}?(?:eval|Function)|(?:eval|Function)[^}]{0,800}?atob)\\s*\\(', 'function\\s+\\w+\\s*\\([^)]*\\)\\s*\\{[^}]*?atob[^}]*?(?:eval|Function)\\s*\\(', '(?:toString|split|replace|substring|substr|slice|trim|charAt|charCodeAt|join|reverse){5,}[^;]{0,200}?(?:eval|Function|atob|document\\.write)\\s*\\(', '(?:setTimeout|setInterval)\\s*\\(\\s*(?:function|\\(\\)\\s*=>)\\s*\\{[^}]{0,300}?(?:eval|Function|atob)\\s*\\(', '(?:fetch|XMLHttpRequest)\\s*\\(?[^)]{0,200}?\\)[^;]{0,300}?(?:eval|Function)\\s*\\(', '\\beval\\s*\\(\\s*atob\\s*\\(', '\\beval\\s*\\(\\s*String\\.fromCharCode\\s*\\(', '\\blocation\\s*\\.\\s*href\\s*=\\s*(?:atob|String\\.fromCharCode|decodeURIComponent)', '\\blocation\\s*(?:\\.\\s*href|\\[\\s*[\'"]href[\'"]\\s*\\])\\s*=\\s*[\'"][^\'"]{200,}', 'setTimeout\\s*\\(\\s*[\'"][^\'"]*location\\.href[^\'"]*[\'"]', '\\b[a-zA-Z_]\\w{2,}\\s*\\(\\s*\\d{3,}(?:\\s*,\\s*\\d+)*\\s*\\)', '\\$\\s*\\(\\s*(?:function\\s*\\([^)]*\\)\\s*\\{|document\\)\\.ready)\\s*[^}]*?\\w{2,}\\s*\\(\\s*\\d{4,}', '^[a-zA-Z_$]\\w{2,}\\s*\\(\\s*\\d{3,}(?:\\s*,\\s*\\d+)*\\s*\\)\\s*;?\\s*$', ]; const miscGroup = [ 'constructor\\s*\\(\\s*[\'"\\x60]return\\s+this[\'"\\x60]\\s*\\)\\s*\\(', '\\.constructor\\.constructor\\([^\\)]+\\)\\s*\\(', 'try\\s*\\{[^}]{0,200}?(?:eval|Function|atob|document\\.write)\\s*\\([^)]{0,200}\\}\\s*catch\\s*\\(\\s*[a-zA-Z_]+\\s*\\)\\s*\\{\\s*\\}', '(?:try\\s*\\{[^}]*\\}\\s*catch\\s*\\(\\w+\\)\\s*\\{){3,}', 'try\\s*\\{[^}]{0,150}\\}\\s*catch\\s*\\(\\s*\\w+\\s*\\)\\s*\\{\\s*return\\s+(?:_0x|this|constructor)', '\\.style\\.position\\s*=\\s*[\'"]fixed[\'"][\\s\\S]{0,50}?\\.style\\.zIndex\\s*=\\s*[\'"]?[1-9]\\d{4,}', '_0x[0-9a-f]+\\s*=\\s*function\\s*\\(\\s*_0x[0-9a-f]+\\s*,\\s*_0x[0-9a-f]+\\s*\\)', '_0x[0-9a-f]+\\s*=\\s*function\\s*\\(\\s*_0x[0-9a-f]+\\s*,\\s*_0x[0-9a-f]+\\s*,\\s*_0x[0-9a-f]+\\s*\\)', '(?:Object\\.keys|Object\\.values)\\s*\\(\\s*_0x[0-9a-f]+\\s*\\)\\.map\\s*\\(', '_0x[0-9a-f]+\\s*\\.forEach\\s*\\(\\s*(?:_0x[0-9a-f]+|function)\\s*\\(', '(?:const|let|var)\\s*\\{[^}]{5,}\\}\\s*=\\s*_0x[0-9a-f]+\\s*\\(', '!function\\s*\\(\\s*\\)\\s*\\{[^}]{0,200}return\\s+_0x[0-9a-f]+\\s*\\(', 'JSON\\.parse\\s*\\(\\s*_0x[0-9a-f]+\\s*\\(', '(?:window|document|navigator)\\s*\\[\\s*[\'"][^\'"]{2,10}[\'"]\\s*\\+\\s*[\'"][^\'"]{2,10}[\'"]\\s*\\]', 'function\\s+[a-z]\\d[a-z0-9]{5,}', '\\[\\s*[\'"][^\'"]{1,10}[\'"]\\s*\\+\\s*[\'"][^\'"]{1,10}[\'"]\\s*\\+\\s*[\'"][^\'"]{1,10}[\'"]\\s*\\]', 'arguments\\s*\\[\\s*\\d+\\s*\\]\\s*\\(\\s*\\d+\\s*\\)', 'try\\s*\\{\\s*[a-zA-Z_]\\w*?\\d{1,4}\\s*\\([^)]*\\)\\s*;?\\s*\\}\\s*catch\\s*\\(\\s*\\w+\\s*\\)\\s*\\{\\s*\\}?\\s*;?\\s*try\\s*\\{\\s*[a-zA-Z_]\\w*?\\d{1,4}\\s*\\([^)]*\\)\\s*;?\\s*\\}\\s*catch\\s*\\(\\s*\\w+\\s*\\)\\s*\\{\\s*\\}?\\s*;?\\s*try\\s*\\{\\s*[a-zA-Z_]\\w*?\\d{1,4}\\s*\\([^)]*\\)\\s*;?\\s*\\}\\s*catch\\s*\\(\\s*\\w+\\s*\\)\\s*\\{\\s*\\}?\\s*', 'document\\.cookie[^;]{0,200}?(?:new\\s+)?Image\\s*[=(]', '_0x[0-9a-f]+\\s*\\(\\s*_0x[0-9a-f]+\\s*\\)[^;]{0,50}?(?:eval|Function|atob|JSON\\.parse)\\s*\\(', '\\b[a-zA-Z]{4,}(?:md|bm|tp|fn|cb|ex|rx|tx|dx|sx|px|qx|kx|one|two|three|four|five|six|seven|eight|nine|ten)\\s*\\(', '^\\s*[a-zA-Z_]\\w{3,}\\s*\\(\\s*\\)\\s*;?\\s*$', ]; const baseGroups = [ new RegExp(hexGroup.map(p => `(?:${p})`).join('|'), 'i'), new RegExp(domGroup.map(p => `(?:${p})`).join('|'), 'i'), new RegExp(controlGroup.map(p => `(?:${p})`).join('|'), 'i'), new RegExp(cipherGroup.map(p => `(?:${p})`).join('|'), 'i'), new RegExp(miscGroup.map(p => `(?:${p})`).join('|'), 'i'), ]; _heuristicRegexGroups = baseGroups; return baseGroups; } catch (e) { _heuristicRegexGroups = null; return null; } } const DEFAULT_REDIRECT_TITLE_BLACKLIST = ['小说', '章节', '阅读', '在线', '热播', '影院', '影音', 're:第[\\d零一二三四五六七八九十百千万亿]+[章节页]']; const DEFAULT_REDIRECT_TITLE_WHITELIST = ['银行', '转账', '支付', '安全中心', '登录', '验证', '密码', '交易', '脚本', '搜索']; let currentConfig = { ...DEFAULT_CONFIG_STATE, modules: { ...DEFAULT_MODULE_STATE }, cspRules: DEFAULT_CSP_RULES_TEMPLATE.map((rule) => ({ ...rule })), whitelist: new Set(), keywordWhitelist: new Set(), scriptBlacklist: new Set(), thirdPartyWhitelist: [], removedBuiltinKeywords: new Set(), whitelistDisplayNames: new Map(), redirectTitleBlacklistKeywords: [...DEFAULT_REDIRECT_TITLE_BLACKLIST], redirectTitleWhitelistKeywords: [...DEFAULT_REDIRECT_TITLE_WHITELIST], trackingParamKeys: [], }; const ConfigUpdater = { saveNow() { StorageManager.saveConfig(); }, }; const StorageManager = { getConfigKey(domain) { return `adblock_unified_config_${domain}`; }, loadConfig() { const hostname = location.hostname; const key = this.getConfigKey(hostname); try { const saved = GM_getValue(key, null); if (saved) { const data = JSON.parse(saved); if (data.modules) Object.assign(currentConfig.modules, data.modules); if (data.cspRules) currentConfig.cspRules = data.cspRules.map((r) => ({ ...r })); if (Array.isArray(data.whitelist)) currentConfig.whitelist = new Set(data.whitelist); if (Array.isArray(data.keywordWhitelist)) currentConfig.keywordWhitelist = new Set(data.keywordWhitelist); if (Array.isArray(data.scriptBlacklist)) currentConfig.scriptBlacklist = new Set(data.scriptBlacklist); if (Array.isArray(data.thirdPartyWhitelist)) currentConfig.thirdPartyWhitelist = data.thirdPartyWhitelist; if (Array.isArray(data.removedBuiltinKeywords)) currentConfig.removedBuiltinKeywords = new Set( data.removedBuiltinKeywords ); if (Array.isArray(data.whitelistDisplayNames)) { try { currentConfig.whitelistDisplayNames = new Map(data.whitelistDisplayNames); } catch (e) { currentConfig.whitelistDisplayNames = new Map(); } } Object.keys(DEFAULT_CONFIG_STATE).forEach((configKey) => { if (data[configKey] !== undefined) { currentConfig[configKey] = data[configKey]; } else if (currentConfig[configKey] === undefined) { currentConfig[configKey] = DEFAULT_CONFIG_STATE[configKey]; } }); } try { var glBlKw = GM_getValue('adblock_gl_blacklistKeywords', null); if (glBlKw !== null) currentConfig.redirectTitleBlacklistKeywords = JSON.parse(glBlKw); var glWlKw = GM_getValue('adblock_gl_whitelistKeywords', null); if (glWlKw !== null) currentConfig.redirectTitleWhitelistKeywords = JSON.parse(glWlKw); var glTp = GM_getValue('adblock_gl_trackingParams', null); if (glTp !== null) { currentConfig.trackingParamKeys = JSON.parse(glTp); rebuildTrackingParamRegex(currentConfig.trackingParamKeys); } } catch(e) { _debugCatch(e, "currentConfig.trackingPar"); } } catch (e) { console.warn( '[广告拦截器] 配置加载失败,已回退默认值:', e.message || e ); try { GM_deleteValue(key); } catch (e2) { } } }, saveConfig() { const hostname = location.hostname; const key = this.getConfigKey(hostname); const toStore = { modules: currentConfig.modules, cspRules: currentConfig.cspRules, whitelist: Array.from(currentConfig.whitelist), keywordWhitelist: Array.from(currentConfig.keywordWhitelist), scriptBlacklist: Array.from(currentConfig.scriptBlacklist), thirdPartyWhitelist: currentConfig.thirdPartyWhitelist, removedBuiltinKeywords: Array.from(currentConfig.removedBuiltinKeywords), whitelistDisplayNames: Array.from(currentConfig.whitelistDisplayNames.entries()), }; Object.keys(DEFAULT_CONFIG_STATE).forEach((k) => { toStore[k] = currentConfig[k]; }); GM_setValue(key, JSON.stringify(toStore)); }, resetAllSettings() { currentConfig.modules = { ...DEFAULT_MODULE_STATE }; currentConfig.cspRules = DEFAULT_CSP_RULES_TEMPLATE.map((rule) => ({ ...rule, })); currentConfig.whitelist.clear(); currentConfig.keywordWhitelist.clear(); currentConfig.scriptBlacklist.clear(); currentConfig.thirdPartyWhitelist = []; currentConfig.removedBuiltinKeywords.clear(); currentConfig.whitelistDisplayNames.clear(); currentConfig.redirectTitleBlacklistKeywords = [...DEFAULT_REDIRECT_TITLE_BLACKLIST]; currentConfig.redirectTitleWhitelistKeywords = [...DEFAULT_REDIRECT_TITLE_WHITELIST]; Object.keys(DEFAULT_CONFIG_STATE).forEach((k) => { currentConfig[k] = DEFAULT_CONFIG_STATE[k]; }); try { GM_deleteValue('adblock_gl_blacklistKeywords'); GM_deleteValue('adblock_gl_whitelistKeywords'); GM_deleteValue('adblock_gl_trackingParams'); } catch(e) { _debugCatch(e, "GM_deleteValue('adblock_g"); } currentConfig.trackingParamKeys = [...DEFAULT_TRACKING_PARAMS]; rebuildTrackingParamRegex(currentConfig.trackingParamKeys); this.saveConfig(); }, }; StorageManager.loadConfig(); let menuCommandsRegistered = false; try { registerAllMenuCommands(); } catch(e) { _debugCatch(e, "registerAllMenuCommands"); } // ======================== ScriptSyncGuard 中央同步脚本拦截器 ======================== // 负责同步检查动态插入的