一个手机端via浏览器能用的强大的广告拦截器
// ==UserScript==
// @name 小说漫画网页广告拦截器
// @namespace http://tampermonkey.net/
// @version 4.5.0
// @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
// ==/UserScript==
(function() {
'use strict';
const DEFAULT_MODULE_STATE = {
smartInterception: false,
removeInlineScripts: false,
removeExternalScripts: false,
interceptThirdPartyResources: false,
interceptSubdomains: false,
manageCSP: false,
};
const MODULE_NAMES = {
smartInterception: '智能拦截',
removeInlineScripts: '移除内嵌脚本',
removeExternalScripts: '移除外联脚本',
interceptThirdPartyResources: '拦截第三方资源',
interceptSubdomains: '拦截子域名资源',
manageCSP: 'CSP策略管理',
};
const DEFAULT_CSP_RULES_TEMPLATE = [
{ id: 1, name: '仅允许同源脚本', rule: "script-src 'self'", enabled: false },
{ 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: false },
{ 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_PREFIX = `customAdBlockerConfig_`;
let currentConfig = {
modules: { ...DEFAULT_MODULE_STATE },
cspRules: DEFAULT_CSP_RULES_TEMPLATE.map(rule => ({ ...rule })),
whitelist: {},
};
class LRUCache {
constructor(capacity = 100, defaultTTL = 0) {
this.capacity = capacity;
this.defaultTTL = defaultTTL;
this.cache = new Map();
}
get(key) {
if (!this.cache.has(key)) return null;
const entry = this.cache.get(key);
if (this.defaultTTL > 0 && (Date.now() - entry.timestamp) > this.defaultTTL) {
this.cache.delete(key);
return null;
}
this.cache.delete(key);
this.cache.set(key, entry);
return entry.value;
}
set(key, value, ttl) {
const finalTTL = ttl !== undefined ? ttl : this.defaultTTL;
const entry = {
value: value,
timestamp: Date.now(),
ttl: finalTTL
};
if (this.cache.has(key)) {
this.cache.delete(key);
} else if (this.cache.size >= this.capacity) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, entry);
}
has(key) {
const entry = this.cache.get(key);
if (!entry) return false;
if (this.defaultTTL > 0 && (Date.now() - entry.timestamp) > this.defaultTTL) {
this.cache.delete(key);
return false;
}
return true;
}
delete(key) {
return this.cache.delete(key);
}
clear() {
this.cache.clear();
}
get size() {
return this.cache.size;
}
}
class URLResolutionCache {
constructor() {
this.hostnameCache = new LRUCache(1000, 300000);
this.domainCache = new LRUCache(1000, 300000);
this.absoluteUrlCache = new LRUCache(1000, 300000);
this.thirdPartyCache = new LRUCache(1000, 300000);
}
isDynamicURL(url) {
if (!url || typeof url !== 'string') return false;
const dynamicPatterns = [
/\?.*[tT]=/,
/\?.*timestamp/,
/\?.*rand/,
/\?.*rnd/,
/\?.*[0-9]{13,}/,
/\?.*\d{10,}/,
/\?.*[a-zA-Z0-9]{32,}/,
/\/\d{10,}\./,
/\/[0-9a-f]{32,}\./
];
return dynamicPatterns.some(pattern => pattern.test(url));
}
getHostname(url) {
if (!url || typeof url !== 'string') return null;
const cacheKey = `hostname_${url}`;
if (this.hostnameCache.has(cacheKey)) {
return this.hostnameCache.get(cacheKey);
}
if (url.startsWith('data:') || url.startsWith('blob:') || url.startsWith('about:blank')) {
this.hostnameCache.set(cacheKey, null, 60000);
return null;
}
try {
const hostname = new URL(url, location.href).hostname;
const ttl = this.isDynamicURL(url) ? 30000 : 300000;
this.hostnameCache.set(cacheKey, hostname, ttl);
return hostname;
} catch (e) {
this.hostnameCache.set(cacheKey, null, 30000);
return null;
}
}
getDomain(hostname) {
if (!hostname) return null;
const cacheKey = `domain_${hostname}`;
if (this.domainCache.has(cacheKey)) {
return this.domainCache.get(cacheKey);
}
const parts = hostname.split('.');
const domain = parts.length <= 2 ? hostname : parts.slice(-2).join('.');
this.domainCache.set(cacheKey, domain, 300000);
return domain;
}
getAbsoluteURL(url) {
if (!url) return '';
const cacheKey = `absolute_${url}_${location.href}`;
if (this.absoluteUrlCache.has(cacheKey)) {
return this.absoluteUrlCache.get(cacheKey);
}
try {
const absoluteUrl = new URL(url, location.href).href;
const ttl = this.isDynamicURL(url) ? 30000 : 300000;
this.absoluteUrlCache.set(cacheKey, absoluteUrl, ttl);
return absoluteUrl;
} catch (e) {
this.absoluteUrlCache.set(cacheKey, url, 30000);
return url;
}
}
isThirdPartyHost(resourceHostname, currentHost, interceptSubdomains = false) {
if (!resourceHostname) return false;
const cacheKey = `thirdparty_${resourceHostname}_${currentHost}_${interceptSubdomains}`;
if (this.thirdPartyCache.has(cacheKey)) {
return this.thirdPartyCache.get(cacheKey);
}
const currentHostDomain = this.getDomain(currentHost);
const resourceHostDomain = this.getDomain(resourceHostname);
let isThirdParty = false;
if (!currentHostDomain || !resourceHostDomain) {
isThirdParty = false;
} else if (resourceHostDomain === currentHostDomain) {
if (interceptSubdomains && resourceHostname !== currentHost) {
isThirdParty = true;
} else {
isThirdParty = false;
}
} else {
isThirdParty = true;
}
this.thirdPartyCache.set(cacheKey, isThirdParty, 300000);
return isThirdParty;
}
clear() {
this.hostnameCache.clear();
this.domainCache.clear();
this.absoluteUrlCache.clear();
this.thirdPartyCache.clear();
}
}
const urlCache = new URLResolutionCache();
const Utils = {
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 '';
const content = scriptElement.textContent;
if (content.length > 200) {
return content.slice(0, 100) + '...';
}
return content.split(/\s+/).join(' ').slice(0, 100);
},
getIframeSrcPreview(iframeElement) {
if (!iframeElement || iframeElement.tagName !== 'IFRAME') return '';
return Utils.truncateString(iframeElement.src, 100);
},
getResourceHostname(url) {
return urlCache.getHostname(url);
},
getDomain(hostname) {
return urlCache.getDomain(hostname);
},
isThirdPartyHost(resourceHostname, currentHost) {
return urlCache.isThirdPartyHost(resourceHostname, currentHost, currentConfig.modules.interceptSubdomains);
},
getAbsoluteURL(url) {
return urlCache.getAbsoluteURL(url);
},
getContentIdentifier(element, reasonType = null) {
if (!element && !reasonType) return null; // 必须有 element 或 reasonType
if (element && Utils.isElement(element)) {
const tagName = element.tagName;
const src = element.src || element.getAttribute('data-src') || element.href || element.action || '';
if (tagName === 'SCRIPT') {
return element.src ? `SCRIPT_SRC: ${Utils.truncateString(element.src, 100)}` : `SCRIPT_CONTENT: ${Utils.getScriptContentPreview(element)}`;
} else if (tagName === 'IFRAME') {
return `IFRAME_SRC: ${Utils.truncateString(element.src, 100)}`;
} else if (tagName === 'IMG') {
return src ? `IMG_SRC: ${Utils.truncateString(src, 100)}` : null;
} else if (tagName === 'A') {
return src ? `A_HREF: ${Utils.truncateString(src, 100)}` : null;
} else if (tagName === 'LINK' && element.rel === 'stylesheet' && element.href) {
return `CSS_HREF: ${Utils.truncateString(element.href, 100)}`;
} else if (['VIDEO', 'AUDIO', 'SOURCE'].includes(tagName) && src) {
return `${tagName}_SRC: ${Utils.truncateString(src, 100)}`;
} else if (tagName === 'STYLE') {
return `STYLE_CONTENT: ${Utils.truncateString(element.textContent, 100)}`;
}
// 移除了 SVG/CANVAS 的识别逻辑
return null;
} else if (reasonType && typeof reasonType.detail === 'string') {
if (reasonType.detail.startsWith('SRC:')) {
return `${reasonType.type || 'INTERCEPTED'}_SRC: ${Utils.truncateString(reasonType.detail.substring(4).trim(), 100)}`;
} else if (reasonType.detail.startsWith('URL:')) {
return `${reasonType.type || 'INTERCEPTED'}_URL: ${Utils.truncateString(reasonType.detail.substring(5).trim(), 100)}`;
} else if (reasonType.type === 'EVAL') {
return `EVAL_CODE: ${Utils.truncateString(reasonType.detail, 100)}`;
} else if (reasonType.type === 'FUNCTION_CONSTRUCTOR') {
return `FUNCTION_CODE: ${Utils.truncateString(reasonType.detail, 100)}`;
} else if (reasonType.type === 'SETTIMEOUT') {
return `SETTIMEOUT: ${Utils.truncateString(reasonType.detail, 100)}`;
} else if (reasonType.type === 'SETINTERVAL') {
return `SETINTERVAL: ${Utils.truncateString(reasonType.detail, 100)}`;
} else if (reasonType.type === 'STYLE_ADS') {
return `STYLE_ADS: ${Utils.truncateString(reasonType.detail, 100)}`;
} else if (reasonType.type === 'PRELOAD_ADS') {
return `PRELOAD_ADS: ${Utils.truncateString(reasonType.detail, 100)}`;
} else if (reasonType.type === 'SCROLL_ADS') {
return `SCROLL_ADS: ${Utils.truncateString(reasonType.detail, 100)}`;
}
return `LOG_DETAIL: ${Utils.truncateString(reasonType.detail, 100)}`;
}
return null;
},
isParentProcessed(element) {
let parent = element.parentElement;
while (parent) {
if (parent.dataset.adblockProcessed === 'true' || ProcessedElementsCache.isProcessed(parent)) {
return true;
}
parent = parent.parentElement;
}
return false;
},
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
},
throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
};
const LogManager = {
logs: [],
maxLogs: 150,
logEntryData: new LRUCache(150, 300000),
loggedContentIdentifiers: new LRUCache(150, 300000),
add(moduleKey, element, reason) {
if (!currentConfig.modules.interceptThirdPartyResources && !currentConfig.modules.removeInlineScripts && !currentConfig.modules.removeExternalScripts && !currentConfig.modules.smartInterception) {
return;
}
if (!Utils.isElement(element) && element !== null && typeof reason !== 'object') return;
const currentDomain = Utils.getCurrentHostname();
if (Whitelisting.isElementWhitelisted(element) || Whitelisting.isReasonWhitelisted(reason)) {
return;
}
let elementIdentifier = '[未知元素]';
let interceptedContent = '[无法获取内容]';
let contentIdentifier = null;
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}`;
contentIdentifier = Utils.getContentIdentifier(element);
if (tagName === 'SCRIPT') {
interceptedContent = element.src ? `SRC: ${Utils.truncateString(element.src, 100)}` : Utils.getScriptContentPreview(element);
} else if (tagName === 'IFRAME') {
interceptedContent = Utils.getIframeSrcPreview(element);
} else if (tagName === 'IMG') {
const src = element.src || element.dataset.src;
interceptedContent = Utils.truncateString(src || '', 100);
} else if (tagName === 'A') {
interceptedContent = Utils.truncateString(element.href || '', 100);
} else if (tagName === 'LINK' && element.rel === 'stylesheet' && element.href) {
interceptedContent = Utils.truncateString(element.href || '', 100);
} else if (['VIDEO', 'AUDIO', 'SOURCE'].includes(tagName)) {
interceptedContent = Utils.truncateString(element.src || '', 100);
} else if (tagName === 'STYLE') {
interceptedContent = Utils.truncateString(element.textContent, 100);
} else {
interceptedContent = Utils.truncateString(element.outerHTML, 100);
}
} else if (reason && typeof reason.detail === 'string') {
interceptedContent = Utils.truncateString(reason.detail, 100);
elementIdentifier = reason.type ? `[${reason.type}]` : '[未知类型]';
contentIdentifier = Utils.getContentIdentifier(null, reason);
}
if (!contentIdentifier) {
return;
}
if (this.loggedContentIdentifiers.has(contentIdentifier)) {
return;
}
const logId = this.logs.length + 1;
const logEntry = {
id: logId,
module: MODULE_NAMES[moduleKey] || moduleKey,
element: elementIdentifier,
content: interceptedContent,
};
this.logs.push(logEntry);
this.logEntryData.set(logId, {
contentIdentifier: contentIdentifier,
element: element,
reason: reason,
});
this.loggedContentIdentifiers.set(contentIdentifier, true);
if (this.logs.length > this.maxLogs) {
const removedLogEntry = this.logs.shift();
this.logEntryData.delete(removedLogEntry.id);
}
},
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. 输入序号(如 1-3, 1,3,5)\n` +
`2. 输入关键词(如 aaa, bbb)\n` +
`3. 输入 0 清空当前域名所有白名单\n\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 keywordsToWhitelist = new Set();
const parts = input.replace(/,/g, ',').split(/[\s,]+/);
parts.forEach(part => {
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);
} else if (part.length > 0) {
keywordsToWhitelist.add(part.toLowerCase());
}
}
});
let addedCount = 0;
let whitelistedIdentifiers = new Set();
indicesToWhitelist.forEach(index => {
const logEntryInfo = this.logEntryData.get(index);
if (logEntryInfo && logEntryInfo.contentIdentifier) {
const { contentIdentifier } = logEntryInfo;
if (!Whitelisting.isContentWhitelisted(currentDomain, contentIdentifier)) {
whitelistedIdentifiers.add(contentIdentifier);
}
}
});
keywordsToWhitelist.forEach(keyword => {
for (const [key, entry] of this.logEntryData.cache) {
const logEntryInfo = entry.value;
if (logEntryInfo.contentIdentifier && logEntryInfo.contentIdentifier.toLowerCase().includes(keyword)) {
if (!Whitelisting.isContentWhitelisted(currentDomain, logEntryInfo.contentIdentifier)) {
whitelistedIdentifiers.add(logEntryInfo.contentIdentifier);
}
}
}
});
whitelistedIdentifiers.forEach(identifier => {
Whitelisting.add(currentDomain, identifier);
addedCount++;
});
if (addedCount > 0) {
StorageManager.saveConfig();
alert(`${addedCount} 项已添加到白名单,页面将刷新。`);
location.reload();
} else if (input.length > 0) {
alert("未找到匹配项,或已在白名单中。");
}
}
}
};
const Whitelisting = {
isElementWhitelisted(element) {
if (!element || !Utils.isElement(element)) return false;
const currentDomain = Utils.getCurrentHostname();
const contentIdentifier = Utils.getContentIdentifier(element);
return !!(contentIdentifier && currentDomain && currentConfig.whitelist[currentDomain] && currentConfig.whitelist[currentDomain].has(contentIdentifier));
},
isReasonWhitelisted(reason) {
if (!reason || typeof reason.detail !== 'string') return false;
const currentDomain = Utils.getCurrentHostname();
const contentIdentifier = Utils.getContentIdentifier(null, reason);
return !!(contentIdentifier && currentDomain && currentConfig.whitelist[currentDomain] && currentConfig.whitelist[currentDomain].has(contentIdentifier));
},
isContentWhitelisted(domain, contentIdentifier) {
if (!domain || !contentIdentifier) return false;
return !!(currentConfig.whitelist[domain] && currentConfig.whitelist[domain].has(contentIdentifier));
},
add(domain, contentIdentifier) {
if (!domain || !contentIdentifier || contentIdentifier.trim() === '') return;
if (!currentConfig.whitelist[domain]) {
currentConfig.whitelist[domain] = new Set();
}
currentConfig.whitelist[domain].add(contentIdentifier);
},
clearDomainWhitelist(domain) {
if (currentConfig.whitelist[domain]) {
currentConfig.whitelist[domain].clear();
}
},
clearAllWhitelists() {
currentConfig.whitelist = {};
}
};
const StorageManager = {
getConfigKey(hostname) {
return `${CONFIG_STORAGE_KEY_PREFIX}${hostname}`;
},
loadConfig() {
const hostname = Utils.getCurrentHostname();
const key = this.getConfigKey(hostname);
try {
const savedConfig = JSON.parse(GM_getValue(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 = {};
for (const domain in savedConfig.whitelist) {
if (Array.isArray(savedConfig.whitelist[domain])) {
currentConfig.whitelist[domain] = new Set(savedConfig.whitelist[domain]);
}
}
}
}
} catch (e) {
Object.assign(currentConfig.modules, DEFAULT_MODULE_STATE);
currentConfig.cspRules = DEFAULT_CSP_RULES_TEMPLATE.map(rule => ({ ...rule }));
currentConfig.whitelist = {};
}
Object.keys(DEFAULT_MODULE_STATE).forEach(key => {
if (currentConfig.modules[key] === undefined) {
currentConfig.modules[key] = DEFAULT_MODULE_STATE[key];
}
});
if (currentConfig.modules.manageCSP === undefined) currentConfig.modules.manageCSP = false;
if (currentConfig.modules.interceptSubdomains === undefined) currentConfig.modules.interceptSubdomains = false;
if (currentConfig.modules.smartInterception === undefined) currentConfig.modules.smartInterception = false;
},
saveConfig() {
const hostname = Utils.getCurrentHostname();
const key = this.getConfigKey(hostname);
try {
const whitelistForStorage = {};
for (const domain in currentConfig.whitelist) {
whitelistForStorage[domain] = Array.from(currentConfig.whitelist[domain]);
}
GM_setValue(key, JSON.stringify({
modules: currentConfig.modules,
cspRules: currentConfig.cspRules,
whitelist: whitelistForStorage
}));
} catch (e) {}
}
};
const ProcessedElementsCache = {
_processedElements: new WeakSet(),
isProcessed(element) {
if (!Utils.isElement(element)) return false;
return this._processedElements.has(element) || element.dataset.adblockProcessed === 'true';
},
markAsProcessed(element) {
if (!Utils.isElement(element)) return;
this._processedElements.add(element);
element.dataset.adblockProcessed = 'true';
},
clear() {
this._processedElements = new WeakSet();
}
};
const ResourceCanceller = {
cancelResourceLoading(element) {
if (!Utils.isElement(element) || ProcessedElementsCache.isProcessed(element)) return;
const tagName = element.tagName;
if (['IMG', 'VIDEO', 'AUDIO', 'SOURCE'].includes(tagName) && element.src) {
element.src = '';
element.srcset = '';
element.removeAttribute('srcset');
if (element.load) element.load();
} else if (tagName === 'IFRAME' && element.src) {
element.src = 'about:blank';
} else if (tagName === 'SCRIPT' && element.src) {
element.src = '';
} else if (tagName === 'LINK' && element.rel === 'stylesheet' && element.href) {
element.href = '';
} else if (tagName === 'STYLE') {
element.textContent = '';
}
if (element.parentNode) {
element.parentNode.removeChild(element);
}
}
};
const SmartInterceptionModule = {
originalSetTimeout: null,
originalSetInterval: null,
originalClearTimeout: null,
originalClearInterval: null,
processedTimers: new WeakSet(),
stylePatterns: [
/position\s*:\s*fixed\s*!\s*important/i,
/position\s*:\s*sticky\s*!\s*important/i,
/z-index\s*:\s*[1-9][0-9]{3,}\s*!\s*important/i,
/display\s*:\s*none\s*!\s*important.*(ad|banner|popup)/i,
/visibility\s*:\s*hidden\s*!\s*important.*(ad|banner|popup)/i,
/opacity\s*:\s*0\s*!\s*important.*(ad|banner|popup)/i,
/width\s*:\s*[0-9]{1,3}px.*height\s*:\s*[0-9]{1,3}px.*(fixed|sticky)/i,
/height\s*:\s*[0-9]{1,3}px.*width\s*:\s*[0-9]{1,3}px.*(fixed|sticky)/i,
/background\s*.*(url\s*\(.*(ad|banner|popup|track|analytics))|(ad\.[a-z]+|ads\.[a-z]+|banner\.[a-z]+)/i,
/\.ad[\w-]*\s*{.*(position\s*:\s*fixed|position\s*:\s*sticky|z-index\s*:\s*[1-9][0-9]{3,})/i,
/#ad[\w-]*\s*{.*(position\s*:\s*fixed|position\s*:\s*sticky|z-index\s*:\s*[1-9][0-9]{3,})/i,
/\.ads?[\w-]*\s*{.*(display\s*:\s*block|visibility\s*:\s*visible).*(width|height).*px/i,
/\.popup[\w-]*\s*{.*(position\s*:\s*fixed|position\s*:\s*absolute).*(top|left|right|bottom).*0/i
],
init() {
if (currentConfig.modules.smartInterception) {
this.enable();
}
},
enable() {
this.setupTimerInterception();
this.setupFastStyleChecker();
},
disable() {
this.disableTimerInterception();
},
setupTimerInterception() {
this.originalSetTimeout = window.setTimeout;
this.originalSetInterval = window.setInterval;
this.originalClearTimeout = window.clearTimeout;
this.originalClearInterval = window.clearInterval;
const self = this;
window.setTimeout = function(callback, delay, ...args) {
if (typeof callback === 'function') {
const callbackStr = callback.toString();
if (self.isAdTimerCallback(callbackStr)) {
const contentIdentifier = Utils.getContentIdentifier(null, { type: 'SETTIMEOUT', detail: `代码: ${Utils.truncateString(callbackStr, 100)}` });
if (contentIdentifier && !Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
LogManager.add('smartInterception', null, { type: 'SETTIMEOUT', detail: `广告定时器: ${Utils.truncateString(callbackStr, 100)}` });
const timerId = self.originalSetTimeout(() => {}, delay);
self.processedTimers.add(timerId);
return timerId;
}
}
}
return self.originalSetTimeout.call(this, callback, delay, ...args);
};
window.setInterval = function(callback, delay, ...args) {
if (typeof callback === 'function') {
const callbackStr = callback.toString();
if (self.isAdTimerCallback(callbackStr)) {
const contentIdentifier = Utils.getContentIdentifier(null, { type: 'SETINTERVAL', detail: `代码: ${Utils.truncateString(callbackStr, 100)}` });
if (contentIdentifier && !Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
LogManager.add('smartInterception', null, { type: 'SETINTERVAL', detail: `广告定时器: ${Utils.truncateString(callbackStr, 100)}` });
const timerId = self.originalSetInterval(() => {}, delay);
self.processedTimers.add(timerId);
return timerId;
}
}
}
return self.originalSetInterval.call(this, callback, delay, ...args);
};
window.clearTimeout = function(timerId) {
if (self.processedTimers.has(timerId)) {
self.processedTimers.delete(timerId);
return;
}
self.originalClearTimeout.call(this, timerId);
};
window.clearInterval = function(timerId) {
if (self.processedTimers.has(timerId)) {
self.processedTimers.delete(timerId);
return;
}
self.originalClearInterval.call(this, timerId);
};
},
disableTimerInterception() {
if (this.originalSetTimeout) window.setTimeout = this.originalSetTimeout;
if (this.originalSetInterval) window.setInterval = this.originalSetInterval;
if (this.originalClearTimeout) window.clearTimeout = this.originalClearTimeout;
if (this.originalClearInterval) window.clearInterval = this.originalClearInterval;
},
isAdTimerCallback(callbackStr) {
const adPatterns = [
/\b(ad|ads|banner|popup|promo|sponsor)\b/i,
/ad\./i,
/ads\./i,
/banner\./i,
/popup\./i,
/showAd/i,
/displayAd/i,
/loadAd/i,
/\.ad[\w-]*\(/i,
/google_ad/i,
/doubleclick/i,
/['"](ad|ads|banner|popup)['"]/i,
/['"]\s*\+\s*['"](ad|ads)['"]/i,
/String\.fromCharCode.*(97|100).*61|64|100/i,
/\w\s*=\s*\w\s*\+\s*['"](ad|ads|banner)['"]/i,
/\w\s*\+\s*=\s*['"](ad|ads|banner)['"]/i,
/\w\s*\.\s*\w\s*\(\s*['"](ad|ads|banner)['"]/i,
/\w\s*\[\s*['"](ad|ads|banner)['"]\s*\]/i,
/(adserver|adservice|advert|advertising)/i,
/\.(doubleclick|googlesyndication|googleadservices)\.[a-z]+\//i,
/\/ads?\//i,
/\/banners?\//i,
/\/popups?\//i,
/function\s+\w*[Aa]d\w*/i,
/var\s+\w*[Aa]d\w*\s*=/i,
/const\s+\w*[Aa]d\w*\s*=/i,
/let\s+\w*[Aa]d\w*\s*=/i,
/addEventListener.*(load|show).*(ad|ads|banner)/i,
/createElement.*(div|iframe).*(ad|ads|banner)/i,
/\.src\s*=\s*['"][^'"]*(ad|ads|banner)[^'"]*['"]/i,
/(adsbygoogle|amazon-adsystem|taboola|outbrain)/i
];
const isMinified = callbackStr.length > 50 &&
(callbackStr.includes(';') &&
callbackStr.split(';').length > 10 &&
callbackStr.replace(/\s+/g, '').length / callbackStr.length > 0.9);
if (isMinified) {
const compressedAdPatterns = [
/a\s*d\s*s/i,
/b\s*a\s*n\s*n\s*e\s*r/i,
/p\s*o\s*p\s*u\s*p/i,
/[\w\.]+ad[\w\.]*/i,
/ad[\w]*\s*=/i
];
if (compressedAdPatterns.some(pattern => pattern.test(callbackStr))) {
return true;
}
}
return adPatterns.some(pattern => pattern.test(callbackStr));
},
setupFastStyleChecker() {
const observer = new MutationObserver(Utils.throttle((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.nodeType !== Node.ELEMENT_NODE) continue;
if (ProcessedElementsCache.isProcessed(node) || Utils.isParentProcessed(node)) continue;
this.fastCheckElement(node);
}
}
}, 200));
observer.observe(document.documentElement, {
childList: true,
subtree: true
});
},
fastCheckElement(element) {
const tagName = element.tagName;
if (tagName === 'STYLE') {
this.checkStyleElement(element);
}
},
checkStyleElement(styleElement) {
const styleContent = styleElement.textContent;
if (this.stylePatterns.some(pattern => pattern.test(styleContent))) {
const contentIdentifier = Utils.getContentIdentifier(styleElement);
if (contentIdentifier && !Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
LogManager.add('smartInterception', styleElement, { type: 'STYLE_ADS', detail: `广告样式: ${Utils.truncateString(styleContent, 100)}` });
ResourceCanceller.cancelResourceLoading(styleElement);
ProcessedElementsCache.markAsProcessed(styleElement);
}
}
},
check(element) {
if (!currentConfig.modules.smartInterception || ProcessedElementsCache.isProcessed(element) || Utils.isParentProcessed(element)) return false;
const tagName = element.tagName;
if (tagName === 'STYLE') {
this.checkStyleElement(element);
return ProcessedElementsCache.isProcessed(element);
}
return false;
}
};
const RemoveInlineScriptsModule = {
mutationObserver: null,
init() {
if (currentConfig.modules.removeInlineScripts) {
this.enable();
}
},
enable() {
this.mutationObserver = new MutationObserver(mutations => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'SCRIPT' && !node.src && !ProcessedElementsCache.isProcessed(node) && !Utils.isParentProcessed(node)) {
const contentIdentifier = Utils.getContentIdentifier(node);
if (contentIdentifier && !Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
LogManager.add('removeInlineScripts', node, { type: '内嵌脚本移除', detail: `内容: ${Utils.getScriptContentPreview(node)}` });
ProcessedElementsCache.markAsProcessed(node);
node.remove();
}
}
}
}
});
this.mutationObserver.observe(document.documentElement, { childList: true, subtree: true });
document.querySelectorAll('script:not([src])').forEach(script => {
if (ProcessedElementsCache.isProcessed(script) || Utils.isParentProcessed(script)) return;
const contentIdentifier = Utils.getContentIdentifier(script);
if (contentIdentifier && !Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
LogManager.add('removeInlineScripts', script, { type: '内嵌脚本移除', detail: `内容: ${Utils.getScriptContentPreview(script)}` });
ProcessedElementsCache.markAsProcessed(script);
script.remove();
}
});
},
disable() {
if (this.mutationObserver) {
this.mutationObserver.disconnect();
this.mutationObserver = null;
}
},
check(element) {
if (!currentConfig.modules.removeInlineScripts || ProcessedElementsCache.isProcessed(element) || Utils.isParentProcessed(element)) return false;
if (element.tagName === 'SCRIPT' && !element.src) {
const contentIdentifier = Utils.getContentIdentifier(element);
if (contentIdentifier && !Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
LogManager.add('removeInlineScripts', element, { type: '内嵌脚本移除', detail: `内容: ${Utils.getScriptContentPreview(element)}` });
ProcessedElementsCache.markAsProcessed(element);
return true;
}
}
return false;
}
};
const RemoveExternalScriptsModule = {
mutationObserver: null,
init() {
if (currentConfig.modules.removeExternalScripts) {
this.enable();
}
},
enable() {
this.mutationObserver = new MutationObserver(mutations => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'SCRIPT' && node.src && !ProcessedElementsCache.isProcessed(node) && !Utils.isParentProcessed(node)) {
const contentIdentifier = Utils.getContentIdentifier(node);
if (contentIdentifier && !Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
LogManager.add('removeExternalScripts', node, { type: '外联脚本移除', detail: `SRC: ${Utils.truncateString(node.src, 100)}` });
ResourceCanceller.cancelResourceLoading(node);
ProcessedElementsCache.markAsProcessed(node);
}
}
}
}
});
this.mutationObserver.observe(document.documentElement, { childList: true, subtree: true });
document.querySelectorAll('script[src]').forEach(script => {
if (ProcessedElementsCache.isProcessed(script) || Utils.isParentProcessed(script)) return;
const contentIdentifier = Utils.getContentIdentifier(script);
if (contentIdentifier && !Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
LogManager.add('removeExternalScripts', script, { type: '外联脚本移除', detail: `SRC: ${Utils.truncateString(script.src, 100)}` });
ResourceCanceller.cancelResourceLoading(script);
ProcessedElementsCache.markAsProcessed(script);
}
});
},
disable() {
if (this.mutationObserver) {
this.mutationObserver.disconnect();
this.mutationObserver = null;
}
},
check(element) {
if (!currentConfig.modules.removeExternalScripts || ProcessedElementsCache.isProcessed(element) || Utils.isParentProcessed(element)) return false;
if (element.tagName === 'SCRIPT' && element.src) {
const contentIdentifier = Utils.getContentIdentifier(element);
if (contentIdentifier && !Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
LogManager.add('removeExternalScripts', element, { type: '外联脚本移除', detail: `SRC: ${Utils.truncateString(element.src, 100)}` });
ResourceCanceller.cancelResourceLoading(element);
ProcessedElementsCache.markAsProcessed(element);
return true;
}
}
return false;
}
};
const ThirdPartyModule = {
originalFetch: null,
originalXhrOpen: null,
originalXhrSend: null,
originalCreateElement: null,
originalAppendChild: null,
originalDocumentWrite: null,
originalEval: null,
originalFunction: null,
init() {
if (currentConfig.modules.interceptThirdPartyResources) {
this.enable();
}
},
enable() {
const cspBlockEval = currentConfig.cspRules.find(rule => rule.id === 3 && rule.enabled);
if (!cspBlockEval) {
this.originalEval = unsafeWindow.eval;
unsafeWindow.eval = (code) => {
const contentIdentifier = Utils.getContentIdentifier(null, { type: 'EVAL', detail: code });
if (contentIdentifier && !Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
LogManager.add('interceptThirdPartyResources', null, { type: 'EVAL拦截', detail: `代码: ${Utils.truncateString(code, 100)}` });
return undefined;
}
return this.originalEval.call(unsafeWindow, code);
};
}
const cspBlockFunction = currentConfig.cspRules.find(rule => rule.id === 3 && rule.enabled);
if (!cspBlockFunction) {
this.originalFunction = unsafeWindow.Function;
unsafeWindow.Function = function(...args) {
const code = args.length > 0 ? args[args.length - 1] : '';
const contentIdentifier = Utils.getContentIdentifier(null, { type: 'FUNCTION_CONSTRUCTOR', detail: code });
if (contentIdentifier && !Whitelisting.isContentWhitelisted(Utils.getCurrentHostname(), contentIdentifier)) {
LogManager.add('interceptThirdPartyResources', null, { type: 'Function构造拦截', detail: `代码: ${Utils.truncateString(code, 100)}` });
return () => {};
}
return this.originalFunction.apply(unsafeWindow, args);
};
}
this.originalFetch = window.fetch;
window.fetch = (input, init) => {
if (!currentConfig.modules.interceptThirdPartyResources) {
return this.originalFetch.call(this, input, init);
}
const url = typeof input === 'string' ? input : input.url;
const resourceURL = Utils.getAbsoluteURL(url);
const resourceHostname = Utils.getResourceHostname(resourceURL);
const currentHost = Utils.getCurrentHostname();
const contentIdentifier = Utils.getContentIdentifier(null, { type: 'FETCH', detail: `URL: ${Utils.truncateString(resourceURL, 100)}` });
if (resourceHostname && Utils.isThirdPartyHost(resourceHostname, currentHost)) {
if (contentIdentifier && !Whitelisting.isContentWhitelisted(currentHost, 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.call(this, input, init);
};
this.originalXhrOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url) {
if (!currentConfig.modules.interceptThirdPartyResources) {
return this.originalXhrOpen.apply(this, arguments);
}
const resourceURL = Utils.getAbsoluteURL(url);
const resourceHostname = Utils.getResourceHostname(resourceURL);
const currentHost = Utils.getCurrentHostname();
const contentIdentifier = Utils.getContentIdentifier(null, { type: 'XHR', detail: `URL: ${Utils.truncateString(resourceURL, 100)}` });
if (resourceHostname && Utils.isThirdPartyHost(resourceHostname, currentHost)) {
if (contentIdentifier && !Whitelisting.isContentWhitelisted(currentHost, 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 || ProcessedElementsCache.isProcessed(element)) {
return element;
}
const resourceURL = Utils.getAbsoluteURL(element.src || element.getAttribute('data-src') || element.href || element.action);
if (resourceURL) {
const resourceHostname = Utils.getResourceHostname(resourceURL);
const currentHost = Utils.getCurrentHostname();
const contentIdentifier = Utils.getContentIdentifier(element);
if (resourceHostname && Utils.isThirdPartyHost(resourceHostname, currentHost)) {
if (contentIdentifier && !Whitelisting.isContentWhitelisted(currentHost, contentIdentifier)) {
LogManager.add('interceptThirdPartyResources', element, { type: '第三方资源拦截', detail: `SRC: ${Utils.truncateString(resourceURL, 100)}` });
ResourceCanceller.cancelResourceLoading(element);
ProcessedElementsCache.markAsProcessed(element);
return element;
}
}
}
return element;
};
this.originalAppendChild = Node.prototype.appendChild;
Node.prototype.appendChild = function(node) {
if (!currentConfig.modules.interceptThirdPartyResources || !Utils.isElement(node) || ProcessedElementsCache.isProcessed(node) || Utils.isParentProcessed(node)) {
return this.originalAppendChild.call(this, node);
}
const resourceURL = Utils.getAbsoluteURL(node.src || node.getAttribute('data-src') || node.href || node.action);
if (resourceURL) {
const resourceHostname = Utils.getResourceHostname(resourceURL);
const currentHost = Utils.getCurrentHostname();
const contentIdentifier = Utils.getContentIdentifier(node);
if (resourceHostname && Utils.isThirdPartyHost(resourceHostname, currentHost)) {
if (contentIdentifier && !Whitelisting.isContentWhitelisted(currentHost, contentIdentifier)) {
LogManager.add('interceptThirdPartyResources', node, { type: '第三方资源拦截', detail: `SRC: ${Utils.truncateString(resourceURL, 100)}` });
ResourceCanceller.cancelResourceLoading(node);
ProcessedElementsCache.markAsProcessed(node);
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]');
let modifiedContent = content;
for (const script of scripts) {
const src = script.src;
if (src) {
const resourceURL = Utils.getAbsoluteURL(src);
const resourceHostname = Utils.getResourceHostname(resourceURL);
const currentHost = Utils.getCurrentHostname();
const contentIdentifier = Utils.getContentIdentifier(script);
if (resourceHostname && Utils.isThirdPartyHost(resourceHostname, currentHost)) {
if (contentIdentifier && !Whitelisting.isContentWhitelisted(currentHost, contentIdentifier)) {
LogManager.add('interceptThirdPartyResources', script, { type: '第三方脚本通过document.write拦截', detail: `SRC: ${Utils.truncateString(resourceURL, 100)}` });
ResourceCanceller.cancelResourceLoading(script);
ProcessedElementsCache.markAsProcessed(script);
}
}
}
}
modifiedContent = tempDiv.innerHTML;
this.originalDocumentWrite.call(document, modifiedContent);
}.bind(this);
},
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;
if (this.originalEval) unsafeWindow.eval = this.originalEval;
if (this.originalFunction) unsafeWindow.Function = this.originalFunction;
},
check(element) {
if (!currentConfig.modules.interceptThirdPartyResources || !Utils.isElement(element) || ProcessedElementsCache.isProcessed(element) || Utils.isParentProcessed(element)) return false;
const resourceURL = Utils.getAbsoluteURL(element.src || element.getAttribute('data-src') || element.href || element.action);
if (resourceURL) {
const resourceHostname = Utils.getResourceHostname(resourceURL);
const currentHost = Utils.getCurrentHostname();
const contentIdentifier = Utils.getContentIdentifier(element);
if (resourceHostname && Utils.isThirdPartyHost(resourceHostname, currentHost)) {
if (contentIdentifier && !Whitelisting.isContentWhitelisted(currentHost, contentIdentifier)) {
LogManager.add('interceptThirdPartyResources', element, { type: '第三方资源拦截', detail: `SRC: ${Utils.truncateString(resourceURL, 100)}` });
ResourceCanceller.cancelResourceLoading(element);
ProcessedElementsCache.markAsProcessed(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 ruleDescriptions = currentConfig.cspRules.map(r => ({
id: r.id,
name: r.name,
description: this.getRuleDescription(r),
enabled: r.enabled
}));
const rulesDisplay = ruleDescriptions
.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();
needsReload = true;
} else if (input === 'disable') {
currentConfig.modules.manageCSP = false;
this.removeCSP();
needsReload = true;
} else if (input === 'allon') {
currentConfig.cspRules.forEach(rule => rule.enabled = true);
if (currentConfig.modules.manageCSP) this.applyCSP();
needsReload = true;
} else if (input === 'alloff') {
currentConfig.cspRules.forEach(rule => rule.enabled = false);
if (currentConfig.modules.manageCSP) this.removeCSP();
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();
needsReload = true;
} else {
alert("未找到指定规则。");
}
} else {
alert("无效格式。");
}
} else {
alert("无效指令。");
}
if (needsReload) {
StorageManager.saveConfig();
location.reload();
}
},
getRuleDescription(rule) {
const descriptions = {
1: "只允许本网站脚本",
2: "禁止页面内脚本",
3: "禁止eval执行代码",
4: "只允许本网站样式",
5: "禁止页面内样式",
6: "只允许本网站图片",
7: "禁止框架加载",
8: "禁止媒体加载",
9: "阻止对象嵌入"
};
return descriptions[rule.id] || "无描述";
}
};
const UIController = {
mutationObserver: null,
batchProcessingQueue: [],
batchSize: 15,
isProcessingBatch: false,
lastProcessTime: 0,
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.manageCSP === undefined) {
currentConfig.modules.manageCSP = false;
}
if (currentConfig.modules.interceptSubdomains === undefined) {
currentConfig.modules.interceptSubdomains = false;
}
if (currentConfig.modules.smartInterception === undefined) {
currentConfig.modules.smartInterception = false;
}
},
registerMenuCommands() {
GM_registerMenuCommand(`🔘 广告拦截 [${this.isAnyModuleEnabled() ? '✅' : '❌'}]`, () => this.toggleAllModules());
const moduleOrder = ['smartInterception', 'removeInlineScripts', 'removeExternalScripts', 'interceptThirdPartyResources', 'interceptSubdomains', 'manageCSP'];
moduleOrder.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();
location.reload();
},
toggleModule(key) {
if (key === 'interceptSubdomains') {
if (!currentConfig.modules.interceptThirdPartyResources && !currentConfig.modules.interceptSubdomains) {
if (confirm('开启子域名拦截需要同时开启第三方资源拦截,是否同时开启?')) {
currentConfig.modules.interceptThirdPartyResources = true;
currentConfig.modules.interceptSubdomains = true;
} else {
return;
}
} else {
currentConfig.modules[key] = !currentConfig.modules[key];
}
} else if (key === 'interceptThirdPartyResources') {
const newState = !currentConfig.modules.interceptThirdPartyResources;
currentConfig.modules.interceptThirdPartyResources = newState;
if (!newState && currentConfig.modules.interceptSubdomains) {
currentConfig.modules.interceptSubdomains = false;
}
} else if (key === 'smartInterception') {
currentConfig.modules[key] = !currentConfig.modules[key];
} else {
currentConfig.modules[key] = !currentConfig.modules[key];
}
this.applyModuleSettings();
StorageManager.saveConfig();
location.reload();
},
applyModuleSettings() {
SmartInterceptionModule.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' ||
key === 'smartInterception'
)
);
if (!relevantModulesEnabled) return;
this.mutationObserver = new MutationObserver(Utils.throttle((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE && !ProcessedElementsCache.isProcessed(node) && !Utils.isParentProcessed(node)) {
this.addToBatchProcessingQueue(node);
}
}
}
}, 100));
this.mutationObserver.observe(document.documentElement, {
childList: true,
subtree: true,
});
this.processExistingElementsBatch();
},
addToBatchProcessingQueue(element) {
this.batchProcessingQueue.push(element);
if (!this.isProcessingBatch) {
this.processBatch();
}
},
processBatch() {
this.isProcessingBatch = true;
const processChunk = () => {
const now = Date.now();
if (now - this.lastProcessTime < 16) {
requestAnimationFrame(processChunk);
return;
}
const chunk = this.batchProcessingQueue.splice(0, this.batchSize);
this.lastProcessTime = now;
chunk.forEach(node => {
if (ProcessedElementsCache.isProcessed(node) || Utils.isParentProcessed(node)) return;
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)) ResourceCanceller.cancelResourceLoading(node);
} else if (currentConfig.modules.interceptThirdPartyResources) {
if (ThirdPartyModule.check(node)) ResourceCanceller.cancelResourceLoading(node);
}
} else if (currentConfig.modules.interceptThirdPartyResources) {
if (ThirdPartyModule.check(node)) ResourceCanceller.cancelResourceLoading(node);
}
if (currentConfig.modules.smartInterception) {
SmartInterceptionModule.check(node);
}
});
if (this.batchProcessingQueue.length > 0) {
requestAnimationFrame(processChunk);
} else {
this.isProcessingBatch = false;
}
};
requestAnimationFrame(processChunk);
},
processExistingElementsBatch() {
// 移除了 svg, canvas 从选择器中
const selector = 'script, iframe, img, a[href], link[rel="stylesheet"], form, video, audio, source, img[data-src], style, link[rel="preload"], link[rel="prefetch"]';
const elementsToProcess = Array.from(document.querySelectorAll(selector));
const processInChunks = () => {
const chunk = elementsToProcess.splice(0, this.batchSize * 2);
chunk.forEach(element => {
if (!ProcessedElementsCache.isProcessed(element) && !Utils.isParentProcessed(element)) {
this.addToBatchProcessingQueue(element);
}
});
if (elementsToProcess.length > 0) {
setTimeout(processInChunks, 0);
}
};
processInChunks();
},
resetSettings() {
if (confirm("确定要重置所有设置吗?这将清除所有配置和白名单。")) {
Object.assign(currentConfig.modules, DEFAULT_MODULE_STATE);
currentConfig.cspRules = DEFAULT_CSP_RULES_TEMPLATE.map(rule => ({ ...rule }));
Whitelisting.clearAllWhitelists();
ProcessedElementsCache.clear();
CSPModule.removeCSP();
StorageManager.saveConfig();
location.reload();
}
}
};
function initializeAdBlocker() {
UIController.init();
if (currentConfig.modules.manageCSP) {
CSPModule.applyCSP();
}
}
if (document.readyState === 'loading') {
initializeAdBlocker();
} else {
setTimeout(initializeAdBlocker, 0);
}
})();