// ==UserScript== // @name 小说漫画网页广告拦截器 // @namespace http://tampermonkey.net/ // @version 4.1 // @author DeepSeek&Gemini // @description 一个手机端via浏览器能用的强大的广告拦截器 // @match *://*/* // @license MIT // @grant unsafeWindow // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_notification // @grant GM_xmlhttpRequest // @run-at document-start // @downloadURL none // ==/UserScript== (function() { 'use strict'; const DEFAULT_MODULE_STATE = { specialUA: true, removeInlineScripts: false, removeExternalScripts: false, interceptThirdPartyResources: false, manageCSP: false, }; const MODULE_NAMES = { specialUA: '添加特殊UA', removeInlineScripts: '移除内嵌脚本', removeExternalScripts: '移除外联脚本', interceptThirdPartyResources: '拦截第三方资源', manageCSP: 'CSP策略管理', }; const DEFAULT_CSP_RULES_TEMPLATE = [ { id: 1, name: '仅允许同源脚本', rule: "script-src 'self'", enabled: true }, { id: 2, name: '禁止内联脚本', rule: "script-src 'unsafe-inline'", enabled: false }, { id: 3, name: '禁止eval执行', 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_STORAGE_KEY = `customAdBlockerConfig_${location.hostname}`; let currentConfig = { modules: { ...DEFAULT_MODULE_STATE }, cspRules: DEFAULT_CSP_RULES_TEMPLATE.map(rule => ({ ...rule })), whitelist: {}, }; const Utils = { throttle(fn, delay) { let last = 0; return function(...args) { const now = Date.now(); if (now - last >= delay) { fn.apply(this, args); last = now; } }; }, debounce(fn, delay) { let timer = null; return function(...args) { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), delay); }; }, truncateString(str, maxLength) { if (typeof str !== 'string') return ''; if (str.length <= maxLength) return str; return str.slice(0, maxLength) + '...'; }, getCurrentHostname() { return location.hostname; }, isElement(el) { return el instanceof Element; }, getScriptContentPreview(scriptElement) { if (!scriptElement || scriptElement.tagName !== 'SCRIPT') return ''; return Utils.truncateString(scriptElement.textContent.replace(/\s+/g, ' '), 100); }, getIframeSrcPreview(iframeElement) { if (!iframeElement || iframeElement.tagName !== 'IFRAME') return ''; return Utils.truncateString(iframeElement.src, 100); }, isThirdPartyHost(url, currentHost) { if (!url || typeof url !== 'string') return false; if (url.startsWith('data:') || url.startsWith('blob:') || url.startsWith('about:blank')) return false; try { const resourceHost = new URL(url, location.href).hostname; return !(resourceHost.endsWith(`.${currentHost}`) || resourceHost === currentHost); } catch (e) { return true; } }, getResourceURL(url) { if (!url) return ''; try { return new URL(url, location.href).href; } catch (e) { return url; } } }; const LogManager = { logs: [], maxLogs: 100, logEntryData: {}, loggedContentIdentifiers: new Set(), add(moduleKey, element, reason) { if (!currentConfig.modules.interceptThirdPartyResources && !currentConfig.modules.removeInlineScripts && !currentConfig.modules.removeExternalScripts) { return; } if (!Utils.isElement(element) && element !== null) return; const currentDomain = Utils.getCurrentHostname(); if (Whitelisting.isElementWhitelisted(element) || Whitelisting.isReasonWhitelisted(reason)) { return; } let elementIdentifier = '[未知元素]'; let interceptedContent = '[无法获取内容]'; let contentIdentifier = ''; if (Utils.isElement(element)) { const tagName = element.tagName; const id = element.id ? `#${element.id}` : ''; const className = element.className ? `.${element.className.split(/\s+/).join('.')}` : ''; elementIdentifier = `${tagName}${id}${className}`; if (tagName === 'SCRIPT') { if (element.src) { interceptedContent = `SRC: ${Utils.truncateString(element.src, 100)}`; contentIdentifier = `SCRIPT_SRC: ${Utils.truncateString(element.src, 100)}`; } else { interceptedContent = Utils.getScriptContentPreview(element); contentIdentifier = `SCRIPT_CONTENT: ${Utils.truncateString(element.textContent.replace(/\s+/g, ' '), 100)}`; } } else if (tagName === 'IFRAME') { interceptedContent = Utils.getIframeSrcPreview(element); contentIdentifier = `IFRAME_SRC: ${Utils.truncateString(element.src, 100)}`; } else if (tagName === 'IMG') { const src = element.src || element.dataset.src; interceptedContent = Utils.truncateString(src || '', 100); if (src) { contentIdentifier = `IMG_SRC: ${Utils.truncateString(src, 100)}`; } } else if (tagName === 'A') { interceptedContent = Utils.truncateString(element.href || '', 100); if (element.href) { contentIdentifier = `A_HREF: ${Utils.truncateString(element.href, 100)}`; } } else { interceptedContent = Utils.truncateString(element.outerHTML, 100); } } else if (reason && typeof reason.detail === 'string') { interceptedContent = Utils.truncateString(reason.detail, 100); if (reason.type) { elementIdentifier = `[${reason.type}]`; } if (reason.detail.startsWith('SRC:')) { contentIdentifier = `${reason.type || 'INTERCEPTED'}_SRC: ${Utils.truncateString(reason.detail.substring(4).trim(), 100)}`; } else if (reason.detail.startsWith('URL:')) { contentIdentifier = `${reason.type || 'INTERCEPTED'}_URL: ${Utils.truncateString(reason.detail.substring(5).trim(), 100)}`; } else { contentIdentifier = `LOG_DETAIL: ${Utils.truncateString(reason.detail, 100)}`; } } if (!contentIdentifier) return; if (this.loggedContentIdentifiers.has(contentIdentifier)) { return; } const logId = this.logs.length + 1; this.logs.push({ id: logId, module: MODULE_NAMES[moduleKey] || moduleKey, element: elementIdentifier, content: interceptedContent, }); this.logEntryData[logId] = { contentIdentifier: contentIdentifier, element: element, reason: reason, }; this.loggedContentIdentifiers.add(contentIdentifier); if (this.logs.length > this.maxLogs) { const removedLogId = this.logs[0].id; const removedLogEntry = this.logs.shift(); delete this.logEntryData[removedLogId]; const removedContentIdentifier = (() => { if (removedLogEntry.content.startsWith('SRC: ')) return `SCRIPT_SRC: ${removedLogEntry.content.substring(5)}`; if (removedLogEntry.content.startsWith('内容: ')) return `SCRIPT_CONTENT: ${removedLogEntry.content.substring(4)}`; if (removedLogEntry.content.startsWith('URL: ')) return `FETCH_URL: ${removedLogEntry.content.substring(5)}`; return removedLogEntry.content; })(); this.loggedContentIdentifiers.delete(removedContentIdentifier); } }, showInAlert() { const currentDomain = Utils.getCurrentHostname(); const logEntries = this.logs.map(log => `序号: ${log.id}\n` + `模块: ${log.module}\n` + `元素: ${log.element}\n` + `内容: ${log.content}` ).join('\n\n'); const promptMessage = `广告拦截日志(最近${this.logs.length}条):\n\n${logEntries || '暂无日志'}\n\n` + `--- 白名单操作 ---\n` + `输入序号(如 1-3, 1,3,5)将对应内容加入当前域名 (${currentDomain}) 白名单。\n` + `输入 0 清空当前域名 (${currentDomain}) 的所有白名单条目。\n` + `请输入您的选择:`; let input = prompt(promptMessage, ""); if (input === null) return; input = input.trim(); if (input === "0") { if (confirm(`确定清空当前域名 (${currentDomain}) 的白名单吗?`)) { Whitelisting.clearDomainWhitelist(currentDomain); StorageManager.saveConfig(); alert("当前域名的白名单已清空。页面将刷新。"); location.reload(); } } else { const indicesToWhitelist = new Set(); const parts = input.replace(/,/g, ',').split(/[\s,]+/); for (const part of parts) { if (part.includes('-')) { const range = part.split('-').map(Number); if (range.length === 2 && !isNaN(range[0]) && !isNaN(range[1])) { const start = Math.min(range[0], range[1]); const end = Math.max(range[0], range[1]); for (let i = start; i <= end; i++) { indicesToWhitelist.add(i); } } } else { const num = parseInt(part, 10); if (!isNaN(num)) { indicesToWhitelist.add(num); } } } let addedCount = 0; indicesToWhitelist.forEach(index => { const logEntryInfo = this.logEntryData[index]; if (logEntryInfo && logEntryInfo.contentIdentifier) { const { contentIdentifier, element, reason } = logEntryInfo; if (!Whitelisting.isContentWhitelisted(currentDomain, contentIdentifier)) { Whitelisting.add(currentDomain, contentIdentifier); addedCount++; } } }); if (addedCount > 0) { StorageManager.saveConfig(); alert(`${addedCount} 项内容已添加到当前域名 (${currentDomain}) 的白名单。页面将刷新。`); location.reload(); } else if (input.length > 0) { alert("未找到匹配的日志序号或内容已在白名单中。"); } } } }; const Whitelisting = { isElementWhitelisted(element) { if (!element || !Utils.isElement(element)) return false; const currentDomain = Utils.getCurrentHostname(); const elementContent = (() => { if (element.tagName === 'SCRIPT' && !element.src) return `SCRIPT_CONTENT: ${Utils.getScriptContentPreview(element)}`; if (element.tagName === 'SCRIPT' && element.src) return `SCRIPT_SRC: ${Utils.truncateString(element.src, 100)}`; if (element.tagName === 'IFRAME' && element.src) return `IFRAME_SRC: ${Utils.truncateString(element.src, 100)}`; if (element.tagName === 'IMG' && (element.src || element.dataset.src)) return `IMG_SRC: ${Utils.truncateString(element.src || element.dataset.src, 100)}`; if (element.tagName === 'A' && element.href) return `A_HREF: ${Utils.truncateString(element.href, 100)}`; return null; })(); if (elementContent && currentDomain && currentConfig.whitelist[currentDomain]) { return currentConfig.whitelist[currentDomain].includes(elementContent); } return false; }, isReasonWhitelisted(reason) { if (!reason || !reason.detail || typeof reason.detail !== 'string') return false; const currentDomain = Utils.getCurrentHostname(); const identifier = (() => { if (reason.detail.startsWith('SRC:')) { return `${reason.type || 'INTERCEPTED'}_SRC: ${Utils.truncateString(reason.detail.substring(4).trim(), 100)}`; } else if (reason.detail.startsWith('URL:')) { return `${reason.type || 'INTERCEPTED'}_URL: ${Utils.truncateString(reason.detail.substring(5).trim(), 100)}`; } return null; })(); if (identifier && currentDomain && currentConfig.whitelist[currentDomain]) { return currentConfig.whitelist[currentDomain].includes(identifier); } return false; }, isContentWhitelisted(domain, content) { if (!domain || !content) return false; return currentConfig.whitelist[domain] && currentConfig.whitelist[domain].includes(content); }, add(domain, content) { if (!domain || !content || content.trim() === '') return; if (!currentConfig.whitelist[domain]) { currentConfig.whitelist[domain] = []; } if (!currentConfig.whitelist[domain].includes(content)) { currentConfig.whitelist[domain].push(content); } }, clearDomainWhitelist(domain) { if (currentConfig.whitelist[domain]) { currentConfig.whitelist[domain] = []; } }, clearAllWhitelists() { currentConfig.whitelist = {}; } }; const StorageManager = { loadConfig() { try { const savedConfig = JSON.parse(GM_getValue(CONFIG_STORAGE_KEY, '{}')); if (savedConfig) { if (savedConfig.modules) { Object.assign(currentConfig.modules, savedConfig.modules); } if (savedConfig.cspRules) { currentConfig.cspRules = savedConfig.cspRules.map(rule => ({ ...rule })); } if (savedConfig.whitelist) { currentConfig.whitelist = savedConfig.whitelist; } } } catch (e) { console.error("AdBlocker: Error loading config, using defaults:", e); Object.assign(currentConfig.modules, DEFAULT_MODULE_STATE); currentConfig.cspRules = DEFAULT_CSP_RULES_TEMPLATE.map(rule => ({ ...rule })); currentConfig.whitelist = {}; } if (currentConfig.modules.specialUA === undefined) { currentConfig.modules.specialUA = true; } }, saveConfig() { try { GM_setValue(CONFIG_STORAGE_KEY, JSON.stringify({ modules: currentConfig.modules, cspRules: currentConfig.cspRules, whitelist: currentConfig.whitelist })); } catch (e) { console.error("AdBlocker: Error saving config:", e); } } }; const SpecialUAModule = { originalNavigator: window.navigator, fakeUA: 'NokiaE7-00/5.0 UCWEB/2.0 Mozilla/5.0 (Symbian/3; Series60/5.2; Windows Phone 10.0; Android 14; Microsoft; Lumia 950 XL Dual SIM; Java) Gecko/131 Firefox/131 SearchCraft/3.10.2 (Baidu; P1 13) baiduboxapp/4.3.0.10', fakePlatform: 'Win32', fakeAppVersion: 'NokiaE7-00/5.0 UCWEB/2.0 Mozilla/5.0 (Symbian/3; Series60/5.2; Windows Phone 10.0; Android 14; Microsoft; Lumia 950 XL Dual SIM; Java) Gecko/131 Firefox/131 SearchCraft/3.10.2 (Baidu; P1 13) baiduboxapp/4.3.0.10', navigatorProxy: null, originalDescriptor: null, init() { if (currentConfig.modules.specialUA) { this.enable(); } else { this.disable(); } }, enable() { if (!this.navigatorProxy) { this.navigatorProxy = new Proxy(this.originalNavigator, { get: (target, prop) => { if (prop === 'userAgent') return this.fakeUA; if (prop === 'platform') return this.fakePlatform; if (prop === 'appVersion') return this.fakeAppVersion; return Reflect.get(target, prop); } }); } if (window.navigator !== this.navigatorProxy) { try { this.originalDescriptor = Object.getOwnPropertyDescriptor(window, 'navigator'); Object.defineProperty(window, 'navigator', { value: this.navigatorProxy, configurable: true, writable: this.originalDescriptor ? this.originalDescriptor.writable : false }); } catch (e) { console.warn("AdBlocker: Failed to override navigator for special UA."); } } }, disable() { if (this.navigatorProxy && window.navigator === this.navigatorProxy) { try { if (this.originalDescriptor) { Object.defineProperty(window, 'navigator', this.originalDescriptor); } else { Object.defineProperty(window, 'navigator', { value: this.originalNavigator, configurable: true, writable: false }); } } catch (e) { console.warn("AdBlocker: Failed to restore original navigator."); } } }, check(element) { return false; } }; const RemoveInlineScriptsModule = { mutationObserver: null, init() { if (currentConfig.modules.removeInlineScripts) { this.enable(); } else { this.disable(); } }, enable() { this.mutationObserver = new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'SCRIPT' && !node.src) { if (node.dataset.adblockProcessed === 'true') return; const elementContent = Utils.getScriptContentPreview(node); const contentIdentifier = `SCRIPT_CONTENT: ${elementContent}`; if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) { LogManager.add('removeInlineScripts', node, { type: '内嵌脚本移除', detail: `内容: ${elementContent}` }); node.dataset.adblockProcessed = 'true'; node.remove(); } } }); }); }); this.mutationObserver.observe(document.documentElement, { childList: true, subtree: true }); document.querySelectorAll('script:not([src])').forEach(script => { if (script.dataset.adblockProcessed === 'true') return; const elementContent = Utils.getScriptContentPreview(script); const contentIdentifier = `SCRIPT_CONTENT: ${elementContent}`; if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) { LogManager.add('removeInlineScripts', script, { type: '内嵌脚本移除', detail: `内容: ${elementContent}` }); script.dataset.adblockProcessed = 'true'; script.remove(); } }); }, disable() { if (this.mutationObserver) { this.mutationObserver.disconnect(); this.mutationObserver = null; } }, check(element) { if (!currentConfig.modules.removeInlineScripts) return false; if (element.dataset.adblockProcessed === 'true') return false; if (element.tagName === 'SCRIPT' && !element.src) { const elementContent = Utils.getScriptContentPreview(element); const contentIdentifier = `SCRIPT_CONTENT: ${elementContent}`; if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) { LogManager.add('removeInlineScripts', element, { type: '内嵌脚本移除', detail: `内容: ${elementContent}` }); element.dataset.adblockProcessed = 'true'; return true; } } return false; } }; const RemoveExternalScriptsModule = { mutationObserver: null, init() { if (currentConfig.modules.removeExternalScripts) { this.enable(); } else { this.disable(); } }, enable() { this.mutationObserver = new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'SCRIPT' && node.src) { if (node.dataset.adblockProcessed === 'true') return; const src = Utils.truncateString(node.src, 100); const contentIdentifier = `SCRIPT_SRC: ${src}`; if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) { LogManager.add('removeExternalScripts', node, { type: '外联脚本移除', detail: `SRC: ${src}` }); node.dataset.adblockProcessed = 'true'; node.remove(); } } }); }); }); this.mutationObserver.observe(document.documentElement, { childList: true, subtree: true }); document.querySelectorAll('script[src]').forEach(script => { if (script.dataset.adblockProcessed === 'true') return; const src = Utils.truncateString(script.src, 100); const contentIdentifier = `SCRIPT_SRC: ${src}`; if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) { LogManager.add('removeExternalScripts', script, { type: '外联脚本移除', detail: `SRC: ${src}` }); script.dataset.adblockProcessed = 'true'; script.remove(); } }); }, disable() { if (this.mutationObserver) { this.mutationObserver.disconnect(); this.mutationObserver = null; } }, check(element) { if (!currentConfig.modules.removeExternalScripts) return false; if (element.dataset.adblockProcessed === 'true') return false; if (element.tagName === 'SCRIPT' && element.src) { const src = Utils.truncateString(element.src, 100); const contentIdentifier = `SCRIPT_SRC: ${src}`; if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) { LogManager.add('removeExternalScripts', element, { type: '外联脚本移除', detail: `SRC: ${src}` }); element.dataset.adblockProcessed = 'true'; return true; } } return false; } }; const ThirdPartyModule = { originalFetch: null, originalXhrOpen: null, originalXhrSend: null, originalCreateElement: null, originalAppendChild: null, originalDocumentWrite: null, overrideEval: false, overrideFunction: false, init() { if (currentConfig.modules.interceptThirdPartyResources) { this.enable(); } else { this.disable(); } }, enable() { this.overrideEval = !currentConfig.cspRules.find(rule => rule.id === 3)?.enabled; this.overrideFunction = !currentConfig.cspRules.find(rule => rule.id === 3)?.enabled; this.originalFetch = window.fetch; window.fetch = (input, init) => { if (!currentConfig.modules.interceptThirdPartyResources) { return this.originalFetch.apply(this, arguments); } const url = typeof input === 'string' ? input : input.url; const resourceURL = Utils.getResourceURL(url); const contentIdentifier = `FETCH_URL: ${Utils.truncateString(resourceURL, 100)}`; if (Utils.isThirdPartyHost(resourceURL, Utils.getCurrentHostname())) { if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) { LogManager.add('interceptThirdPartyResources', null, { type: '第三方请求拦截', detail: `URL: ${Utils.truncateString(resourceURL, 100)}` }); return Promise.reject(new Error('Third-party resource blocked by AdBlocker.')); } } return this.originalFetch.apply(this, arguments); }; this.originalXhrOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url) { if (!currentConfig.modules.interceptThirdPartyResources) { return this.originalXhrOpen.apply(this, arguments); } const resourceURL = Utils.getResourceURL(url); const contentIdentifier = `XHR_URL: ${Utils.truncateString(resourceURL, 100)}`; if (Utils.isThirdPartyHost(resourceURL, Utils.getCurrentHostname())) { if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) { LogManager.add('interceptThirdPartyResources', null, { type: '第三方请求拦截', detail: `URL: ${Utils.truncateString(resourceURL, 100)}` }); this._adblockBlocked = true; return; } } return this.originalXhrOpen.apply(this, arguments); }; this.originalXhrSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.send = function() { if (this._adblockBlocked) { return; } this.originalXhrSend.apply(this, arguments); }; this.originalCreateElement = document.createElement; document.createElement = (tagName, options) => { const element = this.originalCreateElement.call(document, tagName, options); if (!currentConfig.modules.interceptThirdPartyResources || element.dataset.adblockProcessed === 'true') { return element; } const processElement = (el) => { const src = el.src || el.getAttribute('data-src') || el.href || el.action || ''; if (src) { const resourceURL = Utils.getResourceURL(src); if (Utils.isThirdPartyHost(resourceURL, Utils.getCurrentHostname())) { const contentIdentifier = `${el.tagName}_SRC: ${Utils.truncateString(resourceURL, 100)}`; if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) { LogManager.add('interceptThirdPartyResources', el, { type: '第三方资源拦截', detail: `SRC: ${Utils.truncateString(resourceURL, 100)}` }); el.dataset.adblockProcessed = 'true'; el.remove(); return true; } } } return false; }; if (['SCRIPT', 'IFRAME', 'IMG', 'A', 'LINK', 'FORM', 'VIDEO', 'AUDIO', 'SOURCE'].includes(tagName.toUpperCase())) { if (processElement(element)) return element; } return element; }; this.originalAppendChild = Node.prototype.appendChild; Node.prototype.appendChild = function(node) { if (!currentConfig.modules.interceptThirdPartyResources || node.dataset.adblockProcessed === 'true') { return this.originalAppendChild.call(this, node); } if (node.nodeType === Node.ELEMENT_NODE) { const tagName = node.tagName; const src = node.src || node.getAttribute('data-src') || node.href || node.action || ''; if (src) { const resourceURL = Utils.getResourceURL(src); if (Utils.isThirdPartyHost(resourceURL, Utils.getCurrentHostname())) { const contentIdentifier = `${tagName}_SRC: ${Utils.truncateString(resourceURL, 100)}`; if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) { LogManager.add('interceptThirdPartyResources', node, { type: '第三方资源拦截', detail: `SRC: ${Utils.truncateString(resourceURL, 100)}` }); node.dataset.adblockProcessed = 'true'; return node; } } } } return this.originalAppendChild.call(this, node); }; this.originalDocumentWrite = document.write; document.write = function(content) { if (!currentConfig.modules.interceptThirdPartyResources) { return this.originalDocumentWrite.call(document, content); } const tempDiv = document.createElement('div'); tempDiv.innerHTML = content; const scripts = tempDiv.querySelectorAll('script[src]'); for (const script of scripts) { const src = script.src; if (src) { const resourceURL = Utils.getResourceURL(src); if (Utils.isThirdPartyHost(resourceURL, Utils.getCurrentHostname())) { const contentIdentifier = `DOC_WRITE_SCRIPT_SRC: ${Utils.truncateString(resourceURL, 100)}`; if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) { LogManager.add('interceptThirdPartyResources', null, { type: '第三方脚本通过document.write拦截', detail: `SRC: ${Utils.truncateString(resourceURL, 100)}` }); script.remove(); return; } } } } if (tempDiv.innerHTML !== content) { content = tempDiv.innerHTML; } this.originalDocumentWrite.call(document, content); }.bind(this); this.overrideEvalAndFunction(); }, disable() { if (this.originalFetch) window.fetch = this.originalFetch; if (this.originalXhrOpen) XMLHttpRequest.prototype.open = this.originalXhrOpen; if (this.originalXhrSend) XMLHttpRequest.prototype.send = this.originalXhrSend; if (this.originalCreateElement) document.createElement = this.originalCreateElement; if (this.originalAppendChild) Node.prototype.appendChild = this.originalAppendChild; if (this.originalDocumentWrite) document.write = this.originalDocumentWrite; this.restoreEvalAndFunction(); }, overrideEvalAndFunction() { if (this.overrideEval) { const originalEval = unsafeWindow.eval; unsafeWindow.eval = function(code) { const contentIdentifier = `EVAL_CODE: ${Utils.truncateString(code.replace(/\s+/g, ' '), 100)}`; if (Utils.isThirdPartyHost(location.href, Utils.getCurrentHostname())) { if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) { LogManager.add('interceptThirdPartyResources', null, { type: 'eval拦截', detail: `代码: ${Utils.truncateString(code, 100)}` }); return undefined; } } return originalEval.call(unsafeWindow, code); }; } if (this.overrideFunction) { const originalFunction = unsafeWindow.Function; unsafeWindow.Function = function(...args) { const code = args.length > 0 ? args[args.length - 1] : ''; const contentIdentifier = `FUNCTION_CODE: ${Utils.truncateString(code.replace(/\s+/g, ' '), 100)}`; if (Utils.isThirdPartyHost(location.href, Utils.getCurrentHostname())) { if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) { LogManager.add('interceptThirdPartyResources', null, { type: 'Function构造拦截', detail: `代码: ${Utils.truncateString(code, 100)}` }); return () => {}; } } return originalFunction.apply(unsafeWindow, args); }; } }, restoreEvalAndFunction() { if (this.overrideEval) unsafeWindow.eval = unsafeWindow.originalEval; if (this.overrideFunction) unsafeWindow.Function = unsafeWindow.originalFunction; }, check(element) { if (!currentConfig.modules.interceptThirdPartyResources || element.dataset.adblockProcessed === 'true') return false; const processElement = (el) => { const src = el.src || el.getAttribute('data-src') || el.href || el.action || ''; if (src) { const resourceURL = Utils.getResourceURL(src); if (Utils.isThirdPartyHost(resourceURL, Utils.getCurrentHostname())) { const contentIdentifier = `${el.tagName}_SRC: ${Utils.truncateString(resourceURL, 100)}`; if (!Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) { LogManager.add('interceptThirdPartyResources', el, { type: '第三方资源拦截', detail: `SRC: ${Utils.truncateString(resourceURL, 100)}` }); el.dataset.adblockProcessed = 'true'; return true; } } } return false; }; if (['SCRIPT', 'IFRAME', 'IMG', 'A', 'LINK', 'FORM', 'VIDEO', 'AUDIO', 'SOURCE'].includes(element.tagName.toUpperCase())) { if (processElement(element)) return true; } return false; } }; const CSPModule = { init() { }, applyCSP() { if (!currentConfig.modules.manageCSP) return; const existingMeta = document.querySelector('meta[http-equiv="Content-Security-Policy"]'); if (existingMeta) { existingMeta.remove(); } const enabledRules = currentConfig.cspRules.filter(rule => rule.enabled); if (enabledRules.length === 0) { return; } const policyString = enabledRules.map(rule => rule.rule).join('; '); const meta = document.createElement('meta'); meta.httpEquiv = "Content-Security-Policy"; meta.content = policyString; if (document.head) { document.head.appendChild(meta); } else { document.documentElement.prepend(meta); } }, removeCSP() { const existingMeta = document.querySelector('meta[http-equiv="Content-Security-Policy"]'); if (existingMeta) { existingMeta.remove(); } }, updateRule(ruleId, enabled) { const rule = currentConfig.cspRules.find(r => r.id === ruleId); if (rule) { rule.enabled = enabled; } }, showManagementPanel() { const rulesDisplay = currentConfig.cspRules .map(r => `${r.id}. ${r.name} (${r.enabled ? '✅' : '❌'})`) .join('\n'); const promptMessage = `CSP策略管理:\n` + `当前状态: ${currentConfig.modules.manageCSP ? '启用' : '禁用'}\n\n` + `可用规则:\n${rulesDisplay}\n\n` + `输入指令:\n` + `enable: 启用CSP策略\n` + `disable: 禁用CSP策略\n` + `1on: 启用规则1\n` + `23off: 禁用规则2和3\n` + `allon: 启用所有规则\n` + `alloff: 禁用所有规则\n\n` + `请输入您的指令:`; let input = prompt(promptMessage, ""); if (input === null) return; input = input.trim().toLowerCase(); let needsReload = false; if (input === 'enable') { currentConfig.modules.manageCSP = true; this.applyCSP(); alert("CSP策略已启用。"); needsReload = true; } else if (input === 'disable') { currentConfig.modules.manageCSP = false; this.removeCSP(); alert("CSP策略已禁用。"); needsReload = true; } else if (input === 'allon') { currentConfig.cspRules.forEach(rule => rule.enabled = true); if (currentConfig.modules.manageCSP) this.applyCSP(); alert("所有CSP规则已启用。"); needsReload = true; } else if (input === 'alloff') { currentConfig.cspRules.forEach(rule => rule.enabled = false); if (currentConfig.modules.manageCSP) this.removeCSP(); alert("所有CSP规则已禁用。"); needsReload = true; } else if (/^(\d+|all)(\d*on|off)$/.test(input)) { const match = input.match(/^(\d+|all)(\d*on|off)$/); if (match) { const ruleSpec = match[1]; const action = match[2]; const enable = action.endsWith('on'); let ruleIdsToModify = []; if (ruleSpec === 'all') { ruleIdsToModify = currentConfig.cspRules.map(r => r.id); } else { for (const char of ruleSpec) { const id = parseInt(char, 10); if (!isNaN(id)) ruleIdsToModify.push(id); } } let modified = false; ruleIdsToModify.forEach(id => { const rule = currentConfig.cspRules.find(r => r.id === id); if (rule && rule.enabled !== enable) { rule.enabled = enable; modified = true; } }); if (modified) { if (currentConfig.modules.manageCSP) this.applyCSP(); alert(`CSP规则 ${ruleIdsToModify.join(', ')} 已${enable ? '启用' : '禁用'}。`); needsReload = true; } else { alert("未找到指定的CSP规则或规则状态已是目标状态。"); } } else { alert("无效的CSP规则指令格式。"); } } else { alert("无效指令。"); } if (needsReload) { StorageManager.saveConfig(); location.reload(); } } }; const UIController = { mutationObserver: null, init() { StorageManager.loadConfig(); this.applyInitialModuleStates(); this.registerMenuCommands(); this.applyModuleSettings(); this.setupObservers(); }, applyInitialModuleStates() { Object.keys(DEFAULT_MODULE_STATE).forEach(key => { if (currentConfig.modules[key] === undefined) { currentConfig.modules[key] = DEFAULT_MODULE_STATE[key]; } }); if (currentConfig.modules.specialUA === undefined) { currentConfig.modules.specialUA = true; } }, registerMenuCommands() { GM_registerMenuCommand(`🔘 广告拦截 [${this.isAnyModuleEnabled() ? '✅' : '❌'}]`, () => this.toggleAllModules()); Object.keys(MODULE_NAMES).forEach(key => { GM_registerMenuCommand( `${MODULE_NAMES[key]} [${currentConfig.modules[key] ? '✅' : '❌'}]`, () => this.toggleModule(key) ); }); GM_registerMenuCommand('📜 查看拦截日志', () => LogManager.showInAlert()); GM_registerMenuCommand('🚫 清空当前域名白名单', () => { const currentDomain = Utils.getCurrentHostname(); if (confirm(`确定清空当前域名 (${currentDomain}) 的白名单吗?`)) { Whitelisting.clearDomainWhitelist(currentDomain); StorageManager.saveConfig(); alert("当前域名的白名单已清空。页面将刷新。"); location.reload(); } }); GM_registerMenuCommand('🛡️ CSP策略管理', () => CSPModule.showManagementPanel()); GM_registerMenuCommand('🔄 重置所有设置', () => this.resetSettings()); }, isAnyModuleEnabled() { return Object.keys(MODULE_NAMES).some(key => currentConfig.modules[key]); }, toggleAllModules() { const newState = !this.isAnyModuleEnabled(); Object.keys(MODULE_NAMES).forEach(key => { currentConfig.modules[key] = newState; }); this.applyModuleSettings(); StorageManager.saveConfig(); alert(`广告拦截已${newState ? '启用' : '禁用'}。页面将刷新。`); location.reload(); }, toggleModule(key) { currentConfig.modules[key] = !currentConfig.modules[key]; this.applyModuleSettings(); StorageManager.saveConfig(); alert(`${MODULE_NAMES[key]} 已${currentConfig.modules[key] ? '启用' : '禁用'}。页面将刷新。`); location.reload(); }, applyModuleSettings() { SpecialUAModule.init(); RemoveInlineScriptsModule.init(); RemoveExternalScriptsModule.init(); ThirdPartyModule.init(); CSPModule.init(); }, setupObservers() { const relevantModulesEnabled = Object.keys(MODULE_NAMES).some(key => currentConfig.modules[key] && ( key === 'removeInlineScripts' || key === 'removeExternalScripts' || key === 'interceptThirdPartyResources' ) ); if (relevantModulesEnabled) { this.mutationObserver = new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.addedNodes) { mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE && node.dataset.adblockProcessed !== 'true') { if (node.tagName === 'SCRIPT') { if (currentConfig.modules.removeInlineScripts && !node.src) { if (RemoveInlineScriptsModule.check(node)) { node.remove(); } } else if (currentConfig.modules.removeExternalScripts && node.src) { if (RemoveExternalScriptsModule.check(node)) { node.remove(); } } else if (currentConfig.modules.interceptThirdPartyResources) { if (ThirdPartyModule.check(node)) { node.remove(); } } } else if (currentConfig.modules.interceptThirdPartyResources) { if (ThirdPartyModule.check(node)) { node.remove(); } } } }); } }); }); this.mutationObserver.observe(document.documentElement, { childList: true, subtree: true, }); } }, resetSettings() { if (confirm("确定要重置所有设置吗?这将清除所有配置和白名单。")) { Object.assign(currentConfig.modules, DEFAULT_MODULE_STATE); currentConfig.cspRules = DEFAULT_CSP_RULES_TEMPLATE.map(rule => ({ ...rule })); Whitelisting.clearAllWhitelists(); CSPModule.removeCSP(); StorageManager.saveConfig(); alert("所有设置已重置为默认值。页面将刷新。"); location.reload(); } } }; function initializeAdBlocker() { UIController.init(); if (currentConfig.modules.manageCSP) { CSPModule.applyCSP(); } } StorageManager.loadConfig(); if (currentConfig.modules.manageCSP) { CSPModule.applyCSP(); } initializeAdBlocker(); })();