// ==UserScript== // @name 广告终结者 // @namespace http://tampermonkey.net/ // @version 3.9.4.1 // @description 优化动态检测 // @match *://*/* // @license MIT // @grant unsafeWindow // @grant GM_registerMenuCommand // @grant GM_notification // @grant GM_getValue // @grant GM_setValue // @grant GM_getResourceText // @grant GM_addStyle // @run-at document-start // ==/UserScript== (function() { 'use strict'; const HOSTNAME = location.hostname.replace(/\./g, '\\.'); const REGEX = { dynamicId: /^(?:([0-9a-f]{4}-?){4,}|[0-9a-f]{16,}|[\dA-F-]{18,}|[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12})$/i, jsAdPattern: /(?:window\.open|document\.write|createElement\(['"]script['"]|location\.replace|setTimeout\s*\([^)]*?\b(?:window\.open|document\.write)|eval\s*\(|new\s+Function\s*\(|appendChild\s*\(.*?\bscript\b|on(?:click|load)\s*=\s*['"]?\s*(?:window\.open))/i, adAttributes: /(推广|广告|gg|sponsor|推荐|guanggao|syad|bfad|弹窗|悬浮|葡京|banner|pop(?:up|under)|track(?:ing)?)(?:$|[_-])/i, thirdParty: new RegExp(`^(https?:\\/\\/(.*\\.)?${HOSTNAME}(\\/|$)|data:|about:blank)`, 'i') }; const DEFAULT_MODULES = { main: false, dynamicSystem: false, layoutSystem: false, frameSystem: false, mediaSystem: false, thirdPartyBlock: false, floating: false, specialUA: true }; const DEFAULT_CSP_RULES = [ { id: 1, name: '阻止外部脚本加载', rule: "script-src 'self'", enabled: true }, { id: 2, name: '阻止内联脚本执行', rule: "script-src 'unsafe-inline'", enabled: false }, { id: 3, name: '阻止动态脚本执行', rule: "script-src 'unsafe-eval'", enabled: false }, { id: 4, name: '阻止外部样式加载', rule: "style-src 'self'", enabled: false }, { id: 5, name: '阻止内联样式执行', rule: "style-src 'unsafe-inline'", enabled: false }, { id: 6, name: '阻止外部图片加载', rule: "img-src 'self'", enabled: true }, { id: 7, name: '禁止所有框架加载', rule: "frame-src 'none'", enabled: false }, { id: 8, name: '禁止媒体资源加载', rule: "media-src 'none'", enabled: false }, { id: 9, name: '禁止对象嵌入', rule: "object-src 'none'", enabled: false } ]; const CONFIG = { modules: { ...DEFAULT_MODULES }, protectionRules: { dynamicIdLength: 32, zIndexThreshold: 100, maxFrameDepth: 4 }, csp: { enabled: false, rules: DEFAULT_CSP_RULES.map(rule => ({ ...rule })) }, mobileOptimizations: { lazyLoadImages: true, removeImagePlaceholders: true }, moduleNames: { dynamicSystem: '动态检测系统', layoutSystem: '布局检测系统', frameSystem: '框架过滤系统', mediaSystem: '媒体检测系统', thirdPartyBlock: '第三方拦截', floating: '浮动广告检测', specialUA: '添加特殊UA' }, modulePriority: [ 'thirdPartyBlock', 'specialUA', 'frameSystem', 'dynamicSystem', 'mediaSystem', 'layoutSystem', 'floating' ], performance: { highRiskModules: ['thirdPartyBlock', 'frameSystem'], visibleAreaPriority: true, styleCacheTimeout: 600000, mutationProcessLimit: 40, mutationProcessTimeLimit: 10, idleCallbackTimeout: 2500, throttleScrollDelay: 200, debounceMutationDelay: 300, longTaskThreshold: 500, degradeThreshold: 5 }, originalUA: { userAgent: navigator.userAgent, platform: navigator.platform } }; const Logs = { logs: [], maxLogs: 30, add(moduleKey, element, reason) { const moduleName = CONFIG.moduleNames[moduleKey] || '未知模块'; this.logs.push({ module: moduleName, element: `${element.tagName}#${element.id || ''}.${element.className || ''}`, content: this.getContent(element), reason: reason || {}, timestamp: new Date().toISOString() }); if (this.logs.length > this.maxLogs) this.logs.shift(); }, getContent(element) { if (element instanceof Element) { return element.outerHTML.slice(0, 100) + '...'; } return `[${element.tagName}] ${element.id} [${element.className}]`; }, clear() { this.logs = []; GM_notification('日志已清空'); } }; const perf = { pendingTasks: [], isProcessing: false, processed: new WeakSet(), adElements: new Set(), styleCache: new Map(), lastStyleCacheClear: Date.now(), longTaskCount: 0, degraded: false }; const ModuleManager = { modules: {}, register(name, module) { this.modules[name] = module; }, init() { Object.values(this.modules).forEach(module => { if (typeof module.init === 'function') { module.init(); } }); }, run(moduleName, element) { if (!CONFIG.modules.main || !CONFIG.modules[moduleName]) return false; const module = this.modules[moduleName]; if (module && typeof module.check === 'function') { return module.check(element); } return false; } }; const AdUtils = { safeRemove(element, moduleKey, reason) { if (!element?.parentNode) return false; try { Logs.add(moduleKey, element, reason); element.style.display = 'none'; element.style.visibility = 'hidden'; element.style.opacity = '0'; element.setAttribute('data-ad-removed', 'true'); this.removeEventListeners(element); return true; } catch (e) { console.warn('元素隐藏失败:', e); return false; } }, removeEventListeners(element) { element.removeAttribute('onload'); element.removeAttribute('onerror'); } }; const StyleManager = { styleElement: null, inject() { if (this.styleElement) return; this.styleElement = document.createElement('style'); this.styleElement.id = 'ad-blocker-styles'; let cssRules = []; cssRules.push(` [data-ad-removed] { display: none !important; visibility: hidden !important; opacity: 0 !important; position: absolute !important; z-index: -9999 !important; width: 0 !important; height: 0 !important; } iframe[src*="ad"], .ad-container { display: none !important; height: 0 !important; width: 0 !important; opacity: 0 !important; } `); this.styleElement.textContent = cssRules.join('\n'); if (document.head) { document.head.appendChild(this.styleElement); } else { document.documentElement.insertBefore(this.styleElement, document.documentElement.firstChild); } }, remove() { if (this.styleElement?.parentNode) { this.styleElement.parentNode.removeChild(this.styleElement); this.styleElement = null; } }, toggle(enable) { enable ? this.inject() : this.remove(); } }; const CSPManager = { currentPolicy: null, generatePolicy() { return CONFIG.csp.rules .filter(rule => rule.enabled) .map(rule => rule.rule) .join('; '); }, inject() { this.remove(); if (!CONFIG.csp.enabled) return; const policy = this.generatePolicy(); this.currentPolicy = policy; const meta = document.createElement('meta'); meta.httpEquiv = "Content-Security-Policy"; meta.content = policy; if (document.head) { document.head.appendChild(meta); } else { document.documentElement.insertBefore(meta, document.documentElement.firstChild); } }, remove() { const meta = document.querySelector('meta[http-equiv="Content-Security-Policy"]'); if (meta) meta.remove(); }, toggleRule(ruleId, enable) { const rule = CONFIG.csp.rules.find(r => r.id === ruleId); if (rule) rule.enabled = enable; }, injectInitialCSP() { if (!CONFIG.csp.enabled) return; const initialRules = CONFIG.csp.rules .filter(rule => [1, 6].includes(rule.id) && rule.enabled) .map(rule => rule.rule) .join('; '); if (initialRules) { const meta = document.createElement('meta'); meta.httpEquiv = "Content-Security-Policy"; meta.content = initialRules; if (document.head) { document.head.appendChild(meta); } else { document.documentElement.insertBefore(meta, document.documentElement.firstChild); } } } }; const Detector = { getCachedStyle(el) { if (Date.now() - perf.lastStyleCacheClear > CONFIG.performance.styleCacheTimeout) { perf.styleCache.clear(); perf.lastStyleCacheClear = Date.now(); } const key = el.nodeName + Array.from(el.classList).join(''); if (!perf.styleCache.has(key)) { try { perf.styleCache.set(key, getComputedStyle(el)); } catch (e) { console.warn("Error getting computed style:", e); return {}; } } return perf.styleCache.get(key); }, isVisible(el) { const style = getComputedStyle(el); if (style.display === 'none' || style.visibility === 'hidden') return false; const rect = el.getBoundingClientRect(); return !( rect.width === 0 || rect.height === 0 || (rect.top >= window.innerHeight || rect.bottom <= 0) || (rect.left >= window.innerWidth || rect.right <= 0) ); } }; const Processor = { collectAds() { const treeWalker = document.createTreeWalker( document.documentElement, NodeFilter.SHOW_ELEMENT, null, false ); perf.adElements.clear(); while (treeWalker.nextNode()) { const element = treeWalker.currentNode; if (perf.processed.has(element)) continue; let modulePriority = CONFIG.modulePriority; if (CONFIG.performance.visibleAreaPriority && Detector.isVisible(element)) { modulePriority = [...CONFIG.performance.highRiskModules, ...modulePriority.filter(m => !CONFIG.performance.highRiskModules.includes(m))]; } for (const moduleKey of modulePriority) { if (ModuleManager.run(moduleKey, element)) { perf.adElements.add(element); perf.processed.add(element); break; } } } }, processElements() { if (perf.degraded) return; let startTime = performance.now(); let elementCount = 0; for (let element of perf.adElements) { if (elementCount >= CONFIG.performance.mutationProcessLimit || performance.now() - startTime > CONFIG.performance.mutationProcessTimeLimit) { perf.pendingTasks.push(() => Processor.processElements()); break; } if (perf.processed.has(element)) continue; let modulePriority = CONFIG.modulePriority; if (CONFIG.performance.visibleAreaPriority && Detector.isVisible(element)) { modulePriority = [...CONFIG.performance.highRiskModules, ...modulePriority.filter(m => !CONFIG.performance.highRiskModules.includes(m))]; } for (const moduleKey of modulePriority) { if (ModuleManager.run(moduleKey, element)) { perf.processed.add(element); break; } } elementCount++; } perf.isProcessing = false; if (perf.pendingTasks.length > 0) { scheduleProcess(); } } }; const observer = new MutationObserver(mutations => { let startTime = performance.now(); let mutationCount = 0; let nodeCount = 0; for (let mutation of mutations) { if (mutationCount >= CONFIG.performance.mutationProcessLimit || nodeCount >= 50 || performance.now() - startTime > CONFIG.performance.mutationProcessTimeLimit) { debouncedScheduleProcess(); return; } if (mutation.type === 'childList') { mutation.addedNodes.forEach(node => { if (node.nodeType === 1) { if (!perf.processed.has(node)) { perf.adElements.add(node); nodeCount++; } } }); } else if (mutation.type === 'attributes') { if (!perf.processed.has(mutation.target)) { perf.adElements.add(mutation.target); nodeCount++; } } mutationCount++; } scheduleProcess(); }); const scheduleProcess = (() => { const tasks = []; let isScheduled = false; function runTasks(deadline) { while (tasks.length && (deadline.timeRemaining() > 2 || deadline.didTimeout)) { const task = tasks.shift(); try { task(); } catch(e) { console.error('Task error:', e); } } if (tasks.length) { requestIdleCallback(runTasks, { timeout: CONFIG.performance.idleCallbackTimeout }); } else { isScheduled = false; } } return function(task) { tasks.push(task); if (!isScheduled) { isScheduled = true; if (typeof requestIdleCallback === 'function') { requestIdleCallback(runTasks, { timeout: CONFIG.performance.idleCallbackTimeout }); } else { setTimeout(() => runTasks({ timeRemaining: () => 50, didTimeout: true }), 50); } } }; })(); const debouncedScheduleProcess = debounce(scheduleProcess, CONFIG.performance.debounceMutationDelay); const UIController = { init() { this.loadConfig(); this.registerMainSwitch(); this.registerModuleSwitches(); this.registerLogCommands(); this.registerCSPCommands(); this.registerResetCommand(); StyleManager.toggle(CONFIG.modules.main); }, registerMainSwitch() { GM_registerMenuCommand( `🔘 主开关 [${CONFIG.modules.main ? '✅' : '❌'}]`, () => this.toggleMain() ); }, registerModuleSwitches() { Object.entries(CONFIG.moduleNames).forEach(([key, name]) => { GM_registerMenuCommand( `${name} [${CONFIG.modules[key] ? '✅' : '❌'}]`, () => this.toggleModule(key, name) ); }); }, registerLogCommands() { GM_registerMenuCommand('📜 查看拦截日志', () => this.showLogs()); GM_registerMenuCommand('🧹 清空日志', () => Logs.clear()); }, registerCSPCommands() { GM_registerMenuCommand('🛡️ CSP策略管理', () => this.manageCSP()); }, registerResetCommand() { GM_registerMenuCommand('🔄 重置设置', () => this.resetSettings()); }, toggleMain() { const newState = !CONFIG.modules.main; Object.keys(CONFIG.modules).forEach(key => { if (key !== 'main') CONFIG.modules[key] = newState; }); CONFIG.modules.main = newState; this.saveConfig(); StyleManager.toggle(newState); GM_notification(`主开关已${newState ? '启用' : '禁用'}`, `所有模块${newState ? '✅ 已激活' : '❌ 已停用'}`); location.reload(); }, toggleModule(key, name) { CONFIG.modules[key] = !CONFIG.modules[key]; CONFIG.modules.main = Object.values(CONFIG.modules) .slice(1).some(v => v); this.saveConfig(); StyleManager.toggle(CONFIG.modules.main); GM_notification(`${name} ${CONFIG.modules[key] ? '✅ 已启用' : '❌ 已禁用'}`); location.reload(); }, showLogs() { const logText = Logs.logs.map(log => { const parts = []; if (log.reason.type) parts.push(`类型: ${log.reason.type}`); if (log.reason.detail) parts.push(`详细: ${log.reason.detail}`); if (log.reason.keywords) parts.push(`关键词: ${log.reason.keywords.join(', ')}`); if (log.reason.regex) parts.push(`正则式: ${log.reason.regex.join(', ')}`); return `${log.module}\n元素: ${log.element}\n内容: ${log.content}\n${parts.join('\n')}`; }).join('\n\n'); alert(`广告拦截日志(最近${Logs.maxLogs}条):\n\n${logText || '暂无日志'}`); }, manageCSP() { const rulesDisplay = CONFIG.csp.rules .map(r => `${r.id}. ${r.name} (${r.enabled ? '✅' : '❌'})`) .join('\n'); let input = prompt( `当前CSP策略状态: ${CONFIG.csp.enabled ? '✅ 已启用' : '❌ 已禁用'}\n\n` + "当前策略规则:\n" + rulesDisplay + "\n\n" + "输入选项:\n1. 开启策略 (输入 'enable')\n2. 关闭策略 (输入 'disable')\n" + "修改规则示例:输入 '1on' 启用规则1\n输入 '23off' 禁用规则2和规则3\n\n"+ "请输入你的选择:", "" ); if (input === null) return; input = input.trim().toLowerCase(); switch (input) { case 'enable': CONFIG.csp.enabled = true; this.saveConfig(); CSPManager.inject(); alert("CSP策略已启用"); location.reload(); break; case 'disable': CONFIG.csp.enabled = false; this.saveConfig(); CSPManager.remove(); alert("CSP策略已禁用"); location.reload(); break; default: if (/^\d+(?:on|off)$/i.test(input)) { this.modifyCSPRules(input); } else { alert("无效输入"); } } }, modifyCSPRules(actionInput) { const matches = actionInput.match(/^(\d+)(on|off)$/i); if (matches) { const ruleIds = matches[1].split('').map(Number); const enable = matches[2].toLowerCase() === 'on'; let updatedRules = []; ruleIds.forEach(id => { const rule = CONFIG.csp.rules.find(r => r.id === id); if (rule) { rule.enabled = enable; updatedRules.push(id); } }); this.saveConfig(); CSPManager.inject(); alert(`规则 ${updatedRules.join(',')} 已更新为 ${enable ? '启用' : '禁用'}`); location.reload(); } else { alert("输入格式错误,请按示例输入如'1on'或'123off'"); } }, resetSettings() { if (confirm("确定要重置所有设置吗?")) { Object.assign(CONFIG.modules, DEFAULT_MODULES); CONFIG.csp.enabled = false; CONFIG.csp.rules = DEFAULT_CSP_RULES.map(rule => ({ ...rule })); this.saveConfig(); CSPManager.remove(); alert("设置已重置为默认值"); location.reload(); } }, saveConfig() { GM_setValue(`adblockConfig_${location.hostname}`, JSON.stringify({ modules: CONFIG.modules, csp: CONFIG.csp })); }, loadConfig() { try { const savedConfig = JSON.parse(GM_getValue(`adblockConfig_${location.hostname}`, '{}')); if (savedConfig.modules) { Object.keys(CONFIG.modules).forEach(key => { if (savedConfig.modules.hasOwnProperty(key)) { CONFIG.modules[key] = savedConfig.modules[key]; } }); } if (savedConfig.csp) { Object.assign(CONFIG.csp, savedConfig.csp); } } catch (e) { console.error("加载配置失败:", e); } } }; function init() { UIController.init(); CSPManager.injectInitialCSP(); ModuleManager.init(); observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['id', 'class', 'style', 'src'] }); Processor.collectAds(); scheduleProcess(() => Processor.processElements()); window.addEventListener('scroll', throttle(() => { Processor.collectAds(); scheduleProcess(() => Processor.processElements()); }, CONFIG.performance.throttleScrollDelay)); window.addEventListener('resize', debounce(() => { Processor.collectAds(); scheduleProcess(() => Processor.processElements()); }, 150)); if (CONFIG.mobileOptimizations.lazyLoadImages) { document.addEventListener('DOMContentLoaded', () => { const images = document.querySelectorAll('img[data-src]'); images.forEach(img => { img.src = img.dataset.src; img.removeAttribute('data-src'); }); }); } if (CONFIG.mobileOptimizations.removeImagePlaceholders) { document.addEventListener('DOMContentLoaded', () => { const placeholders = document.querySelectorAll('.image-placeholder'); placeholders.forEach(placeholder => { placeholder.parentNode.removeChild(placeholder); }); }); } } function throttle(fn, delay) { let last = 0, timer = null; return function(...args) { const now = Date.now(); if (now - last >= delay) { fn.apply(this, args); last = now; } else { clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, args); last = now; }, delay - (now - last)); } }; } function debounce(fn, delay) { let timer = null; return function(...args) { clearTimeout(timer); timer = setTimeout(() => { try { fn.apply(this, args); } catch(e) { Logs.add('error', this, { type: 'debounceError', detail: e.message }); } }, delay); }; } ModuleManager.register('thirdPartyBlock', { originals: { createElement: document.createElement, setAttribute: Element.prototype.setAttribute, appendChild: Node.prototype.appendChild, fetch: window.fetch, xhrOpen: XMLHttpRequest.prototype.open, documentWrite: document.write, documentWriteln: document.writeln, insertAdjacentHTML: Element.prototype.insertAdjacentHTML }, blockedUrls: new Set(), enabled: false, hostCache: new Map(), init() { if (!CONFIG.modules.thirdPartyBlock) return this.disable(); this.enable(); }, enable() { if (this.enabled) return; this.enabled = true; document.createElement = this.wrapElementCreation.bind(this); Element.prototype.setAttribute = this.wrapSetAttribute.bind(this); Node.prototype.appendChild = this.wrapAppendChild.bind(this); window.fetch = this.wrapFetch.bind(this); XMLHttpRequest.prototype.open = this.wrapXHROpen.bind(this); document.write = this.wrapDocumentWrite.bind(this); document.writeln = this.wrapDocumentWriteln.bind(this); Element.prototype.insertAdjacentHTML = this.wrapInsertAdjacentHTML.bind(this); document.addEventListener('beforescriptexecute', this.handleBeforeScriptExecute.bind(this)); }, disable() { if (!this.enabled) return; this.enabled = false; document.createElement = this.originals.createElement; Element.prototype.setAttribute = this.originals.setAttribute; Node.prototype.appendChild = this.originals.appendChild; window.fetch = this.originals.fetch; XMLHttpRequest.prototype.open = this.originals.xhrOpen; document.write = this.originals.documentWrite; document.writeln = this.originals.documentWriteln; Element.prototype.insertAdjacentHTML = this.originals.insertAdjacentHTML; document.removeEventListener('beforescriptexecute', this.handleBeforeScriptExecute); }, handleInterception(target, url) { this.blockedUrls.add(url); Logs.add('thirdPartyBlock', target, { type: '第三方资源拦截', detail: `源: ${url.slice(0, 80)}` }); this.blockElement(target); return true; }, blockElement(element) { if (!element?.parentNode) return false; element.style.setProperty('display', 'none', 'important'); element.style.setProperty('visibility', 'hidden', 'important'); element.setAttribute('data-tpi-blocked', 'true'); setTimeout(() => { try { element.parentNode?.removeChild(element); } catch(e) { console.warn('元素移除失败:', e); } }, 100); return true; }, isThirdParty(url) { if (!url) return false; if (this.hostCache.has(url)) return this.hostCache.get(url); try { const currentHost = new URL(location.href).hostname; const resourceHost = new URL(url, location.href).hostname; const result = !(resourceHost.endsWith(`.${currentHost}`) || resourceHost === currentHost); this.hostCache.set(url, result); return result; } catch(e) { return true; } }, wrapSetAttribute(name, value, element = this) { if (name === 'src' && this.isThirdParty(value)) { return this.handleInterception(element, value); } return this.originals.setAttribute.call(element, name, value); }, wrapAppendChild(node, parent = this) { if (node.nodeType === 1) { const src = node.src || node.getAttribute('data-src') || ''; if (this.isThirdParty(src)) { this.handleInterception(node, src); return null; } } return this.originals.appendChild.call(parent, node); }, wrapElementCreation(tagName) { const element = this.originals.createElement.call(document, tagName); const lowerTag = tagName.toLowerCase(); if (['script', 'iframe', 'img'].includes(lowerTag)) { return new Proxy(element, { set: (target, prop, value) => { if (prop === 'src' && this.isThirdParty(value)) { this.handleInterception(target, value); } target[prop] = value; return true; } }); } return element; }, wrapFetch(input, init) { const url = typeof input === 'string' ? input : input?.url; if (this.isThirdParty(url)) { this.handleInterception({ tagName: 'FETCH' }, url); return Promise.reject(new Error('TPI: 第三方请求被拦截')); } return this.originals.fetch(input, init); }, wrapXHROpen(method, url) { if (this.isThirdParty(url)) { this.handleInterception({ tagName: 'XHR' }, url); this._blocked = true; return; } return this.originals.xhrOpen.call(this, method, url); }, handleHTMLInsertion(html, insertionType, originalFunction) { const tempDiv = document.createElement('div'); tempDiv.innerHTML = html; Array.from(tempDiv.querySelectorAll('script, iframe, img')).forEach(el => { const src = el.src || el.getAttribute('src') || ''; if (this.isThirdParty(src)) { this.handleInterception(el.cloneNode(), src); } }); return originalFunction(html); }, wrapDocumentWrite(content) { return this.handleHTMLInsertion(content, 'document.write', this.originals.documentWrite); }, wrapDocumentWriteln(content) { return this.handleHTMLInsertion(content, 'document.writeln', this.originals.documentWriteln); }, wrapInsertAdjacentHTML(position, html) { return this.handleHTMLInsertion(html, 'insertAdjacentHTML', (html) => this.originals.insertAdjacentHTML.call(this, position, html)); }, handleBeforeScriptExecute(e) { const script = e.target; const src = script.src || script.getAttribute('data-src') || ''; if (this.isThirdParty(src)) { e.preventDefault(); e.stopImmediatePropagation(); this.handleInterception(script, src); } }, check(el) { if (!CONFIG.modules.thirdPartyBlock) return false; const src = el.src || el.getAttribute('data-src') || ''; if (this.isThirdParty(src)) { const tagName = el.tagName; if (tagName === 'SCRIPT' || tagName === 'IFRAME' || tagName === 'IMG') { Logs.add('thirdPartyBlock', el, { type: '第三方资源拦截', detail: `源: ${src.slice(0, 80)}` }); return AdUtils.safeRemove(el, 'thirdPartyBlock', { type: '第三方资源拦截', detail: `源: ${src.slice(0, 80)}` }); } } return false; } }); ModuleManager.register('frameSystem', { check(el) { if (!CONFIG.modules.frameSystem) return false; if (el.tagName === 'IFRAME') { let depth = 0, parent = el; while ((parent = parent.parentElement) && depth <= CONFIG.protectionRules.maxFrameDepth) { if (parent.tagName === 'IFRAME') depth++; } if (depth > CONFIG.protectionRules.maxFrameDepth) { return AdUtils.safeRemove(el, 'frameSystem', { type: '深层嵌套框架', detail: `嵌套层级: ${depth}` }); } } return false; } }); ModuleManager.register('dynamicSystem', { checkedScripts: new Set(), originalCreateElement: null, scriptProxies: new WeakMap(), init() { if (!CONFIG.modules.dynamicSystem) return; this.originalCreateElement = document.createElement; document.createElement = this.wrapCreateElement.bind(this); this.patterns = [ REGEX.jsAdPattern, REGEX.adAttributes, /(advert|banner|popup|tracking)\.js(\?|$)/i, /\b(adsbygoogle|adservice|doubleclick)\b/ ]; }, wrapCreateElement(tagName) { const element = this.originalCreateElement.call(document, tagName); if (tagName.toLowerCase() === 'script') { return this.createScriptProxy(element); } return element; }, createScriptProxy(element) { const proxy = new Proxy(element, { set: (target, prop, value) => { if (prop === 'src') { if (this.checkScriptAttributes(value, target)) { this.blockImmediately(target); return true; } } target[prop] = value; return true; }, get: (target, prop) => { if (prop === 'src' && target.hasAttribute('data-ad-blocked')) { return ''; } return target[prop]; } }); this.scriptProxies.set(element, proxy); return proxy; }, checkScriptAttributes(src, element) { const isMalicious = this.patterns.some(pattern => pattern.test(src) || pattern.test(element.outerHTML) ); if (isMalicious) { Logs.add('dynamicSystem', element, { type: '预拦截脚本', detail: `特征匹配: ${src.slice(0, 80)}` }); return true; } return false; }, blockImmediately(scriptEl) { scriptEl.src = ''; scriptEl.type = 'nojs'; scriptEl.setAttribute('data-ad-blocked', 'true'); scriptEl.addEventListener('load', e => e.preventDefault(), true); scriptEl.addEventListener('error', e => e.preventDefault(), true); scriptEl.style.cssText += ';display:none!important;visibility:hidden!important;'; if (scriptEl.parentNode) { scriptEl.parentNode.removeChild(scriptEl); } Object.defineProperty(scriptEl, 'src', { get: () => '', set: () => {} }); }, check(el) { if (!CONFIG.modules.dynamicSystem) return false; const attrs = Array.from(el.attributes) .map(attr => `${attr.name}=${attr.value}`) .join(' '); if (REGEX.adAttributes.test(attrs)) { AdUtils.safeRemove(el, 'dynamicSystem', { type: '广告属性检测', detail: `特征属性: ${attrs.slice(0, 100)}` }); return true; } if (el.tagName === 'SCRIPT') { this.processScriptElement(el); return true; } if (el.tagName === 'A') { const href = el.getAttribute('href') || ''; if (href.startsWith('javascript:') && REGEX.jsAdPattern.test(href)) { return AdUtils.safeRemove(el, 'dynamicSystem', { type: '可疑JS链接广告', detail: `执行代码: ${href.slice(0, 100)}`, regex: ['window\.open|document\.write'] }); } } if (el.id && ( el.id.length > CONFIG.protectionRules.dynamicIdLength || REGEX.dynamicId.test(el.id) )) { AdUtils.safeRemove(el, 'dynamicSystem', { type: '可疑动态ID', detail: `ID特征: ${el.id.slice(0, 50)}` }); return true; } return false; }, processScriptElement(scriptEl) { if (scriptEl.dataset.adChecked) return; const src = scriptEl.src || ''; if (this.checkScriptAttributes(src, scriptEl)) { this.blockImmediately(scriptEl); return; } if (src.startsWith('http')) { this.analyzeRemoteScript(scriptEl); } else { this.analyzeInlineScript(scriptEl); } scriptEl.dataset.adChecked = 'true'; }, async analyzeRemoteScript(scriptEl) { try { const startTime = performance.now(); const response = await fetch(scriptEl.src, { cache: 'no-store', mode: 'no-cors' }); if (performance.now() - startTime > 200) { this.blockImmediately(scriptEl); return; } const reader = response.body.getReader(); let chunks = ''; while (true) { const { done, value } = await reader.read(); if (done) break; chunks += new TextDecoder().decode(value); if (this.detectInStream(chunks, scriptEl)) { reader.cancel(); this.blockImmediately(scriptEl); return; } } } catch (e) { this.blockImmediately(scriptEl); } }, analyzeInlineScript(scriptEl) { const content = scriptEl.textContent; if (this.patterns.some(p => p.test(content))) { this.blockImmediately(scriptEl); Logs.add('dynamicSystem', scriptEl, { type: '内联广告脚本', detail: `检测到: ${content.slice(0, 100)}...` }); } }, detectInStream(chunk, scriptEl) { const dangerPatterns = [ /document\.write\(['"]