广告终结者
// ==UserScript==
// @name 广告终结者
// @namespace http://tampermonkey.net/
// @version 3.9.5.3.1
// @description 一个手机端via浏览器能用的强大的广告拦截器
// @match *://*/*
// @license MIT
// @grant unsafeWindow
// @grant GM_registerMenuCommand
// @grant GM_notification
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_getResourceText
// @grant GM_addStyle
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
const originalMutationObserver = window.MutationObserver;
window.MutationObserver = class extends originalMutationObserver {
observe(target, options) {
const sanitizedOptions = {
childList: options.childList ? true : false,
attributes: options.attributes ?
[...(options.attributeFilter || []), 'data-ad-', 'src', 'id', 'style', 'class'].some(f => f) :
false,
subtree: true,
characterData: false
};
return super.observe(target, Object.assign({}, options, sanitizedOptions));
}
takeRecords() {
return [];
}
};
const HOSTNAME = location.hostname.replace(/\./g, '\\.');
const REGEX = {
dynamicId: /^(?:([0-9a-f]{4}-?){4,}|[0-9a-f]{16,}|[\dA-F-]{18,}|[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12})$/i,
jsAdPattern: /(?:window\.open|document\.write|createElement\(['"]script['"]|location\.replace|setTimeout\s*\([^)]*?\b(?:window\.open|document\.write)|eval\s*\(|new\s+Function\s*\(|appendChild\s*\(.*?\bscript\b|on(?:click|load)\s*=\s*['"]?\s*(?:window\.open))/i,
adAttributes: /(推广|广告|gg|sponsor|推荐|guanggao|syad|bfad|弹窗|悬浮|葡京|banner|pop(?:up|under)|track(?:ing)?)(?:$|[_-])/i,
thirdParty: new RegExp(`^(https?:\\/\\/(.*\\.)?${HOSTNAME}(\\/|$)|data:|about:blank)`, 'i'),
obfuscationPatterns: [
{ type: '字符串混淆属性访问', pattern: /\["\\x73\\x72\\x63"\]\s*=/i, riskLevel: 3 },
{ type: 'Math长属性访问', pattern: /^Math\[\s*["']\w{5,}["']\s*\]/im, riskLevel: 2 },
{ type: '连续十六进制转义', pattern: /\\x[0-9a-f]{2}(?:\\x[0-9a-f]{2}){5,}/gi, riskLevel: 3 },
{ type: '连续Unicode转义', pattern: /\\u[0-9a-f]{4}(?:\\u[0-9a-f]{4}){3,20}/gi, riskLevel: 3 },
{ type: '数组数值操作', pattern: /\[(?:0x[0-9a-f]+|\d+),?(?:0x[0-9a-f]+|\d+)\]\.(?:join|reverse|map)/i, riskLevel: 2 },
{ type: '动态代码执行', pattern: /(eval|(?:new\s+Function\s*\(\s*(["'])\s*)\1|set(?:Timeout|Interval|Immediate)\s*\(\s*function\s*\(\)\s*{)/i, riskLevel: 4 },
{ type: 'Window动态属性访问', pattern: /window\[\s*["']\w{5,}["']\s*\]/im, riskLevel: 3 },
{ type: 'Document动态属性访问', pattern: /document\[\s*(["'])\w{5,}\1\s*\]/im, riskLevel: 3 },
{ type: '长哈希值属性', pattern: /\b[\w$]+\s*:\s*"(?:\\x[0-9a-f]{2}){16,}|[\da-f]{32}"/i, riskLevel: 3 },
{ type: '短变量名长字符串赋值', pattern: /\b[a-z\d]{1,3}\s*=\s*["']\w{16,}["']/gi, riskLevel: 2 },
{ type: '可疑变量初始化', pattern: /(var|let|const)\s+([a-z\d]{2,4})\s*=\s*(?:\d{8,}|"(?:[0-9a-f]{16,}|[\w+/=]{20,})")/gi, riskLevel: 3 },
{ type: '隐藏iframe创建', pattern: /document\.createElement\(['"]iframe['"]\)\.setAttribute\(['"]style['"],\s*['"]display:\s*none/im, riskLevel: 4 },
{ type: '空白iframe加载', pattern: /document\.createElement\(['"]iframe['"]\)\.src\s*=\s*['"]about:blank['"]/im, riskLevel: 3 },
{ type: '多重属性混淆', pattern: /\[(?:'src'|"data-rc")\]\.forEach\(function\(a\)\{this\.setAttribute\(a,/im, riskLevel: 4 },
{ type: '定时器混淆链', pattern: /setTimeout\(function\(\)\{setInterval\(function\(\)\{new\s+Function\(.*?\)/gim, riskLevel: 5 },
{ type: '双重编码payload', pattern: /eval\(atob\(['"][A-Za-z0-9+/=]{40,}['"]\)\)/gi, riskLevel: 5 },
{ type: '长Base64字符串', pattern: /[A-Za-z0-9+/=]{60,}/gi, riskLevel: 3 },
{ type: '异常try-catch结构', pattern: /try\s*\{[^}]*\}\s*catch\s*\(e\)\s*\{[^}]*eval\(e\.message\)/gim, riskLevel: 4 },
{ type: '多级属性访问', pattern: /window\[\w{5,}\]\[\w{5,}\]\[\w{5,}\]/gim, riskLevel: 3 },
{ type: '异常函数调用链', pattern: /function\s+\w{1,3}\(\)\{return\s+\w{1,3}\(\)\.call\(this\)\}\(\).apply\(window\)/gim, riskLevel: 4 },
{ type: '隐藏样式注入', pattern: /document\.body\.style\.\w+\s*=\s*['"]none['"]/gim, riskLevel: 2 },
{ type: '多层数组混淆', pattern: /\[\[\[['"]\w+['"]\]\]\]/gim, riskLevel: 3 },
{ type: '异常事件监听', pattern: /addEventListener\(['"]click['"],\s*function\(\)\{[^}]*eval\(/gim, riskLevel: 4 },
{ type: '十六进制混淆', pattern: /\\x[0-9a-f]{2}(?:\\x[0-9a-f]{2}){5,}/gi, riskLevel: 4 },
{ type: 'Unicode混淆', pattern: /\\u[0-9a-f]{4}(?:\\u[0-9a-f]{4}){3,}/gi, riskLevel: 3 },
{ type: '多层数组混淆', pattern: /\[\[\[['"]\w+['"]\]\]\]/gim, riskLevel: 3 },
{ type: '动态属性拼接', pattern: /\[(['"])\w+\1\s*\+\s*['"]\w+['"]\]/g, riskLevel: 4 },
{ type: '环境检测代码', pattern: /navigator\.\w+\s*===?\s*undefined/g, riskLevel: 5 },
{ type: '时间戳混淆', pattern: /0x[a-f0-9]{8}(?:\+0x[a-f0-9]{8}){3,}/g, riskLevel: 3 },
{ type: '反格式化字符串', pattern: /%[0-9a-f]{2}%[0-9a-f]{2}%[0-9a-f]{2}/g, riskLevel: 3 },
{ type: '异常函数链', pattern: /function\s+\w{1,3}\(\)\{return\s+\w{1,3}\(\)\.call\(this\)\}\(\)\.apply\(window\)/gim, riskLevel: 4 }
],
};
const DEFAULT_MODULES = {
main: false,
dynamicSystem: false,
layoutSystem: false,
frameSystem: false,
mergedMediaSystem: false,
thirdPartyBlock: false,
specialUA: true
};
const DEFAULT_CSP_RULES = [
{ id: 1, name: '仅允许同源脚本', rule: "script-src 'self'", enabled: true },
{ id: 2, name: '禁止内联脚本', rule: "script-src 'unsafe-inline'", enabled: false },
{ id: 3, name: '禁止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 = {
modules: { ...DEFAULT_MODULES },
protectionRules: {
dynamicIdLength: 32,
zIndexThreshold: 100,
maxFrameDepth: 4
},
csp: {
enabled: false,
rules: DEFAULT_CSP_RULES.map(rule => ({ ...rule }))
},
mobileOptimizations: {
lazyLoadImages: true,
removeImagePlaceholders: true
},
moduleNames: {
dynamicSystem: '动态检测系统',
layoutSystem: '布局检测系统',
frameSystem: '框架过滤系统',
mergedMediaSystem: '图片广告移除',
thirdPartyBlock: '第三方拦截',
specialUA: '添加特殊UA'
},
modulePriority: [
'thirdPartyBlock',
'specialUA',
'frameSystem',
'dynamicSystem',
'mergedMediaSystem',
'layoutSystem',
],
performance: {
highRiskModules: ['thirdPartyBlock', 'frameSystem'],
visibleAreaPriority: true,
styleCacheTimeout: 600000,
mutationProcessLimit: 40,
mutationProcessTimeLimit: 10,
idleCallbackTimeout: 2500,
throttleScrollDelay: 200,
debounceMutationDelay: 300,
longTaskThreshold: 500,
degradeThreshold: 5
},
originalUA: {
userAgent: navigator.userAgent,
platform: navigator.platform
}
};
const Logs = {
logs: [],
processedElements: new WeakSet(),
maxLogs: 30,
add(moduleKey, element, reason) {
if (element) {
if (!(element instanceof Element)) {
console.warn('Invalid element:', element);
return;
}
if (this.processedElements.has(element)) {
return;
}
}
const getElementInfo = (el) => {
try {
return {
tag: el.tagName || '未知标签',
id: el.id ? `#${el.id}` : '',
class: el.className ? `.${el.className.split(/\s+/).join('.')}` : '',
src: el.src || el.href || el.dataset.src || '',
attributes: Array.from(el.attributes)
.filter(attr => ['id', 'class', 'src', 'href'].includes(attr.name))
.map(attr => `${attr.name}="${attr.value.slice(0,30)}"`)
}
} catch(e) {
return { error: '元素解析失败' }
}
};
const elementInfo = element ? getElementInfo(element) : {};
const content = element ? this.getContent(element) : '动态创建元素';
if (element) {
this.processedElements.add(element);
}
this.logs.push({
module: CONFIG.moduleNames[moduleKey] || moduleKey,
element: `${elementInfo.tag}${elementInfo.id}${elementInfo.class}`,
content: content,
context: {
src: elementInfo.src?.slice(0, 200) || '',
attributes: elementInfo.attributes || [],
parent: element?.parentElement?.tagName || '无父节点'
},
reason: {
type: reason.type || '自动拦截',
detail: reason.detail || '',
pattern: reason.regex ? reason.regex.map(r => r.source || r) : [],
stack: new Error().stack.split('\n').slice(2,5)
},
timestamp: Date.now()
});
if (this.logs.length > this.maxLogs) this.logs = this.logs.slice(-this.maxLogs);
},
getContent(element) {
try {
return element.outerHTML
.replace(/\s+/g, ' ')
.replace(/<!--.*?-->/g, '')
.slice(0, 200) + (element.outerHTML.length > 200 ? '...' : '');
} catch(e) {
return '[安全限制无法获取内容]';
}
},
clear() {
this.logs = [];
this.processedElements = new WeakSet();
GM_notification('日志已清空');
}
};
const perf = {
pendingTasks: [],
isProcessing: false,
processed: new WeakSet(),
adElements: new Set(),
styleCache: new Map(),
lastStyleCacheClear: Date.now(),
longTaskCount: 0,
degraded: true
};
const ModuleManager = {
modules: {},
register(name, module) {
this.modules[name] = module;
},
init() {
Object.values(this.modules).forEach(module => {
if (typeof module.init === 'function') {
module.init();
}
});
},
run(moduleName, element) {
if (!CONFIG.modules.main || !CONFIG.modules[moduleName]) return false;
const module = this.modules[moduleName];
if (module && typeof module.check === 'function') {
return module.check(element);
}
return false;
}
};
const AdUtils = {
processedScripts: new WeakSet(),
originalAppendChild: Node.prototype.appendChild,
originalCreateElement: document.createElement,
isDynamicSystemActive: false,
observer: null,
initScriptObserver: function() {
const originalAppend = Node.prototype.appendChild;
const originalCreate = document.createElement;
Node.prototype.appendChild = function(node) {
if (AdUtils.isDynamicSystemActive && node.tagName === 'SCRIPT') {
AdUtils.processScriptElement(node);
return node;
}
return originalAppend.call(this, node);
};
document.createElement = function(tagName) {
const el = originalCreate.call(document, tagName);
if (AdUtils.isDynamicSystemActive && tagName.toLowerCase() === 'script') {
return AdUtils.createScriptProxy(el);
}
return el;
};
this.observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1 && node.tagName === 'SCRIPT') {
AdUtils.processScriptElement(node);
}
});
});
});
if (this.isDynamicSystemActive) {
this.observer.observe(document.documentElement, {
childList: true,
subtree: true
});
}
},
createScriptProxy: function(scriptElement) {
return new Proxy(scriptElement, {
set: (target, prop, value) => {
if (prop === 'src' || prop === 'innerHTML') {
AdUtils.processScriptElement(target);
}
target[prop] = value;
return true;
}
});
},
processScriptElement: function(scriptElement) {
if (this.processedScripts.has(scriptElement)) return;
if (ModuleManager.modules.dynamicSystem?.enabled) {
this.nuclearRemove(scriptElement, 'dynamicSystem', {
type: '动态脚本防御',
detail: '拦截动态创建的脚本元素'
});
['src', 'onload', 'onerror', 'integrity'].forEach(prop => {
try {
delete scriptElement[prop];
} catch(e) {}
});
}
},
nuclearRemove: function(scriptElement, moduleKey, reason) {
if (scriptElement.dataset.adNuclearRemoved) return false;
if (Logs.processedElements.has(scriptElement)) return false;
try {
if (scriptElement.parentNode) {
scriptElement.parentNode.removeChild(scriptElement);
}
const unsafeAttributes = ['src', 'onload', 'onerror', 'integrity'];
unsafeAttributes.forEach(attr => {
scriptElement.removeAttribute(attr);
});
if (scriptElement.src) {
const abort = new AbortController();
fetch(scriptElement.src, {
signal: abort.signal,
priority: 'low'
}).catch(() => {});
abort.abort();
}
scriptElement.dataset.adNuclearRemoved = true;
this.processedScripts.add(scriptElement);
Logs.add(moduleKey, scriptElement, {
...reason,
detail: `[原子级移除] ${reason.detail}`,
nuclear: true
});
return true;
} catch(e) {
console.error('终极移除失败:', e);
return false;
}
},
safeRemove: function(element, moduleKey, reason) {
if (element.tagName === 'SCRIPT') {
return this.nuclearRemove(element, moduleKey, reason);
}
if (Logs.processedElements.has(element)) return false;
try {
Logs.add(moduleKey, element, reason);
element.style.display = 'none';
element.style.visibility = 'hidden';
element.setAttribute('data-ad-removed', 'true');
return true;
} catch(e) {
return false;
}
},
enableDynamicSystem: function() {
if (!this.isDynamicSystemActive) {
this.isDynamicSystemActive = true;
this.initScriptObserver();
}
},
disableDynamicSystem: function() {
if (this.isDynamicSystemActive) {
this.isDynamicSystemActive = false;
Node.prototype.appendChild = this.originalAppendChild;
document.createElement = this.originalCreateElement;
if (this.observer) {
this.observer.disconnect();
}
}
},
throttleCheck: function(element) {
if (this.throttleQueue.has(element)) return;
this.throttleQueue.add(element);
if (this.throttleQueue.size === 1) this.processQueue();
},
processQueue: function() {
requestIdleCallback(() => {
const startTime = performance.now();
const iterator = this.throttleQueue.values();
let entry = iterator.next();
while (!entry.done && performance.now() - startTime < 8) {
const element = entry.value;
if (element.tagName === 'SCRIPT') {
this.checkScript(element);
} else {
this.checkStaticElements(element);
}
this.throttleQueue.delete(entry.value);
entry = iterator.next();
}
if (this.throttleQueue.size > 0) this.processQueue();
}, { timeout: 50 });
}
};
const StyleManager = {
styleElement: null,
injectedSheets: new Set(),
inject() {
if (this.injectedSheets.size > 0) return;
const sheet = new CSSStyleSheet();
sheet.replaceSync(`
[data-ad-cleaned] {
all: initial !important;
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
position: absolute !important;
z-index: -9999 !important;
width: 0 !important;
height: 0 !important;
pointer-events: none !important;
}
[data-ad-cleaned] * {
all: unset !important;
}
`);
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
this.injectedSheets.add(sheet);
const style = document.createElement('style');
style.textContent = `
@layer adBlocker {
:not([data-ad-cleaned]) {
contain: style !important;
}
[data-ad-cleaned] {
animation: none !important;
transition: none !important;
}
}
`;
style.setAttribute('data-ad-permanent', 'true');
document.documentElement.prepend(style);
const checkStyles = () => {
if (!document.contains(style)) {
document.documentElement.prepend(style);
}
if (!document.adoptedStyleSheets.includes(sheet)) {
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
}
requestAnimationFrame(checkStyles);
};
checkStyles();
},
remove() {
this.injectedSheets.forEach(sheet => {
document.adoptedStyleSheets = document.adoptedStyleSheets.filter(s => s !== sheet);
});
document.querySelectorAll('style[data-ad-permanent]').forEach(s => s.remove());
},
toggle(enable) {
enable ? this.inject() : this.remove();
}
};
const CSPManager = {
currentPolicy: null,
generatePolicy() {
const merged = {};
CONFIG.csp.rules.filter(rule => rule.enabled).forEach(rule => {
const [directive, ...values] = rule.rule.split(/\s+/);
merged[directive] = (merged[directive] || []).concat(values);
});
return Object.entries(merged)
.map(([k, v]) => `${k} ${[...new Set(v)].join(' ')}`)
.join('; ');
},
inject() {
this.remove();
if (!CONFIG.csp.enabled) return;
const policy = this.generatePolicy();
this.currentPolicy = policy;
const meta = document.createElement('meta');
meta.httpEquiv = "Content-Security-Policy";
meta.content = policy;
if (document.head) {
document.head.appendChild(meta);
} else {
document.documentElement.insertBefore(meta, document.documentElement.firstChild);
}
},
remove() {
const meta = document.querySelector('meta[http-equiv="Content-Security-Policy"]');
if (meta) meta.remove();
},
toggleRule(ruleId, enable) {
const rule = CONFIG.csp.rules.find(r => r.id === ruleId);
if (rule) rule.enabled = enable;
},
injectInitialCSP() {
if (!CONFIG.csp.enabled) return;
const initialRules = CONFIG.csp.rules
.filter(rule => [1, 6].includes(rule.id) && rule.enabled)
.map(rule => rule.rule)
.join('; ');
if (initialRules) {
const meta = document.createElement('meta');
meta.httpEquiv = "Content-Security-Policy";
meta.content = initialRules;
if (document.head) {
document.head.appendChild(meta);
} else {
document.documentElement.insertBefore(meta, document.documentElement.firstChild);
}
}
}
};
const Detector = {
getCachedStyle(el) {
if (Date.now() - perf.lastStyleCacheClear > CONFIG.performance.styleCacheTimeout) {
perf.styleCache.clear();
perf.lastStyleCacheClear = Date.now();
}
const key = `${el.nodeName}#${el.id}.${Array.from(el.classList).join('.')}`;
if (!perf.styleCache.has(key)) {
try {
perf.styleCache.set(key, getComputedStyle(el));
} catch (e) {
console.warn("Error getting computed style:", e);
return {};
}
}
return perf.styleCache.get(key);
},
isVisible(el) {
const style = getComputedStyle(el);
if (style.display === 'none' || style.visibility === 'hidden') return false;
const rect = el.getBoundingClientRect();
return !(
rect.width === 0 ||
rect.height === 0 ||
(rect.top >= window.innerHeight || rect.bottom <= 0) ||
(rect.left >= window.innerWidth || rect.right <= 0)
);
}
};
const Processor = {
collectAds() {
const treeWalker = document.createTreeWalker(
document.documentElement,
NodeFilter.SHOW_ELEMENT,
null,
false
);
perf.adElements.clear();
while (treeWalker.nextNode()) {
const element = treeWalker.currentNode;
if (perf.processed.has(element)) continue;
let modulePriority = CONFIG.modulePriority;
if (CONFIG.performance.visibleAreaPriority && Detector.isVisible(element)) {
modulePriority = [...CONFIG.performance.highRiskModules, ...modulePriority.filter(m => !CONFIG.performance.highRiskModules.includes(m))];
}
for (const moduleKey of modulePriority) {
if (ModuleManager.run(moduleKey, element)) {
perf.adElements.add(element);
perf.processed.add(element);
break;
}
}
}
},
processElements() {
if (perf.degraded) return;
let startTime = performance.now();
let elementCount = 0;
for (let element of perf.adElements) {
if (elementCount >= CONFIG.performance.mutationProcessLimit ||
performance.now() - startTime > CONFIG.performance.mutationProcessTimeLimit) {
perf.pendingTasks.push(() => Processor.processElements());
break;
}
if (perf.processed.has(element)) continue;
let modulePriority = CONFIG.modulePriority;
if (CONFIG.performance.visibleAreaPriority && Detector.isVisible(element)) {
modulePriority = [...CONFIG.performance.highRiskModules, ...modulePriority.filter(m => !CONFIG.performance.highRiskModules.includes(m))];
}
for (const moduleKey of modulePriority) {
if (ModuleManager.run(moduleKey, element)) {
perf.processed.add(element);
break;
}
}
elementCount++;
}
perf.isProcessing = false;
if (perf.pendingTasks.length > 0) {
scheduleProcess();
}
}
};
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 8 && node.data.includes('Removed ad')) {
node.parentNode.removeChild(node);
}
});
}
});
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1) {
if (node.matches('[data-ad-permanent], [data-ad-removed], [data-ad-cleaned]')) {
AdUtils.safeRemove(node, 'mutationClean', {
type: 'DOM清理',
detail: '检测到被移除元素的重新插入'
});
return;
}
const walker = document.createTreeWalker(
node,
NodeFilter.SHOW_ELEMENT,
{
acceptNode: (n) => {
return n.hasAttribute('data-ad-permanent') ?
NodeFilter.FILTER_ACCEPT :
NodeFilter.FILTER_SKIP;
}
}
);
let currentNode;
while ((currentNode = walker.nextNode())) {
AdUtils.safeRemove(currentNode, 'mutationClean', {
type: 'DOM深度清理',
detail: '检测到被移除元素的子元素'
});
}
}
});
} else if (mutation.type === 'attributes') {
if (mutation.target.hasAttribute('data-ad-permanent') &&
mutation.attributeName !== 'data-ad-permanent') {
mutation.target.removeAttribute(mutation.attributeName);
}
}
});
let startTime = performance.now();
let mutationCount = 0;
let nodeCount = 0;
for (let mutation of mutations) {
if (mutationCount >= CONFIG.performance.mutationProcessLimit ||
nodeCount >= 50 ||
performance.now() - startTime > CONFIG.performance.mutationProcessTimeLimit) {
debouncedScheduleProcess();
return;
}
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1) {
if (!perf.processed.has(node)) {
perf.adElements.add(node);
nodeCount++;
}
}
});
} else if (mutation.type === 'attributes') {
if (!perf.processed.has(mutation.target)) {
perf.adElements.add(mutation.target);
nodeCount++;
}
}
mutationCount++;
}
scheduleProcess();
});
const scheduleProcess = (() => {
const tasks = [];
let isScheduled = false;
function runTasks(deadline) {
while (tasks.length && (deadline.timeRemaining() > 2 || deadline.didTimeout)) {
const task = tasks.shift();
try {
task();
} catch(e) {
console.error('Task error:', e);
}
}
if (tasks.length) {
requestIdleCallback(runTasks, { timeout: CONFIG.performance.idleCallbackTimeout });
} else {
isScheduled = false;
}
}
return function(task) {
tasks.push(task);
if (!isScheduled) {
isScheduled = true;
if (typeof requestIdleCallback === 'function') {
requestIdleCallback(runTasks, { timeout: CONFIG.performance.idleCallbackTimeout });
} else {
setTimeout(() => runTasks({
timeRemaining: () => 50,
didTimeout: true
}), 50);
}
}
};
})();
const debouncedScheduleProcess = debounce(scheduleProcess, CONFIG.performance.debounceMutationDelay);
const UIController = {
init() {
this.loadConfig();
this.registerMainSwitch();
this.registerModuleSwitches();
this.registerLogCommands();
this.registerCSPCommands();
this.registerResetCommand();
StyleManager.toggle(CONFIG.modules.main);
},
registerMainSwitch() {
GM_registerMenuCommand(
`🔘 主开关 [${CONFIG.modules.main ? '✅' : '❌'}]`,
() => this.toggleMain()
);
},
registerModuleSwitches() {
Object.entries(CONFIG.moduleNames).forEach(([key, name]) => {
GM_registerMenuCommand(
`${name} [${CONFIG.modules[key] ? '✅' : '❌'}]`,
() => this.toggleModule(key, name)
);
});
},
registerLogCommands() {
GM_registerMenuCommand('📜 查看拦截日志', () => this.showLogs());
GM_registerMenuCommand('🧹 清空日志', () => Logs.clear());
},
registerCSPCommands() {
GM_registerMenuCommand('🛡️ CSP策略管理', () => this.manageCSP());
},
registerResetCommand() {
GM_registerMenuCommand('🔄 重置设置', () => this.resetSettings());
},
toggleMain() {
const newState = !CONFIG.modules.main;
Object.keys(CONFIG.modules).forEach(key => {
if (key !== 'main') CONFIG.modules[key] = newState;
});
CONFIG.modules.main = newState;
this.saveConfig();
StyleManager.toggle(newState);
GM_notification(`主开关已${newState ? '启用' : '禁用'}`, `所有模块${newState ? '✅ 已激活' : '❌ 已停用'}`);
location.reload();
},
toggleModule(key, name) {
CONFIG.modules[key] = !CONFIG.modules[key];
CONFIG.modules.main = Object.values(CONFIG.modules)
.slice(1).some(v => v);
this.saveConfig();
StyleManager.toggle(CONFIG.modules.main);
GM_notification(`${name} ${CONFIG.modules[key] ? '✅ 已启用' : '❌ 已禁用'}`);
location.reload();
},
showLogs() {
const logText = Logs.logs.map(log => {
const parts = [];
if (log.reason.type) parts.push(`类型: ${log.reason.type}`);
if (log.reason.detail) parts.push(`详细: ${log.reason.detail}`);
if (log.reason.pattern?.length) parts.push(`匹配规则: ${log.reason.pattern.join(', ')}`);
return [
`模块: ${log.module}`,
`元素: ${log.element}`,
`内容: ${log.content}`,
`上下文: ${log.context.src ? '来源:' + log.context.src : ''} ${log.context.parent ? '父节点:' + log.context.parent : ''}`,
...parts
].join('\n');
}).join('\n\n');
alert(`广告拦截日志(最近${Logs.maxLogs}条):\n\n${logText || '暂无日志'}`);
},
manageCSP() {
const rulesDisplay = CONFIG.csp.rules
.map(r => `${r.id}. ${r.name} (${r.enabled ? '✅' : '❌'})`)
.join('\n');
let input = prompt(
`当前CSP策略状态: ${CONFIG.csp.enabled ? '✅ 已启用' : '❌ 已禁用'}\n\n` +
"当前策略规则:\n" + rulesDisplay + "\n\n" +
"输入选项:\n1. 开启策略 (输入 'enable')\n2. 关闭策略 (输入 'disable')\n" +
"修改规则示例:输入 '1on' 启用规则1\n输入 '23off' 禁用规则2和规则3\n\n"+
"请输入你的选择:",
""
);
if (input === null) return;
input = input.trim().toLowerCase();
switch (input) {
case 'enable':
CONFIG.csp.enabled = true;
this.saveConfig();
CSPManager.inject();
alert("CSP策略已启用");
location.reload();
break;
case 'disable':
CONFIG.csp.enabled = false;
this.saveConfig();
CSPManager.remove();
alert("CSP策略已禁用");
location.reload();
break;
default:
if (/^\d+(?:on|off)$/i.test(input)) {
this.modifyCSPRules(input);
} else {
alert("无效输入");
}
}
},
modifyCSPRules(actionInput) {
const matches = actionInput.match(/^(\d+)(on|off)$/i);
if (matches) {
const ruleIds = matches[1].split('').map(Number);
const enable = matches[2].toLowerCase() === 'on';
let updatedRules = [];
ruleIds.forEach(id => {
const rule = CONFIG.csp.rules.find(r => r.id === id);
if (rule) {
rule.enabled = enable;
updatedRules.push(id);
}
});
this.saveConfig();
CSPManager.inject();
alert(`规则 ${updatedRules.join(',')} 已更新为 ${enable ? '启用' : '禁用'}`);
location.reload();
} else {
alert("输入格式错误,请按示例输入如'1on'或'123off'");
}
},
resetSettings() {
if (confirm("确定要重置所有设置吗?")) {
Object.assign(CONFIG.modules, DEFAULT_MODULES);
CONFIG.csp.enabled = false;
CONFIG.csp.rules = DEFAULT_CSP_RULES.map(rule => ({ ...rule }));
this.saveConfig();
CSPManager.remove();
alert("设置已重置为默认值");
location.reload();
}
},
saveConfig() {
GM_setValue(`adblockConfig_${location.hostname}`, JSON.stringify({
modules: CONFIG.modules,
csp: CONFIG.csp
}));
},
loadConfig() {
try {
const savedConfig = JSON.parse(GM_getValue(`adblockConfig_${location.hostname}`, '{}'));
if (savedConfig.modules) {
Object.keys(CONFIG.modules).forEach(key => {
if (savedConfig.modules.hasOwnProperty(key)) {
CONFIG.modules[key] = savedConfig.modules[key];
}
});
}
if (savedConfig.csp) {
Object.assign(CONFIG.csp, savedConfig.csp);
}
} catch (e) {
console.error("加载配置失败:", e);
}
}
};
function init() {
UIController.init();
CSPManager.injectInitialCSP();
ModuleManager.init();
observer.observe(document.documentElement, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['id', 'class', 'data-ad', 'src']
});
Processor.collectAds();
scheduleProcess(() => Processor.processElements());
window.addEventListener('scroll', throttle(() => {
Processor.collectAds();
scheduleProcess(() => Processor.processElements());
}, CONFIG.performance.throttleScrollDelay));
window.addEventListener('resize', debounce(() => {
Processor.collectAds();
scheduleProcess(() => Processor.processElements());
}, 150));
if (CONFIG.mobileOptimizations.lazyLoadImages) {
document.addEventListener('DOMContentLoaded', () => {
const images = document.querySelectorAll('img[data-src]');
images.forEach(img => {
img.src = img.dataset.src;
img.removeAttribute('data-src');
});
});
}
if (CONFIG.mobileOptimizations.removeImagePlaceholders) {
document.addEventListener('DOMContentLoaded', () => {
const placeholders = document.querySelectorAll('.image-placeholder');
placeholders.forEach(placeholder => {
placeholder.parentNode.removeChild(placeholder);
});
});
}
}
function throttle(fn, delay) {
let last = 0, timer = null;
return function(...args) {
const now = Date.now();
if (now - last >= delay) {
fn.apply(this, args);
last = now;
} else {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
last = now;
}, delay - (now - last));
}
};
}
function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => {
try {
fn.apply(this, args);
} catch(e) {
Logs.add('error', this, {
type: 'debounceError',
detail: e.message
});
}
}, delay);
};
}
const ASTAnalyzer = (function() {
const tokenTypes = {
IDENTIFIER: 'identifier',
STRING: 'string',
KEYWORD: 'keyword',
OPERATOR: 'operator'
};
return {
getObfuscationRules: function(minLevel = 2) {
return CONFIG.obfuscationPatterns.filter(r => r.riskLevel >= minLevel);
},
checkObfuscation: function(code, minLevel = 2) {
const rules = this.getObfuscationRules(minLevel);
for (let i = 0; i < rules.length; i++) {
try {
if (rules[i].pattern.test(code)) {
return true;
}
} catch (e) {
console.error('[AST] 规则执行错误:', rules[i].type, e);
}
}
return false;
},
detectBase64: function(code) {
const rule = CONFIG.obfuscationPatterns.find(r => r.type === '长Base64字符串');
return rule ? rule.pattern.test(code) : false;
},
checkLexicalPatterns: function(code) {
const tokens = this.tokenize(code);
return this.checkLongIdentifiers(tokens) ||
this.checkHexEscapes(tokens) ||
this.checkObfuscation(code, 2);
},
checkDynamicBehavior: function(code) {
return this.checkObfuscation(code, 3) ||
this.checkDynamicElementCreation(code);
},
checkDynamicElementCreation: function(code) {
const rules = this.getObfuscationRules(3).filter(
r => r.type.includes('iframe') || r.type.includes('元素创建')
);
return rules.some(r => r.pattern.test(code));
},
tokenize: function(code) {
const tokens = [];
const regex = /(\b[a-zA-Z_$][\w$]*\b)|(['"])(?:(?!\2).|\\.)*?\2|([{}()[\];,])|(\S)/g;
let match;
while ((match = regex.exec(code)) !== null) {
if (match[1]) {
tokens.push({ type: tokenTypes.IDENTIFIER, value: match[1] });
} else if (match[2]) {
tokens.push({ type: tokenTypes.STRING, value: match[0] });
} else if (match[3]) {
tokens.push({ type: tokenTypes.OPERATOR, value: match[3] });
} else {
tokens.push({ type: 'other', value: match[4] });
}
}
return tokens;
},
checkLongIdentifiers: function(tokens) {
return tokens.some(t =>
t.type === tokenTypes.IDENTIFIER &&
t.value.length > CONFIG.protectionRules.dynamicIdLength
);
},
checkHexEscapes: function(tokens) {
let hexCount = 0;
for (const t of tokens) {
if (t.type === tokenTypes.STRING) {
const matches = t.value.match(/\\x[0-9a-f]{2}/gi) || [];
hexCount += matches.length;
if (hexCount >= 3) return true;
}
}
return false;
}
};
})();
ModuleManager.register('thirdPartyBlock', {
originals: {
createElement: document.createElement,
setAttribute: Element.prototype.setAttribute,
appendChild: Node.prototype.appendChild,
fetch: window.fetch,
xhrOpen: XMLHttpRequest.prototype.open,
documentWrite: document.write,
documentWriteln: document.writeln,
insertAdjacentHTML: Element.prototype.insertAdjacentHTML
},
blockedUrls: new Set(),
enabled: false,
hostCache: new Map(),
abortControllers: new WeakMap(),
init() {
if (!CONFIG.modules.thirdPartyBlock) return this.disable();
this.enable();
},
enable() {
if (this.enabled) return;
this.enabled = true;
document.createElement = this.wrapElementCreation.bind(this);
Element.prototype.setAttribute = this.wrapSetAttribute.bind(this);
Node.prototype.appendChild = this.wrapAppendChild.bind(this);
window.fetch = this.wrapFetch.bind(this);
XMLHttpRequest.prototype.open = this.wrapXHROpen.bind(this);
document.write = this.wrapDocumentWrite.bind(this);
document.writeln = this.wrapDocumentWriteln.bind(this);
Element.prototype.insertAdjacentHTML = this.wrapInsertAdjacentHTML.bind(this);
document.addEventListener('beforescriptexecute', this.handleBeforeScriptExecute.bind(this));
},
disable() {
if (!this.enabled) return;
this.enabled = false;
this.abortControllers.forEach(controller => controller.abort());
this.abortControllers.clear();
document.createElement = this.originals.createElement;
Element.prototype.setAttribute = this.originals.setAttribute;
Node.prototype.appendChild = this.originals.appendChild;
window.fetch = this.originals.fetch;
XMLHttpRequest.prototype.open = this.originals.xhrOpen;
document.write = this.originals.documentWrite;
document.writeln = this.originals.documentWriteln;
Element.prototype.insertAdjacentHTML = this.originals.insertAdjacentHTML;
document.removeEventListener('beforescriptexecute', this.handleBeforeScriptExecute);
},
handleInterception(target, url) {
if (this.blockedUrls.has(url)) return;
this.blockedUrls.add(url);
if (target.tagName === 'SCRIPT') {
const abort = new AbortController();
this.abortControllers.set(target, abort);
if (target.src) {
fetch(target.src, { signal: abort.signal })
.catch(() => {});
abort.abort();
}
const success = AdUtils.nuclearRemove(target, 'thirdPartyBlock', {
type: '第三方脚本拦截',
detail: `原子级移除脚本: ${url.slice(0, 80)}`
});
Object.defineProperty(target, 'src', {
get() { return ''; },
set() {},
configurable: false
});
return success;
}
return this.blockElement(target);
},
blockElement(element) {
if (!element?.parentNode) return false;
element.dataset.tpiBlocked = Date.now().toString(36);
element.style.setProperty('display', 'none', 'important');
element.style.setProperty('visibility', 'hidden', 'important');
setTimeout(() => {
try {
if (element.parentNode) {
element.parentNode.removeChild(element);
}
} catch(e) {
console.debug('元素移除失败:', e);
}
}, 100);
return true;
},
isThirdParty(url) {
try {
const currentHost = new URL(location.href).hostname;
const resourceHost = new URL(url, location.href).hostname;
return !(resourceHost.endsWith(`.${currentHost}`) || resourceHost === currentHost);
} catch(e) {
return true;
}
},
wrapSetAttribute(name, value, element = this) {
if (name === 'src' && this.isThirdParty(value)) {
if (element.tagName === 'SCRIPT') {
return this.handleInterception(element, value);
}
return this.blockElement(element);
}
return this.originals.setAttribute.call(element, name, value);
},
wrapAppendChild(node, parent = this) {
if (node.nodeType === 1) {
const src = node.src || node.getAttribute('data-src') || '';
if (this.isThirdParty(src)) {
if (node.tagName === 'SCRIPT') {
this.handleInterception(node, src);
return null;
}
return this.blockElement(node);
}
}
return this.originals.appendChild.call(parent, node);
},
wrapElementCreation(tagName) {
const element = this.originals.createElement.call(document, tagName);
const lowerTag = tagName.toLowerCase();
if (['script', 'iframe', 'img'].includes(lowerTag)) {
return new Proxy(element, {
set: (target, prop, value) => {
if (prop === 'src' && this.isThirdParty(value)) {
if (lowerTag === 'script') {
this.handleInterception(target, value);
} else {
this.blockElement(target);
}
}
target[prop] = value;
return true;
}
});
}
return element;
},
wrapFetch(input, init) {
const url = typeof input === 'string' ? input : input?.url;
if (this.isThirdParty(url)) {
Logs.add('thirdPartyBlock', document.createElement('fetch'), {
type: 'Fetch请求拦截',
detail: `URL: ${url?.slice(0,150) || 'unknown'}`,
context: {
method: init?.method || 'GET',
mode: init?.mode || 'no-cors'
}
});
return Promise.reject(new Error('TPI: 第三方请求被拦截'));
}
return this.originals.fetch(input, init);
},
wrapXHROpen(method, url) {
if (this.isThirdParty(url)) {
Logs.add('thirdPartyBlock', document.createElement('xhr'), {
type: 'XHR请求拦截',
detail: `${method} ${url.slice(0,150)}`,
context: {
async: this.originals.xhrOpen.arguments[2] || true
}
});
this._blocked = true;
return;
}
return this.originals.xhrOpen.call(this, method, url);
},
handleHTMLInsertion(html, insertionType, originalFunction) {
const tempDiv = document.createElement('div');
tempDiv.innerHTML = html;
Array.from(tempDiv.querySelectorAll('script, iframe, img')).forEach(el => {
const src = el.src || el.getAttribute('src') || '';
if (this.isThirdParty(src)) {
Logs.add('thirdPartyBlock', el.cloneNode(), {
type: '动态插入拦截',
detail: `插入方式: ${insertionType}`,
context: {
originalHTML: html.slice(0, 200) + (html.length > 200 ? '...' : '')
}
});
this.handleInterception(el.cloneNode(), src);
}
});
return originalFunction(html);
},
wrapDocumentWrite(content) {
return this.handleHTMLInsertion(content, 'document.write', this.originals.documentWrite);
},
wrapDocumentWriteln(content) {
return this.handleHTMLInsertion(content, 'document.writeln', this.originals.documentWriteln);
},
wrapInsertAdjacentHTML(position, html) {
return this.handleHTMLInsertion(html, 'insertAdjacentHTML',
(html) => this.originals.insertAdjacentHTML.call(this, position, html));
},
handleBeforeScriptExecute(e) {
const script = e.target;
const src = script.src || script.getAttribute('data-src') || '';
if (this.isThirdParty(src)) {
e.preventDefault();
e.stopImmediatePropagation();
requestIdleCallback(() => {
AdUtils.nuclearRemove(script, 'thirdPartyBlock', {
type: '预执行脚本拦截',
detail: `脚本类型: ${script.type || '常规脚本'}`
});
}, { timeout: 50 });
}
},
check(el) {
if (!CONFIG.modules.thirdPartyBlock) return false;
const src = el.src || el.getAttribute('data-src') || '';
if (this.isThirdParty(src)) {
const tagName = el.tagName;
if (tagName === 'SCRIPT') {
return AdUtils.nuclearRemove(el, 'thirdPartyBlock', {
type: '静态脚本拦截',
detail: `源: ${src.slice(0, 80)}`
});
}
return this.blockElement(el);
}
return false;
}
});
ModuleManager.register('frameSystem', {
check(el) {
if (!CONFIG.modules.frameSystem) return false;
if (el.tagName === 'IFRAME') {
let depth = 0, parent = el;
while ((parent = parent.parentElement) && depth <= CONFIG.protectionRules.maxFrameDepth) {
if (parent.tagName === 'IFRAME') depth++;
}
if (depth > CONFIG.protectionRules.maxFrameDepth) {
return AdUtils.safeRemove(el, 'frameSystem', {
type: '深层嵌套框架',
detail: `嵌套层级: ${depth}`
});
}
}
return false;
}
});
ModuleManager.register('dynamicSystem', {
checkedScripts: new WeakSet(),
originalCreateElement: null,
scriptProxies: new WeakMap(),
abortControllers: new WeakMap(),
patternCache: new Map(),
dangerKeywords: ['adservice', 'tracking', 'popunder', 'adsystem'],
throttleQueue: new Set(),
observer: null,
enabled: false,
init() {
this.originalCreateElement = document.createElement.bind(document);
if (CONFIG.modules.dynamicSystem) {
this.enable();
AdUtils.enableDynamicSystem();
} else {
this.disable();
AdUtils.disableDynamicSystem();
}
this.observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1) {
if (node.tagName === 'SCRIPT') {
this.throttleCheck(node);
} else {
this.checkStaticElements(node);
}
}
});
});
});
if (this.enabled) {
this.observer.observe(document.documentElement, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['id', 'class', 'style', 'src']
});
}
this.processQueue();
this.startRecoveryCheck();
},
enable() {
this.enabled = true;
document.createElement = this.wrapCreateElement.bind(this);
if (this.observer) {
this.observer.observe(document.documentElement, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['id', 'class', 'style', 'src']
});
}
AdUtils.enableDynamicSystem();
},
disable() {
this.enabled = false;
document.createElement = this.originalCreateElement;
if (this.observer) {
this.observer.disconnect();
}
AdUtils.disableDynamicSystem();
},
checkObfuscatedAttributes(attrs) {
const SHORT_VAR_PATTERN = /^[a-z\d]{1,3}$/i;
const SIGNATURE_PATTERN = /^(?:[\da-f]{32}|[\w+/=]{40,})$/i;
const ALPHANUM_ALTERNATE = /(?:[a-z]\d|\d[a-z]){5,}/gi;
return attrs.some(attr => {
const isShortVar = SHORT_VAR_PATTERN.test(attr.name);
const isObfuscatedValue = SIGNATURE_PATTERN.test(attr.value) ||
attr.value.match(ALPHANUM_ALTERNATE)?.length > 3;
const isDynamicName = /\[(?:0x[a-f\d]+|\d+)\]/.test(attr.name);
return (isShortVar && isObfuscatedValue) ||
attr.name.length > 20 ||
isDynamicName ||
(attr.value.includes(';') && attr.value.length > 60);
});
},
processQueue() {
requestIdleCallback(() => {
const startTime = performance.now();
const iterator = this.throttleQueue.values();
let entry = iterator.next();
while (!entry.done && performance.now() - startTime < 8) {
const element = entry.value;
if (element.tagName === 'SCRIPT') {
this.checkScript(element);
} else {
this.checkStaticElements(element);
}
this.throttleQueue.delete(entry.value);
entry = iterator.next();
}
if (this.throttleQueue.size > 0) this.processQueue();
}, { timeout: 50 });
},
throttleCheck(element) {
if (this.throttleQueue.has(element)) return;
this.throttleQueue.add(element);
if (this.throttleQueue.size === 1) this.processQueue();
},
checkStaticElements(el) {
if (el.id && (
el.id.length > CONFIG.protectionRules.dynamicIdLength ||
REGEX.dynamicId.test(el.id)
)) {
AdUtils.safeRemove(el, 'dynamicSystem', {
type: '可疑动态ID',
detail: `ID特征: ${el.id.slice(0, 50)}`
});
return true;
}
if (el.tagName === 'A') {
const href = el.getAttribute('href') || '';
if (href.startsWith('javascript:') &&
REGEX.jsAdPattern.test(href)) {
return AdUtils.safeRemove(el, 'dynamicSystem', {
type: '可疑JS链接广告',
detail: `执行代码: ${href.slice(0, 100)}`,
regex: [REGEX.jsAdPattern.source]
});
}
}
return false;
},
wrapCreateElement(tagName) {
const element = this.originalCreateElement.call(document, tagName);
if (this.enabled && tagName.toLowerCase() === 'script') {
return this.createScriptProxy(element);
}
return element;
},
createScriptProxy(element) {
const proxy = new Proxy(element, {
set: (target, prop, value) => {
if (prop === 'src') {
if (this.quickDetect(target)) {
this.blockImmediately(target);
return true;
}
}
target[prop] = value;
return true;
},
get: (target, prop) => {
if (prop === 'src' && target.hasAttribute('data-ad-blocked')) {
return '';
}
return target[prop];
}
});
this.scriptProxies.set(element, proxy);
return proxy;
},
quickDetect(scriptEl) {
if (!this.enabled) return false;
const attrs = Array.from(scriptEl.attributes);
if (this.checkObfuscatedAttributes(attrs)) {
Logs.add('dynamicSystem', scriptEl, {
type: '混淆属性检测',
detail: `属性列表: ${attrs.map(a => a.name).join(', ')}`
});
return true;
}
if (!scriptEl.src && scriptEl.textContent) {
const content = scriptEl.textContent.slice(0, 200);
return this.dangerKeywords.some(k => content.includes(k)) ||
REGEX.obfuscationPatterns.some(p => p.pattern.test(content));
}
return false;
},
checkScript(scriptEl) {
if (!this.enabled || this.checkedScripts.has(scriptEl)) return;
this.checkedScripts.add(scriptEl);
if (this.quickDetect(scriptEl)) {
this.blockImmediately(scriptEl);
return;
}
if (scriptEl.src) {
this.analyzeRemoteScript(scriptEl);
} else {
this.analyzeInlineScript(scriptEl);
}
},
async analyzeRemoteScript(scriptEl) {
const controller = new AbortController();
this.abortControllers.set(scriptEl, controller);
try {
const response = await fetch(scriptEl.src, {
signal: controller.signal,
priority: 'low'
});
const reader = response.body.getReader();
let chunk = '', isSafe = true;
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunk += new TextDecoder().decode(value);
if (this.detectInChunk(chunk)) {
isSafe = false;
reader.cancel();
break;
}
if (chunk.length > 4096) break;
}
if (!isSafe) {
this.blockImmediately(scriptEl);
Logs.add('dynamicSystem', scriptEl, {
type: '流式特征阻断',
detail: `检测到前4KB风险内容`
});
}
} catch (e) {
if (e.name !== 'AbortError') console.warn('脚本分析失败:', e);
} finally {
this.abortControllers.delete(scriptEl);
}
},
detectInChunk(chunk) {
return REGEX.obfuscationPatterns.some(({ pattern }) => pattern.test(chunk));
},
analyzeInlineScript(scriptEl) {
const content = scriptEl.textContent;
const dynamicVarPatterns = [
/(?:var|let|const)\s+([a-z\d]{1,4})\s*=\s*(?:"[\\x0-9a-f]{20,}"|[\d+/*\-()]{8,})/gi,
/\.(?:setAttribute|style)\s*\(\s*["']\w{1,4}["']/gi
];
if (dynamicVarPatterns.some(p => p.test(content))) {
this.blockImmediately(scriptEl);
Logs.add('dynamicSystem', scriptEl, {
type: '动态变量赋值',
detail: `检测到可疑的变量初始化`,
regex: dynamicVarPatterns.map(p => p.source)
});
}
let dangerLevel = 0;
dangerLevel += content.includes('eval(') ? 2 : 0;
dangerLevel += content.includes('Function(') ? 1 : 0;
dangerLevel += this.detectInChunk(content.slice(0, 500)) ? 3 : 0;
if (dangerLevel >= 3) {
this.blockImmediately(scriptEl);
Logs.add('dynamicSystem', scriptEl, {
type: '内联脚本风险',
detail: `危险级别: ${dangerLevel}`
});
}
if (ASTAnalyzer.detectMalicious(content)) {
this.blockImmediately(scriptEl);
Logs.add('dynamicSystem', scriptEl, {
type: 'AST检测阻断',
detail: `可疑代码: ${content.slice(0, 100)}...`
});
}
},
blockImmediately(scriptEl) {
if (!scriptEl.parentNode) return;
scriptEl.setAttribute('data-ad-blocked', 'true');
scriptEl.style.setProperty('display', 'none', 'important');
requestIdleCallback(() => {
try {
scriptEl.parentNode.removeChild(scriptEl);
} catch(e) {
console.warn('移除失败:', e);
}
}, { timeout: 200 });
},
check(el) {
if (!this.enabled) return false;
const attrs = Array.from(el.attributes);
if (this.checkObfuscatedAttributes(attrs)) {
return AdUtils.safeRemove(el, 'dynamicSystem', {
type: '混淆属性检测',
detail: `属性列表: ${attrs.map(a => a.name).join(', ')}`
});
}
if (el.tagName === 'SCRIPT') {
this.throttleCheck(el);
return true;
}
return false;
},
startRecoveryCheck() {
setInterval(() => {
document.querySelectorAll('script:not([data-ad-checked])').forEach(script => {
this.throttleCheck(script);
});
}, 500);
}
});
ModuleManager.register('layoutSystem', {
check(el) {
if (!CONFIG.modules.layoutSystem) return false;
const style = Detector.getCachedStyle(el);
const zIndex = parseInt(style.zIndex, 10);
if (!isNaN(zIndex) && zIndex > CONFIG.protectionRules.zIndexThreshold) {
return AdUtils.safeRemove(el, 'layoutSystem', {
type: '高Z轴元素',
detail: `z-index: ${zIndex}`
});
}
if (style.overflow === 'hidden' && el.scrollHeight > el.clientHeight) {
return AdUtils.safeRemove(el, 'layoutSystem', {
type: '隐藏溢出内容的容器'
});
}
if (style.visibility === 'hidden' || style.display === 'none') {
return AdUtils.safeRemove(el, 'layoutSystem', {
type: '不可见容器'
});
}
if (el.children.length === 0 && el.textContent.trim() === '' && style.width === '0px' && style.height === '0px') {
return AdUtils.safeRemove(el, 'layoutSystem', {
type: '空的不可见容器'
});
}
if (el.children.length > 0 && Array.from(el.children).every(child => child.style.display === 'none' || child.style.visibility === 'hidden')) {
return AdUtils.safeRemove(el, 'layoutSystem', {
type: '子元素的不可见容器'
});
}
return false;
}
});
ModuleManager.register('mergedMediaSystem', {
checkedParents: new WeakSet(),
imageExtensions: /\.(gif|webp|png|jpe?g|svg)(\?.*)?$/i,
jsExtensionPattern: /\.js(\?|$)/i,
visibilityThreshold: 0.1,
minValidSize: 32,
aspectRatioConfig: {
commonAdRatios: [
{ w: 728, h: 90 },
{ w: 300, h: 250 },
{ w: 160, h: 600 },
{ w: 88, h: 31 },
{ w: 1, h: 1 }
],
dynamicThreshold: {
mobile: { min: 0.3, max: 3.5 },
desktop: { min: 0.2, max: 5 },
ultrawide: { min: 0.1, max: 8 }
},
scaleTolerance: 5,
containerMatchLevel: 3
},
check(el) {
if (!CONFIG.modules.mergedMediaSystem) return false;
const src = el.src || el.getAttribute('src') || '';
if (this.isJsExtensionAd(el, src)) {
return AdUtils.safeRemove(el, 'mergedMediaSystem', {
type: 'JS扩展名图片广告',
detail: `异常图片源: ${src.slice(0, 80)}`
});
}
const isImageElement = el.tagName === 'IMG' ||
el.tagName === 'IMAGE' ||
(el.tagName === 'DIV' && this.hasImageBackground(el));
const hasImageSource = src || el.getAttribute('data-src') ||
el.getAttribute('data-original');
if (isImageElement || hasImageSource) {
return this.checkImageAdvertisement(el);
}
if (this.isSuspiciousContainer(el)) {
return this.handleSuspiciousContainer(el);
}
return false;
},
isJsExtensionAd(el, src) {
return this.jsExtensionPattern.test(src) &&
(el.tagName === 'IMG' || el.tagName === 'IMAGE');
},
checkImageAdvertisement(el) {
const style = Detector.getCachedStyle(el);
const rect = el.getBoundingClientRect();
const viewportArea = window.innerWidth * window.innerHeight;
if (!this.isVisibleElement(el, style)) return false;
const dynamicSrc = el.getAttribute('data-src') || '';
if (this.jsExtensionPattern.test(dynamicSrc)) {
return AdUtils.safeRemove(el, 'mergedMediaSystem', {
type: '动态JS扩展名图片',
detail: `data-src: ${dynamicSrc.slice(0, 80)}`
});
}
if (rect.width * rect.height < this.minValidSize * this.minValidSize) {
return AdUtils.safeRemove(el, 'mergedMediaSystem', {
type: '微型隐藏图片',
detail: `尺寸: ${rect.width}x${rect.height}px`
});
}
const naturalWidth = el.naturalWidth || rect.width;
const naturalHeight = el.naturalHeight || rect.height;
const aspectRatio = naturalWidth / naturalHeight;
if (this.isCommonAdSize(naturalWidth, naturalHeight)) {
return this.handleAdDetection(el, '标准广告尺寸',
`${naturalWidth}x${naturalHeight}`);
}
const ratioThreshold = this.getDynamicThreshold();
if (aspectRatio < ratioThreshold.min || aspectRatio > ratioThreshold.max) {
return this.handleAdDetection(el, '异常宽高比',
`比例:${aspectRatio.toFixed(1)} 阈值:[${ratioThreshold.min},${ratioThreshold.max}]`);
}
if (this.hasContainerMismatch(el, rect.width, rect.height)) {
return this.handleAdDetection(el, '容器尺寸异常',
`显示尺寸:${rect.width}x${rect.height}`);
}
if (this.isPixelDisguised(el, naturalWidth, naturalHeight, rect.width, rect.height)) {
return this.handleAdDetection(el, '像素缩放伪装',
`原始:${naturalWidth}x${naturalHeight} 显示:${rect.width}x${rect.height}`);
}
if (this.isEdgePosition(rect, viewportArea)) {
return AdUtils.safeRemove(el, 'mergedMediaSystem', {
type: '边缘定位图片',
detail: `位置: ${rect.top}px`
});
}
if (this.isDynamicLoaded(el)) {
return AdUtils.safeRemove(el, 'mergedMediaSystem', {
type: '动态加载图片',
detail: `来源: ${el.getAttribute('data-src')?.slice(0,50) || '未知'}`
});
}
return false;
},
isDynamicLoaded(el) {
const src = el.getAttribute('data-src') || el.getAttribute('data-original') || '';
return (
!el.src &&
src &&
(this.imageExtensions.test(src) || this.jsExtensionPattern.test(src))
);
},
isCommonAdSize(width, height) {
return this.aspectRatioConfig.commonAdRatios.some(ratio => {
const targetRatio = ratio.w / ratio.h;
return Math.abs(width/height - targetRatio) < 0.1 &&
Math.abs(width - ratio.w) < 10 &&
Math.abs(height - ratio.h) < 10;
});
},
getDynamicThreshold() {
const viewportWidth = window.innerWidth;
if (viewportWidth > 1920) return this.aspectRatioConfig.dynamicThreshold.ultrawide;
return window.matchMedia('(max-width: 768px)').matches ?
this.aspectRatioConfig.dynamicThreshold.mobile :
this.aspectRatioConfig.dynamicThreshold.desktop;
},
hasContainerMismatch(el, displayW, displayH) {
let parent = el.parentElement;
for (let i = 0; i < this.aspectRatioConfig.containerMatchLevel; i++) {
if (!parent) break;
const parentStyle = getComputedStyle(parent);
const parentW = parseFloat(parentStyle.width);
const parentH = parseFloat(parentStyle.height);
if (parentW > 0 && parentH > 0) {
const parentRatio = parentW / parentH;
const elementRatio = displayW / displayH;
if (Math.abs(parentRatio - elementRatio) > 0.5) {
return true;
}
}
parent = parent.parentElement;
}
return false;
},
isPixelDisguised(el, naturalW, naturalH, displayW, displayH) {
const scaleX = displayW / naturalW;
const scaleY = displayH / naturalH;
return (naturalW === 1 && naturalH === 1 && displayW > 50) ||
(Math.abs(scaleX - scaleY) > this.aspectRatioConfig.scaleTolerance) ||
(Math.abs(scaleX) > 50 && naturalW < 10);
},
handleAdDetection(el, type, detail) {
const isImportant = type.includes('像素') ? 2 : 1;
return AdUtils.safeRemove(el, 'mergedMediaSystem', {
type: `图片广告检测[${type}]`,
detail: detail,
priority: isImportant
});
},
isVisibleElement(el, style) {
const rect = el.getBoundingClientRect();
return (
style.opacity > this.visibilityThreshold &&
style.visibility !== 'hidden' &&
style.display !== 'none' &&
rect.width > 0 &&
rect.height > 0 &&
!this.isCoveredByParent(el)
);
},
isEdgePosition(rect, viewportArea) {
const elementArea = rect.width * rect.height;
return (
(rect.top < 50 && elementArea > viewportArea * 0.3) ||
(rect.bottom > window.innerHeight - 100 && elementArea > viewportArea * 0.2) ||
(Math.abs(rect.left) > window.innerWidth * 0.8) ||
(Math.abs(rect.right) < window.innerWidth * 0.2)
);
},
hasImageBackground(el) {
const style = Detector.getCachedStyle(el);
return (
style.backgroundImage !== 'none' &&
/url\(['"]?.*\.(gif|webp|png|jpe?g)/i.test(style.backgroundImage)
);
},
isSuspiciousContainer(el) {
const style = Detector.getCachedStyle(el);
return (
['fixed', 'sticky'].includes(style.position) ||
style.zIndex > 999 ||
this.hasScrollDisable(style) ||
this.containsTrackingPixel(el)
);
},
hasScrollDisable(style) {
return (
style.overflow === 'hidden' &&
(style.touchAction === 'none' || style.overscrollBehavior === 'contain')
);
},
containsTrackingPixel(el) {
return el.querySelectorAll('img[width="1" i], img[height="1" i]').length > 0;
},
handleSuspiciousContainer(el) {
const style = Detector.getCachedStyle(el);
const rect = el.getBoundingClientRect();
if (
['fixed', 'sticky'].includes(style.position) &&
(rect.top < 50 || rect.bottom > window.innerHeight - 50)
) {
return AdUtils.safeRemove(el, 'mergedMediaSystem', {
type: '固定位置容器',
detail: `定位方式: ${style.position} 位置: ${rect.top}px`
});
}
if (
rect.width >= window.innerWidth * 0.9 &&
rect.height >= window.innerHeight * 0.7 &&
style.zIndex > 1000
) {
return AdUtils.safeRemove(el, 'mergedMediaSystem', {
type: '全屏覆盖容器',
detail: `尺寸: ${rect.width}x${rect.height}px`
});
}
return false;
},
isCoveredByParent(el) {
let parent = el.parentElement;
while (parent) {
const pStyle = Detector.getCachedStyle(parent);
if (
pStyle.display === 'none' ||
pStyle.visibility === 'hidden' ||
pStyle.opacity < 0.1
) {
return true;
}
parent = parent.parentElement;
}
return false;
}
});
ModuleManager.register('specialUA', {
navigatorProxy: null,
originalNavigator: Object.create(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: 'Mozilla/5.0 (Linux; Android 12; LIO-AN00 Build/HUAWEILIO-AN00) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.196 Mobile Safari/537.36',
init() {
this.createProxy();
if (CONFIG.modules.specialUA) this.enable();
},
createProxy() {
this.navigatorProxy = new Proxy(navigator, {
get: (target, prop) => {
if (!CONFIG.modules.specialUA)
return Reflect.get(target, prop);
switch(prop) {
case 'userAgent':
return this.fakeUA;
case 'platform':
return this.fakePlatform;
case 'appVersion':
return this.fakeAppVersion;
case 'vendor':
return 'Google Inc.';
default:
return Reflect.get(target, prop);
}
}
});
Object.defineProperty(window, 'navigator', {
value: this.navigatorProxy,
configurable: true,
writable: false
});
},
enable() {
CONFIG.modules.specialUA = true;
},
disable() {
CONFIG.modules.specialUA = false;
Object.defineProperty(window, 'navigator', {
value: this.originalNavigator,
configurable: true,
writable: false
});
},
check() {
return false;
}
});
ModuleManager.register('performanceOptimizer', {
mutationQueue: new Set(),
pendingFrames: 0,
lastProcessTime: 0,
init() {
this.optimizeMutationObserver();
this.setupPerformanceMonitor();
},
optimizeMutationObserver() {
const originalObserve = MutationObserver.prototype.observe;
MutationObserver.prototype.observe = function(target, options) {
const optimizedOptions = {
childList: options.childList,
attributes: options.attributes,
subtree: options.subtree && !options.subtreeOptimized,
attributeFilter: ['id', 'class', 'data-ad', 'src']
};
originalObserve.call(this, target, optimizedOptions);
};
},
setupPerformanceMonitor() {
const checkLongTasks = () => {
if (performance.now() - this.lastProcessTime > CONFIG.performance.longTaskThreshold) {
perf.longTaskCount++;
if (perf.longTaskCount >= CONFIG.performance.degradeThreshold) {
this.activateDegradedMode();
}
}
requestAnimationFrame(checkLongTasks);
};
checkLongTasks();
},
activateDegradedMode() {
perf.degraded = true;
observer.disconnect();
document.removeEventListener('DOMNodeInserted', captureInsertion);
console.warn('进入性能降级模式,停止动态监测');
},
scheduleFrame(callback) {
if (this.pendingFrames < 2) {
this.pendingFrames++;
requestAnimationFrame(() => {
callback();
this.pendingFrames--;
});
} else {
setTimeout(callback, 16);
}
}
});
const captureInsertion = (function() {
const handler = function(e) {
if (perf.degraded) return;
const element = e.target;
if (!perf.processed.has(element)) {
perf.adElements.add(element);
scheduleProcess(() => Processor.processElements());
}
};
document.addEventListener('DOMNodeInserted', handler, true);
return handler;
})();
(function ensureDOMInjection() {
const originalCreateElement = document.createElement;
document.createElement = function(tagName) {
const element = originalCreateElement.call(document, tagName);
if (tagName.toLowerCase() === 'meta' && !document.head) {
document.documentElement.insertBefore(element, document.documentElement.firstChild);
}
return element;
};
const originalAppendChild = Node.prototype.appendChild;
Node.prototype.appendChild = function(node) {
if (node.nodeType === 1 && node.tagName === 'STYLE' && !document.head) {
return document.documentElement.insertBefore(node, document.documentElement.firstChild);
}
return originalAppendChild.call(this, node);
};
})();
window.addEventListener('load', () => {
if (!perf.degraded) {
Processor.collectAds();
ModuleManager.modules.performanceOptimizer.scheduleFrame(() => {
Processor.processElements();
});
}
});
init();
})();