// ==UserScript== // @name 广告终结者 // @namespace http://tampermonkey.net/ // @version 3.4 // @description 修复第三方拦截可能存在的潜在bug和csp策略管理(现在同时启用多个规则应该不会产生冲突) // @author DeepSeek // @match *://*/* // @license MIT // @grant GM_registerMenuCommand // @grant GM_notification // @grant GM_getValue // @grant GM_setValue // @run-at document-start // ==/UserScript== (function() { 'use strict'; const HOSTNAME = location.hostname.replace(/\./g, '\\.'); const REGEX = { dynamicId: /^(?:[0-9a-f]{16,}|[\dA-F-]{20,})$/i, adAttributes: /(?:ad(?:s|vert|vertisement|box|frame|container|wrap|content|label)?|推广|广告|gg|sponsor|推荐|guanggao|syad|bfad|弹窗|悬浮|葡京|banner|pop(?:up|under)|track(?:ing)?)[_-]?/i, thirdParty: new RegExp(`^(https?:\\/\\/(.*\\.)?${HOSTNAME}(\\/|$)|data:|about:blank)`, 'i'), textAdKeywords: /限时(?:优惠|免费)|立即(?:下载|注册|咨询)|微信(?:号|客服)?[::]?|vx[::]\w+|telegram[::]\w+|免广告|偷拍|黑料|点击下载/ }; const FIRST_SCRIPT_DEFAULT_KEYWORDS = [ '.substr(10)', '.substr(22)', 'htmlAds', 'ads_codes', '{return void 0!==b[a]?b[a]:a}).join("")}', '-${scripts[randomIndex]}', '/image/202${scripts[Math.random()', '"https://"+Date.parse(new Date())+', '"https://"+(new Date().getDate())+', 'new Function(t)()', 'new Function(b)()', 'new Function(\'d\',e)', 'Math.floor(2147483648 * Math.random());', 'Math.floor(Math.random()*url.length)', '&&navigator[', '=navigator;', 'navigator.platform){setTimeout(function', 'disableDebugger', 'sojson.v', '<\\/\'+\'s\'+\'c\'+\'ri\'+\'pt\'+\'>\');', '\');', ]; const FIRST_SCRIPT_REGEX = { obfuscatedAds: { randomPathScript: / ({ ...rule })) }, mobileOptimizations: { lazyLoadImages: true, removeImagePlaceholders: true }, moduleNames: { dynamicSystem: '动态检测系统', layoutSystem: '布局检测系统', frameSystem: '框架过滤系统', mediaSystem: '媒体检测系统', textSystem: '文本广告检测', thirdPartyBlock: '第三方拦截', obfuscatedAdSystem: '混淆广告检测', floating: '浮动广告检测' }, modulePriority: [ 'thirdPartyBlock', 'frameSystem', 'obfuscatedAdSystem', 'dynamicSystem', 'mediaSystem', 'layoutSystem', 'textSystem', 'floating' ], keywordConfig: { useDefault: true, custom: [], blockThreshold: 2 }, performance: { highRiskModules: ['thirdPartyBlock', 'frameSystem', 'obfuscatedAdSystem'], visibleAreaPriority: true, styleCacheTimeout: 600000, mutationProcessLimit: 40, mutationProcessTimeLimit: 10, idleCallbackTimeout: 2500, throttleScrollDelay: 200, debounceMutationDelay: 300, longTaskThreshold: 500, degradeThreshold: 3 }, obfuscatedAdConfig: { useDefault: true, customKeywords: [], customRegex: [], blockThreshold: 2 } }; 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) { return element.outerHTML.slice(0, 100) + (element.outerHTML.length > 100 ? '...' : ''); }, 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 ThirdPartyInterceptor = { originals: { createElement: document.createElement, setAttribute: Element.prototype.setAttribute, appendChild: Node.prototype.appendChild, fetch: window.fetch, xhrOpen: XMLHttpRequest.prototype.open }, blockedUrls: new Set(), init() { if (!CONFIG.modules.thirdPartyBlock) return; document.createElement = function(tagName) { return ThirdPartyInterceptor.wrapElementCreation(tagName); }; Element.prototype.setAttribute = function(name, value) { const _this = this; if (name === 'src' && ThirdPartyInterceptor.shouldBlock(_this, value)) { ThirdPartyInterceptor.blockedUrls.add(value); Logs.add('thirdPartyBlock', _this, { type: '第三方资源', detail: value }); return; } return ThirdPartyInterceptor.originals.setAttribute.call(_this, name, value); }; Node.prototype.appendChild = function(node) { const _this = this; if (node.nodeType === 1 && ThirdPartyInterceptor.shouldBlock(node, node.src)) { ThirdPartyInterceptor.blockedUrls.add(node.src); Logs.add('thirdPartyBlock', node, { type: '动态添加第三方元素', detail: node.src }); return null; } return ThirdPartyInterceptor.originals.appendChild.call(_this, node); }; window.fetch = function(input, init) { const url = typeof input === 'string' ? input : (input && input.url) ? input.url : ''; if (ThirdPartyInterceptor.isThirdParty(url)) { ThirdPartyInterceptor.blockedUrls.add(url); Logs.add('thirdPartyBlock', {tagName: 'FETCH', id: '', className: ''}, { type: '第三方资源', detail: url }); return Promise.reject(new Error('第三方资源请求被拦截')); } return ThirdPartyInterceptor.originals.fetch(input, init); }; XMLHttpRequest.prototype.open = function(method, url) { const _this = this; if (ThirdPartyInterceptor.isThirdParty(url)) { _this._blocked = true; ThirdPartyInterceptor.blockedUrls.add(url); Logs.add('thirdPartyBlock', {tagName: 'XHR', id: '', className: ''}, { type: '第三方资源', detail: url }); return; } return ThirdPartyInterceptor.originals.xhrOpen.apply(_this, arguments); }; }, wrapElementCreation(tagName) { const element = ThirdPartyInterceptor.originals.createElement.call(document, tagName); if (['script', 'iframe', 'img'].includes(tagName.toLowerCase())) { return new Proxy(element, { set(target, prop, value) { if (prop === 'src' && ThirdPartyInterceptor.isThirdParty(value)) { ThirdPartyInterceptor.blockedUrls.add(value); Logs.add('thirdPartyBlock', target, { type: '代理对象属性设置', detail: value }); return true; } target[prop] = value; return true; } }); } return element; }, shouldBlock(element, url) { if (!CONFIG.modules.thirdPartyBlock) return false; if (!url) return false; const tagName = element.tagName.toLowerCase(); const shouldCheck = ['script', 'iframe', 'img'].includes(tagName); return shouldCheck && ThirdPartyInterceptor.isThirdParty(url); }, isThirdParty(url) { try { if (!url || url.startsWith('data:') || url.startsWith('blob:')) return false; const currentHost = new URL(location.href).hostname; const resourceHost = new URL(url, location.href).hostname; return !resourceHost.endsWith('.' + currentHost) && resourceHost !== currentHost; } catch (e) { return true; } } }; 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'); document.head.appendChild(this.styleElement); }, 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; document.head.appendChild(meta); }, 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; document.head.appendChild(meta); } } }; const Detector = { checkModule(moduleKey, element) { if (!CONFIG.modules.main || !CONFIG.modules[moduleKey]) return false; return this[`check${moduleKey.charAt(0).toUpperCase() + moduleKey.slice(1)}`](element); }, checkThirdPartyBlock(el) { if (!CONFIG.modules.thirdPartyBlock) return false; if (el.tagName === 'SCRIPT') { const src = el.src || el.getAttribute('data-src') || ''; if (!REGEX.thirdParty.test(src)) { FirstScriptAdUtils.safeRemove(el, { type: '第三方资源拦截', detail: `源: ${src.slice(0, 80)}`, moduleKey: 'thirdPartyBlock' }); return true; } } else if (el.tagName === 'IFRAME') { const src = el.src || el.getAttribute('data-src') || ''; if (!REGEX.thirdParty.test(src)) { return AdUtils.safeRemove(el, 'thirdPartyBlock', { type: '第三方资源拦截', detail: `源: ${src.slice(0, 80)}` }); } } return false; }, checkFrameSystem(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; }, checkDynamicSystem(el) { if (!CONFIG.modules.dynamicSystem) return false; const attrs = el.getAttributeNames().map(name => `${name}=${el.getAttribute(name)}` ).join(' '); if (REGEX.adAttributes.test(attrs)) { return AdUtils.safeRemove(el, 'dynamicSystem', { type: '广告属性检测', detail: `属性: ${attrs.slice(0, 100)}` }); } if (el.id && ( el.id.length > CONFIG.protectionRules.dynamicIdLength || REGEX.dynamicId.test(el.id) )) { return AdUtils.safeRemove(el, 'dynamicSystem', { type: '动态ID检测', detail: `异常ID: ${el.id}` }); } return false; }, checkMediaSystem(el) { if (!CONFIG.modules.mediaSystem) return false; if (el.tagName === 'IMG' && (el.src.includes('ad') || el.src.match(/\.(gif|webp)(\?|$)/))) { return AdUtils.safeRemove(el, 'mediaSystem', { type: '图片广告', detail: `图片源: ${el.src.slice(0, 50)}` }); } const style = this.getCachedStyle(el); const rect = el.getBoundingClientRect(); if (['fixed', 'sticky'].includes(style.position)) { const isTopBanner = rect.top < 50; const isBottomBanner = rect.bottom > window.innerHeight - 50; if (isTopBanner || isBottomBanner) { return AdUtils.safeRemove(el, 'mediaSystem', { type: '浮动广告', detail: `位置: ${rect.top}px` }); } } return false; }, checkLayoutSystem(el) { if (!CONFIG.modules.layoutSystem) return false; const style = this.getCachedStyle(el); const zIndex = parseInt(style.zIndex, 10); if (!isNaN(zIndex) && zIndex > CONFIG.protectionRules.zIndexThreshold) { return AdUtils.safeRemove(el, 'layoutSystem', { type: '高Z轴元素', detail: `z-index: ${zIndex}` }); } if (style.overflow === 'hidden' && el.scrollHeight > el.clientHeight) { return AdUtils.safeRemove(el, 'layoutSystem', { type: '隐藏溢出内容的容器' }); } if (style.visibility === 'hidden' || style.display === 'none') { return AdUtils.safeRemove(el, 'layoutSystem', { type: '不可见容器' }); } if (el.children.length === 0 && el.textContent.trim() === '' && style.width === '0px' && style.height === '0px') { return AdUtils.safeRemove(el, 'layoutSystem', { type: '空的不可见容器' }); } if (el.children.length > 0 && Array.from(el.children).every(child => child.style.display === 'none' || child.style.visibility === 'hidden')) { return AdUtils.safeRemove(el, 'layoutSystem', { type: '子元素的不可见容器' }); } return false; }, checkTextSystem(el) { if (!CONFIG.modules.textSystem) return false; const text = el.textContent?.toLowerCase() || ''; const match = text.match(REGEX.textAdKeywords); if (match) { return AdUtils.safeRemove(el, 'textSystem', { type: '文本广告', detail: `触发规则: ${match}`, keywords: [match] }); } return false; }, checkFloating(el) { if (!CONFIG.modules.floating) return false; const style = this.getCachedStyle(el); if (['fixed', 'sticky'].includes(style.position)) { return AdUtils.safeRemove(el, 'floating', { type: '浮动元素', detail: `定位方式: ${style.position}` }); } return false; }, 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 rect = el.getBoundingClientRect(); return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); } }; const Processor = { collectAds() { const elements = [...document.getElementsByTagName('*')]; perf.adElements.clear(); elements.forEach(element => { if (perf.processed.has(element)) return; 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 (moduleKey === 'obfuscatedAdSystem') continue; if (Detector.checkModule(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 (Detector.checkModule(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; for (let mutation of mutations) { if (mutationCount >= CONFIG.performance.mutationProcessLimit || 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); } } }); } else if (mutation.type === 'attributes') { if (!perf.processed.has(mutation.target)) { perf.adElements.add(mutation.target); } } mutationCount++; } scheduleProcess(); }); let scheduledProcess = null; const debouncedScheduleProcess = debounce(scheduleProcess, CONFIG.performance.debounceMutationDelay); const FirstScriptAdUtils = { safeRemove(element, reason) { if (!element?.parentNode || element.dataset.firstScriptAdRemoved) return false; try { const moduleKey = reason.moduleKey || 'obfuscatedAdSystem'; Logs.add(moduleKey, element, reason); element.parentNode.removeChild(element); element.dataset.firstScriptAdRemoved = true; return true; } catch (e) { console.warn('元素移除失败:', e); return false; } }, checkKeywords(content) { const keywords = [ ...(CONFIG.obfuscatedAdConfig.useDefault ? FIRST_SCRIPT_DEFAULT_KEYWORDS : []), ...CONFIG.obfuscatedAdConfig.customKeywords ]; return keywords.filter(k => content.includes(k)); }, checkRegex(content) { const regexList = [ ...(CONFIG.obfuscatedAdConfig.useDefault ? Object.values(FIRST_SCRIPT_REGEX.obfuscatedAds) : []), ...CONFIG.obfuscatedAdConfig.customRegex.map(r => new RegExp(r.pattern, r.flags)) ]; return regexList.filter(r => r.test(content)); } }; const FirstScriptObfuscatedAdDetector = { observer: null, init() { this.observer = new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType === 1) this.processElement(node); }); }); }); this.observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['id', 'class', 'style', 'src'] }); this.scanExistingElements(); }, scanExistingElements() { document.querySelectorAll('script, iframe, div, span, img').forEach(el => { this.processElement(el); }); }, processElement(element) { if (element.dataset.firstScriptProcessed) return; element.dataset.firstScriptProcessed = true; const content = element.innerHTML || element.src || ''; const matchedKeywords = FirstScriptAdUtils.checkKeywords(content); const matchedRegex = FirstScriptAdUtils.checkRegex(content); if (matchedKeywords.length + matchedRegex.length >= CONFIG.obfuscatedAdConfig.blockThreshold) { FirstScriptAdUtils.safeRemove(element, { type: '规则匹配', keywords: matchedKeywords, regex: matchedRegex.map(r => r.toString()) }); } } }; 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, obfuscatedAdConfig: { ...CONFIG.obfuscatedAdConfig, customRegex: CONFIG.obfuscatedAdConfig.customRegex.map(r => ({ pattern: r.pattern, flags: r.flags })) } })); }, 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); } if (savedConfig.obfuscatedAdConfig) { CONFIG.obfuscatedAdConfig.useDefault = savedConfig.obfuscatedAdConfig.useDefault; CONFIG.obfuscatedAdConfig.customKeywords = savedConfig.obfuscatedAdConfig.customKeywords; CONFIG.obfuscatedAdConfig.customRegex = savedConfig.obfuscatedAdConfig.customRegex.map(r => new RegExp(r.pattern, r.flags)); CONFIG.obfuscatedAdConfig.blockThreshold = savedConfig.obfuscatedAdConfig.blockThreshold; } } catch (e) { console.error("加载配置失败:", e); } } }; function init() { UIController.init(); CSPManager.injectInitialCSP(); ThirdPartyInterceptor.init(); if (CONFIG.modules.obfuscatedAdSystem) { FirstScriptObfuscatedAdDetector.init(); } observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['id', 'class', 'style', 'src'] }); Processor.collectAds(); scheduleProcess(); window.addEventListener('scroll', throttle(() => { Processor.collectAds(); scheduleProcess(); }, CONFIG.performance.throttleScrollDelay)); window.addEventListener('resize', debounce(() => { Processor.collectAds(); scheduleProcess(); }, 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); }; } function isElementInViewport(el) { if (!el || !el.getBoundingClientRect) return false; const rect = el.getBoundingClientRect(); return ( rect.top <= (window.innerHeight || document.documentElement.clientHeight) && rect.left <= (window.innerWidth || document.documentElement.clientWidth) && rect.bottom >= 0 && rect.right >= 0 ); } function 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); perf.record('longTask', performance.now()); } } 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.bind(null, { timeRemaining: () => 50, didTimeout: true }), 50); } } }; } init(); })();