// ==UserScript== // @name 广告终结者(实验性版本) // @namespace http://tampermonkey.net/ // @version 3.0.1 // @description 实验性版本,引入csp,可能存在bug // @author DeepSeek // @match *://*/* // @exclude *://*.bing.com/* // @exclude *://*.iqiyi.com/* // @exclude *://*.qq.com/* // @exclude *://*.v.qq.com/* // @exclude *://*.sohu.com/* // @exclude *://*.mgtv.com/* // @exclude *://*.ifeng.com/* // @exclude *://*.pptv.com/* // @exclude *://*.sina.com.cn/* // @exclude *://*.56.com/* // @exclude *://*.cntv.cn/* // @exclude *://*.tudou.com/* // @exclude *://*.baofeng.com/* // @exclude *://*.le.com/* // @exclude *://*.pps.tv/* // @exclude *://*.fun.tv/* // @exclude *://*.baidu.com/* // @exclude *://*.ku6.com/* // @exclude *://*.tvsou.com/* // @exclude *://*.kankan.com/* // @exclude *://*.douyu.com/* // @exclude *://*.weibo.com/* // @exclude *://*.people.com.cn/* // @exclude *://*.cctv.com/* // @exclude *://*.gdtv.com.cn/* // @exclude *://*.ahtv.cn/* // @exclude *://*.tvb.com/* // @exclude *://*.tvmao.com/* // @exclude *://*.douban.com/* // @exclude *://*.163.com/* // @exclude *://*.bilibili.com/* // @exclude *://*.gov.cn/* // @exclude *://*.thepaper.cn/* // @exclude *://*.xinhuanet.com/* // @exclude *://*.china.com/* // @exclude *://*.jianshu.com/* // @exclude *://*.amazon.cn/* // @exclude *://*.cnblogs.com/* // @exclude *://*.cnstock.com/* // @exclude *://*.baike.com/* // @exclude *://*.guokr.com/* // @exclude *://*.360doc.com/* // @exclude *://*.qiushibaike.com/* // @exclude *://*.zol.com.cn/* // @exclude *://*.pconline.com.cn/* // @exclude *://*.pcpop.com/* // @exclude *://*.it168.com/* // @exclude *://*.gfan.com/* // @exclude *://*.feng.com/* // @exclude *://*.xiaomi.cn/* // @exclude *://*.10086.cn/* // @exclude *://*.10010.com/* // @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+|免广告|偷拍|黑料|点击下载/, obfuscatedAds: { adUrl: /(?:\/[a-z0-9]{12,}\/|(?:adservice|tracking|promo)\.[a-z]{3,10}\.(?:com|net))(?:\/|$)(?:ads?|promo|banner|track|stat|click|log)/i, hashedFile: /[a-f0-9]{32,}\.(?:js|css|html)|(?:[a-z0-9]{20,}|[A-Z0-9]{20,})\.(?:js|css|html)/i, adFunction: /(?:get|fetch|load|show|display|render|set(?:Timeout|Interval))\(.*?(?:Ad|Banner|Popup)/i, dynamicCode: /(?:eval|new\s+Function|Function)\(.*?(?:decodeURIComponent|atob|fromCharCode)/i, iframeWrite: /(?:\.createElement\(['"]iframe['"]\)|document\.write\([\s\S]*?<(script|iframe|img).*?(src|href)=['"]?(?:[^'">]*?(?:ad|banner|popup))['"]?)/i, base64Data: /(?:data:.*?(?:ad|banner)|(?:[A-Za-z0-9+/]{4}){10,}(?:ad|banner)|data:image\/(?:png|jpeg|gif);base64,)/i, inlineScript: /]*>[\s\S]*?(?:ad|banner|popup|[a-zA-Z0-9]{8})[\s\S]*?<\/script>/i, encodedString: /(?:base64|hex)\([^)]+\)/i, unicodeCharEncoded: /\\u[0-9a-f]{4}(?:\\u[0-9a-f]{4}){3,}/gi } }; const CONFIG = { modules: { main: false, dynamicSystem: false, layoutSystem: false, frameSystem: false, mediaSystem: false, textSystem: false, thirdPartyBlock: false, obfuscatedAdSystem: false, floating: false }, protectionRules: { dynamicIdLength: 18, zIndexThreshold: 100, maxFrameDepth: 4, textAdKeywords: ['限时优惠','立即下载','微信','vx:','telegram','偷拍','黑料','扫码关注','点击下载'] }, csp: { enabled: false, rules: [ { id: 1, name: '脚本限制', rule: "script-src 'self'", enabled: true }, { id: 2, name: '样式限制', rule: "style-src 'self'", enabled: false }, { id: 3, name: '图片限制', rule: "img-src 'self'", enabled: false }, { id: 4, name: '字体限制', rule: "font-src 'self'", enabled: false }, { id: 5, name: '连接限制', rule: "connect-src 'self'", enabled: false }, { id: 6, name: '框架限制', rule: "frame-src 'none'", enabled: false } ] }, mobileOptimizations: { lazyLoadImages: true, removeImagePlaceholders: true }, moduleNames: { dynamicSystem: '动态检测系统', layoutSystem: '布局检测系统', frameSystem: '框架过滤系统', mediaSystem: '媒体检测系统', textSystem: '文本广告检测', thirdPartyBlock: '第三方拦截', obfuscatedAdSystem: '混淆广告检测', floating: '浮动广告检测' }, modulePriority: [ 'thirdPartyBlock', 'frameSystem', 'obfuscatedAdSystem', 'dynamicSystem', 'mediaSystem', 'layoutSystem', 'textSystem', 'floating' ] }; 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() }; 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'); return true; } catch(e) { console.warn('元素隐藏失败:', e); return false; } } }; 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; } }; 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 (['SCRIPT', 'IFRAME'].includes(el.tagName)) { 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; }, checkObfuscatedAdSystem(el) { if (!CONFIG.modules.obfuscatedAdSystem) return false; const scripts = el.getElementsByTagName('script'); for (let script of scripts) { const src = script.src || script.innerHTML; if (Object.values(REGEX.obfuscatedAds).some(r => r.test(src))) { return AdUtils.safeRemove(script, 'obfuscatedAdSystem', { type: '混淆广告检测', detail: `源: ${src.slice(0, 80)}` }); } } 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); if (zIndex > CONFIG.protectionRules.zIndexThreshold) { return AdUtils.safeRemove(el, 'layoutSystem', { type: '高堆叠元素', detail: `z-index=${zIndex}` }); } const rect = el.getBoundingClientRect(); const isVisible = rect.width > 0 && rect.height > 0; if (!isVisible && el.children.length > 0) { return AdUtils.safeRemove(el, 'layoutSystem', { type: '隐藏容器元素', detail: '包含子元素的不可见容器' }); } return false; }, checkTextSystem(el) { if (!CONFIG.modules.textSystem) return false; const text = el.textContent?.toLowerCase() || ''; if (REGEX.textAdKeywords.test(text)) { return AdUtils.safeRemove(el, 'textSystem', { type: '文本广告', detail: `关键词: ${text.slice(0, 80)}` }); } 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 > 600000) { perf.styleCache.clear(); perf.lastStyleCacheClear = Date.now(); } const key = el.nodeName + Array.from(el.classList).join(''); if (!perf.styleCache.has(key)) { perf.styleCache.set(key, getComputedStyle(el)); } return perf.styleCache.get(key); } }; const Processor = { collectAds() { const elements = [...document.getElementsByTagName('*')]; perf.adElements.clear(); elements.forEach(element => { if (perf.processed.has(element)) return; for (const moduleKey of CONFIG.modulePriority) { if (Detector.checkModule(moduleKey, element)) { perf.adElements.add(element); perf.processed.add(element); break; } } }); } }; const observer = new MutationObserver(mutations => { if (!CONFIG.modules.main) return; for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (node.nodeType === 1 && !perf.processed.has(node)) { perf.pendingTasks.push(node); } } } scheduleProcess(); }); function processElements() { const start = performance.now(); let processed = 0; while (perf.pendingTasks.length && processed < 40) { const element = perf.pendingTasks.shift(); if (!perf.processed.has(element)) { Processor.collectAds(); processed++; } if (performance.now() - start > 10) break; } if (perf.pendingTasks.length) { scheduleProcess(); } else { perf.isProcessing = false; } } function scheduleProcess() { if (!perf.isProcessing && CONFIG.modules.main) { perf.isProcessing = true; requestIdleCallback(processElements, { timeout: 2500 }); } } const UIController = { init() { this.loadConfig(); this.registerMainSwitch(); this.registerModuleSwitches(); this.registerLogCommands(); this.registerCSPCommands(); this.registerResetCommand(); StyleManager.toggle(CONFIG.modules.main); if (CONFIG.modules.main) CSPManager.inject(); }, 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); newState ? CSPManager.inject() : CSPManager.remove(); 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 => `[${log.timestamp}] ${log.module}\n元素: ${log.element}\n内容: ${log.content}` ).join('\n\n'); alert(`广告拦截日志(最近${Logs.maxLogs}条):\n\n${logText || '暂无日志'}`); }, manageCSP() { const rulesDisplay = CONFIG.csp.rules .map(r => `${r.id}. ${r.name} (${r.rule}) (${r.enabled ? '✅' : '❌'})`) .join('\n'); let input = prompt( `当前CSP策略状态: ${CONFIG.csp.enabled ? '✅ 已启用' : '❌ 已禁用'}\n\n` + "当前策略规则:\n" + rulesDisplay + "\n\n" + "输入选项:\n1. 开启策略 (输入 'enable')\n2. 关闭策略 (输入 'disable')\n" + "修改规则示例:\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: { main: false, dynamicSystem: false, layoutSystem: false, frameSystem: false, mediaSystem: false, textSystem: false, thirdPartyBlock: false, obfuscatedAdSystem: false, floating: false }, csp: { enabled: false, rules: [ { id: 1, name: '脚本限制', rule: "script-src 'self'", enabled: true }, { id: 2, name: '样式限制', rule: "style-src 'self'", enabled: true }, { id: 3, name: '图片限制', rule: "img-src 'self'", enabled: true }, { id: 4, name: '字体限制', rule: "font-src 'self'", enabled: false }, { id: 5, name: '连接限制', rule: "connect-src 'self'", enabled: false }, { id: 6, name: '框架限制', rule: "frame-src 'none'", enabled: false } ] } }); 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 initSystem() { UIController.init(); observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['id', 'class', 'style', 'src', 'href'] }); if (CONFIG.mobileOptimizations.lazyLoadImages) { document.addEventListener('scroll', () => { [...document.getElementsByTagName('img')].forEach(img => { if (img.getBoundingClientRect().top < window.innerHeight + 500) { img.src = img.dataset.src || img.src; } }); }, { passive: true }); } window.addEventListener('beforeunload', () => { UIController.saveConfig(); }); })(); })();