广告终结者(实验性版本)
// ==UserScript==
// @name 广告终结者(实验性版本)
// @namespace http://tampermonkey.net/
// @version 3.0.1
// @description 实验性版本,引入csp,可能存在bug
// @author DeepSeek
// @match *://*/*
// @exclude *://*.bing.com/*
// @exclude *://*.iqiyi.com/*
// @exclude *://*.qq.com/*
// @exclude *://*.v.qq.com/*
// @exclude *://*.sohu.com/*
// @exclude *://*.mgtv.com/*
// @exclude *://*.ifeng.com/*
// @exclude *://*.pptv.com/*
// @exclude *://*.sina.com.cn/*
// @exclude *://*.56.com/*
// @exclude *://*.cntv.cn/*
// @exclude *://*.tudou.com/*
// @exclude *://*.baofeng.com/*
// @exclude *://*.le.com/*
// @exclude *://*.pps.tv/*
// @exclude *://*.fun.tv/*
// @exclude *://*.baidu.com/*
// @exclude *://*.ku6.com/*
// @exclude *://*.tvsou.com/*
// @exclude *://*.kankan.com/*
// @exclude *://*.douyu.com/*
// @exclude *://*.weibo.com/*
// @exclude *://*.people.com.cn/*
// @exclude *://*.cctv.com/*
// @exclude *://*.gdtv.com.cn/*
// @exclude *://*.ahtv.cn/*
// @exclude *://*.tvb.com/*
// @exclude *://*.tvmao.com/*
// @exclude *://*.douban.com/*
// @exclude *://*.163.com/*
// @exclude *://*.bilibili.com/*
// @exclude *://*.gov.cn/*
// @exclude *://*.thepaper.cn/*
// @exclude *://*.xinhuanet.com/*
// @exclude *://*.china.com/*
// @exclude *://*.jianshu.com/*
// @exclude *://*.amazon.cn/*
// @exclude *://*.cnblogs.com/*
// @exclude *://*.cnstock.com/*
// @exclude *://*.baike.com/*
// @exclude *://*.guokr.com/*
// @exclude *://*.360doc.com/*
// @exclude *://*.qiushibaike.com/*
// @exclude *://*.zol.com.cn/*
// @exclude *://*.pconline.com.cn/*
// @exclude *://*.pcpop.com/*
// @exclude *://*.it168.com/*
// @exclude *://*.gfan.com/*
// @exclude *://*.feng.com/*
// @exclude *://*.xiaomi.cn/*
// @exclude *://*.10086.cn/*
// @exclude *://*.10010.com/*
// @license MIT
// @grant GM_registerMenuCommand
// @grant GM_notification
// @grant GM_getValue
// @grant GM_setValue
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
const HOSTNAME = location.hostname.replace(/\./g, '\\.');
const REGEX = {
dynamicId: /^(?:[0-9a-f]{16,}|[\dA-F-]{20,})$/i,
adAttributes: /(?:ad(?:s|vert|vertisement|box|frame|container|wrap|content|label)?|推广|广告|gg|sponsor|推荐|guanggao|syad|bfad|弹窗|悬浮|葡京|banner|pop(?:up|under)|track(?:ing)?)[_-]?/i,
thirdParty: new RegExp(`^(https?:\\/\\/(.*\\.)?${HOSTNAME}(\\/|$)|data:|about:blank)`, 'i'),
textAdKeywords: /限时(?:优惠|免费)|立即(?:下载|注册|咨询)|微信(?:号|客服)?[::]?|vx[::]\w+|telegram[::]\w+|免广告|偷拍|黑料|点击下载/,
obfuscatedAds: {
adUrl: /(?:\/[a-z0-9]{12,}\/|(?:adservice|tracking|promo)\.[a-z]{3,10}\.(?:com|net))(?:\/|$)(?:ads?|promo|banner|track|stat|click|log)/i,
hashedFile: /[a-f0-9]{32,}\.(?:js|css|html)|(?:[a-z0-9]{20,}|[A-Z0-9]{20,})\.(?:js|css|html)/i,
adFunction: /(?:get|fetch|load|show|display|render|set(?:Timeout|Interval))\(.*?(?:Ad|Banner|Popup)/i,
dynamicCode: /(?:eval|new\s+Function|Function)\(.*?(?:decodeURIComponent|atob|fromCharCode)/i,
iframeWrite: /(?:\.createElement\(['"]iframe['"]\)|document\.write\([\s\S]*?<(script|iframe|img).*?(src|href)=['"]?(?:[^'">]*?(?:ad|banner|popup))['"]?)/i,
base64Data: /(?:data:.*?(?:ad|banner)|(?:[A-Za-z0-9+/]{4}){10,}(?:ad|banner)|data:image\/(?:png|jpeg|gif);base64,)/i,
inlineScript: /<script[^>]*>[\s\S]*?(?:ad|banner|popup|[a-zA-Z0-9]{8})[\s\S]*?<\/script>/i,
encodedString: /(?:base64|hex)\([^)]+\)/i,
unicodeCharEncoded: /\\u[0-9a-f]{4}(?:\\u[0-9a-f]{4}){3,}/gi
}
};
const CONFIG = {
modules: {
main: false,
dynamicSystem: false,
layoutSystem: false,
frameSystem: false,
mediaSystem: false,
textSystem: false,
thirdPartyBlock: false,
obfuscatedAdSystem: false,
floating: false
},
protectionRules: {
dynamicIdLength: 18,
zIndexThreshold: 100,
maxFrameDepth: 4,
textAdKeywords: ['限时优惠','立即下载','微信','vx:','telegram','偷拍','黑料','扫码关注','点击下载']
},
csp: {
enabled: false,
rules: [
{ id: 1, name: '脚本限制', rule: "script-src 'self'", enabled: true },
{ id: 2, name: '样式限制', rule: "style-src 'self'", enabled: false },
{ id: 3, name: '图片限制', rule: "img-src 'self'", enabled: false },
{ id: 4, name: '字体限制', rule: "font-src 'self'", enabled: false },
{ id: 5, name: '连接限制', rule: "connect-src 'self'", enabled: false },
{ id: 6, name: '框架限制', rule: "frame-src 'none'", enabled: false }
]
},
mobileOptimizations: {
lazyLoadImages: true,
removeImagePlaceholders: true
},
moduleNames: {
dynamicSystem: '动态检测系统',
layoutSystem: '布局检测系统',
frameSystem: '框架过滤系统',
mediaSystem: '媒体检测系统',
textSystem: '文本广告检测',
thirdPartyBlock: '第三方拦截',
obfuscatedAdSystem: '混淆广告检测',
floating: '浮动广告检测'
},
modulePriority: [
'thirdPartyBlock',
'frameSystem',
'obfuscatedAdSystem',
'dynamicSystem',
'mediaSystem',
'layoutSystem',
'textSystem',
'floating'
]
};
const Logs = {
logs: [],
maxLogs: 30,
add(moduleKey, element, reason) {
const moduleName = CONFIG.moduleNames[moduleKey] || '未知模块';
this.logs.push({
module: moduleName,
element: `${element.tagName}#${element.id || ''}.${element.className || ''}`,
content: this.getContent(element),
reason: reason || {},
timestamp: new Date().toISOString()
});
if (this.logs.length > this.maxLogs) this.logs.shift();
},
getContent(element) {
return element.outerHTML.slice(0, 100) + (element.outerHTML.length > 100 ? '...' : '');
},
clear() {
this.logs = [];
GM_notification('日志已清空');
}
};
const perf = {
pendingTasks: [],
isProcessing: false,
processed: new WeakSet(),
adElements: new Set(),
styleCache: new Map(),
lastStyleCacheClear: Date.now()
};
const AdUtils = {
safeRemove(element, moduleKey, reason) {
if (!element?.parentNode) return false;
try {
Logs.add(moduleKey, element, reason);
element.style.display = 'none';
element.style.visibility = 'hidden';
element.style.opacity = '0';
element.setAttribute('data-ad-removed', 'true');
return true;
} catch(e) {
console.warn('元素隐藏失败:', e);
return false;
}
}
};
const StyleManager = {
styleElement: null,
inject() {
if (this.styleElement) return;
this.styleElement = document.createElement('style');
this.styleElement.id = 'ad-blocker-styles';
let cssRules = [];
cssRules.push(`
[data-ad-removed] {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
position: absolute !important;
z-index: -9999 !important;
width: 0 !important;
height: 0 !important;
}
iframe[src*="ad"], .ad-container {
display: none !important;
height: 0 !important;
width: 0 !important;
opacity: 0 !important;
}
`);
this.styleElement.textContent = cssRules.join('\n');
document.head.appendChild(this.styleElement);
},
remove() {
if (this.styleElement?.parentNode) {
this.styleElement.parentNode.removeChild(this.styleElement);
this.styleElement = null;
}
},
toggle(enable) {
enable ? this.inject() : this.remove();
}
};
const CSPManager = {
currentPolicy: null,
generatePolicy() {
return CONFIG.csp.rules
.filter(rule => rule.enabled)
.map(rule => rule.rule)
.join('; ');
},
inject() {
this.remove();
if (!CONFIG.csp.enabled) return;
const policy = this.generatePolicy();
this.currentPolicy = policy;
const meta = document.createElement('meta');
meta.httpEquiv = "Content-Security-Policy";
meta.content = policy;
document.head.appendChild(meta);
},
remove() {
const meta = document.querySelector('meta[http-equiv="Content-Security-Policy"]');
if (meta) meta.remove();
},
toggleRule(ruleId, enable) {
const rule = CONFIG.csp.rules.find(r => r.id === ruleId);
if (rule) rule.enabled = enable;
}
};
const Detector = {
checkModule(moduleKey, element) {
if (!CONFIG.modules.main || !CONFIG.modules[moduleKey]) return false;
return this[`check${moduleKey.charAt(0).toUpperCase() + moduleKey.slice(1)}`](element);
},
checkThirdPartyBlock(el) {
if (!CONFIG.modules.thirdPartyBlock) return false;
if (['SCRIPT', 'IFRAME'].includes(el.tagName)) {
const src = el.src || el.getAttribute('data-src') || '';
if (!REGEX.thirdParty.test(src)) {
return AdUtils.safeRemove(el, 'thirdPartyBlock', {
type: '第三方资源拦截',
detail: `源: ${src.slice(0, 80)}`
});
}
}
return false;
},
checkFrameSystem(el) {
if (!CONFIG.modules.frameSystem) return false;
if (el.tagName === 'IFRAME') {
let depth = 0, parent = el;
while ((parent = parent.parentElement) && depth <= CONFIG.protectionRules.maxFrameDepth) {
if (parent.tagName === 'IFRAME') depth++;
}
if (depth > CONFIG.protectionRules.maxFrameDepth) {
return AdUtils.safeRemove(el, 'frameSystem', {
type: '深层嵌套框架',
detail: `嵌套层级: ${depth}`
});
}
}
return false;
},
checkObfuscatedAdSystem(el) {
if (!CONFIG.modules.obfuscatedAdSystem) return false;
const scripts = el.getElementsByTagName('script');
for (let script of scripts) {
const src = script.src || script.innerHTML;
if (Object.values(REGEX.obfuscatedAds).some(r => r.test(src))) {
return AdUtils.safeRemove(script, 'obfuscatedAdSystem', {
type: '混淆广告检测',
detail: `源: ${src.slice(0, 80)}`
});
}
}
return false;
},
checkDynamicSystem(el) {
if (!CONFIG.modules.dynamicSystem) return false;
const attrs = el.getAttributeNames().map(name =>
`${name}=${el.getAttribute(name)}`
).join(' ');
if (REGEX.adAttributes.test(attrs)) {
return AdUtils.safeRemove(el, 'dynamicSystem', {
type: '广告属性检测',
detail: `属性: ${attrs.slice(0, 100)}`
});
}
if (el.id && (
el.id.length > CONFIG.protectionRules.dynamicIdLength ||
REGEX.dynamicId.test(el.id)
)) {
return AdUtils.safeRemove(el, 'dynamicSystem', {
type: '动态ID检测',
detail: `异常ID: ${el.id}`
});
}
return false;
},
checkMediaSystem(el) {
if (!CONFIG.modules.mediaSystem) return false;
if (el.tagName === 'IMG' && (el.src.includes('ad') || el.src.match(/\.(gif|webp)(\?|$)/))) {
return AdUtils.safeRemove(el, 'mediaSystem', {
type: '图片广告',
detail: `图片源: ${el.src.slice(0, 50)}`
});
}
const style = this.getCachedStyle(el);
const rect = el.getBoundingClientRect();
if (['fixed', 'sticky'].includes(style.position)) {
const isTopBanner = rect.top < 50;
const isBottomBanner = rect.bottom > window.innerHeight - 50;
if (isTopBanner || isBottomBanner) {
return AdUtils.safeRemove(el, 'mediaSystem', {
type: '浮动广告',
detail: `位置: ${rect.top}px`
});
}
}
return false;
},
checkLayoutSystem(el) {
if (!CONFIG.modules.layoutSystem) return false;
const style = this.getCachedStyle(el);
const zIndex = parseInt(style.zIndex);
if (zIndex > CONFIG.protectionRules.zIndexThreshold) {
return AdUtils.safeRemove(el, 'layoutSystem', {
type: '高堆叠元素',
detail: `z-index=${zIndex}`
});
}
const rect = el.getBoundingClientRect();
const isVisible = rect.width > 0 && rect.height > 0;
if (!isVisible && el.children.length > 0) {
return AdUtils.safeRemove(el, 'layoutSystem', {
type: '隐藏容器元素',
detail: '包含子元素的不可见容器'
});
}
return false;
},
checkTextSystem(el) {
if (!CONFIG.modules.textSystem) return false;
const text = el.textContent?.toLowerCase() || '';
if (REGEX.textAdKeywords.test(text)) {
return AdUtils.safeRemove(el, 'textSystem', {
type: '文本广告',
detail: `关键词: ${text.slice(0, 80)}`
});
}
return false;
},
checkFloating(el) {
if (!CONFIG.modules.floating) return false;
const style = this.getCachedStyle(el);
if (['fixed', 'sticky'].includes(style.position)) {
return AdUtils.safeRemove(el, 'floating', {
type: '浮动元素',
detail: `定位方式: ${style.position}`
});
}
return false;
},
getCachedStyle(el) {
if (Date.now() - perf.lastStyleCacheClear > 600000) {
perf.styleCache.clear();
perf.lastStyleCacheClear = Date.now();
}
const key = el.nodeName + Array.from(el.classList).join('');
if (!perf.styleCache.has(key)) {
perf.styleCache.set(key, getComputedStyle(el));
}
return perf.styleCache.get(key);
}
};
const Processor = {
collectAds() {
const elements = [...document.getElementsByTagName('*')];
perf.adElements.clear();
elements.forEach(element => {
if (perf.processed.has(element)) return;
for (const moduleKey of CONFIG.modulePriority) {
if (Detector.checkModule(moduleKey, element)) {
perf.adElements.add(element);
perf.processed.add(element);
break;
}
}
});
}
};
const observer = new MutationObserver(mutations => {
if (!CONFIG.modules.main) return;
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.nodeType === 1 && !perf.processed.has(node)) {
perf.pendingTasks.push(node);
}
}
}
scheduleProcess();
});
function processElements() {
const start = performance.now();
let processed = 0;
while (perf.pendingTasks.length && processed < 40) {
const element = perf.pendingTasks.shift();
if (!perf.processed.has(element)) {
Processor.collectAds();
processed++;
}
if (performance.now() - start > 10) break;
}
if (perf.pendingTasks.length) {
scheduleProcess();
} else {
perf.isProcessing = false;
}
}
function scheduleProcess() {
if (!perf.isProcessing && CONFIG.modules.main) {
perf.isProcessing = true;
requestIdleCallback(processElements, { timeout: 2500 });
}
}
const UIController = {
init() {
this.loadConfig();
this.registerMainSwitch();
this.registerModuleSwitches();
this.registerLogCommands();
this.registerCSPCommands();
this.registerResetCommand();
StyleManager.toggle(CONFIG.modules.main);
if (CONFIG.modules.main) CSPManager.inject();
},
registerMainSwitch() {
GM_registerMenuCommand(
`🔘 主开关 [${CONFIG.modules.main ? '✅' : '❌'}]`,
() => this.toggleMain()
);
},
registerModuleSwitches() {
Object.entries(CONFIG.moduleNames).forEach(([key, name]) => {
GM_registerMenuCommand(
`${name} [${CONFIG.modules[key] ? '✅' : '❌'}]`,
() => this.toggleModule(key, name)
);
});
},
registerLogCommands() {
GM_registerMenuCommand('📜 查看拦截日志', () => this.showLogs());
GM_registerMenuCommand('🧹 清空日志', () => Logs.clear());
},
registerCSPCommands() {
GM_registerMenuCommand('🛡️ CSP策略管理', () => this.manageCSP());
},
registerResetCommand() {
GM_registerMenuCommand('🔄 重置设置', () => this.resetSettings());
},
toggleMain() {
const newState = !CONFIG.modules.main;
Object.keys(CONFIG.modules).forEach(key => {
if (key !== 'main') CONFIG.modules[key] = newState;
});
CONFIG.modules.main = newState;
this.saveConfig();
StyleManager.toggle(newState);
newState ? CSPManager.inject() : CSPManager.remove();
GM_notification(`主开关已${newState ? '启用' : '禁用'}`, `所有模块${newState ? '✅ 已激活' : '❌ 已停用'}`);
location.reload();
},
toggleModule(key, name) {
CONFIG.modules[key] = !CONFIG.modules[key];
CONFIG.modules.main = Object.values(CONFIG.modules)
.slice(1).some(v => v);
this.saveConfig();
StyleManager.toggle(CONFIG.modules.main);
GM_notification(`${name} ${CONFIG.modules[key] ? '✅ 已启用' : '❌ 已禁用'}`);
location.reload();
},
showLogs() {
const logText = Logs.logs.map(log =>
`[${log.timestamp}] ${log.module}\n元素: ${log.element}\n内容: ${log.content}`
).join('\n\n');
alert(`广告拦截日志(最近${Logs.maxLogs}条):\n\n${logText || '暂无日志'}`);
},
manageCSP() {
const rulesDisplay = CONFIG.csp.rules
.map(r => `${r.id}. ${r.name} (${r.rule}) (${r.enabled ? '✅' : '❌'})`)
.join('\n');
let input = prompt(
`当前CSP策略状态: ${CONFIG.csp.enabled ? '✅ 已启用' : '❌ 已禁用'}\n\n` +
"当前策略规则:\n" + rulesDisplay + "\n\n" +
"输入选项:\n1. 开启策略 (输入 'enable')\n2. 关闭策略 (输入 'disable')\n" +
"修改规则示例:\n输入 '1on' 启用规则1\n输入 '23off' 禁用规则2和3\n\n" +
"请输入你的选择:",
""
);
if (input === null) return;
input = input.trim().toLowerCase();
switch(input) {
case 'enable':
CONFIG.csp.enabled = true;
this.saveConfig();
CSPManager.inject();
alert("CSP策略已启用");
location.reload();
break;
case 'disable':
CONFIG.csp.enabled = false;
this.saveConfig();
CSPManager.remove();
alert("CSP策略已禁用");
location.reload();
break;
default:
if (/^\d+(?:on|off)$/i.test(input)) {
this.modifyCSPRules(input);
} else {
alert("无效输入");
}
}
},
modifyCSPRules(actionInput) {
const matches = actionInput.match(/^(\d+)(on|off)$/i);
if (matches) {
const ruleIds = matches[1].split('').map(Number);
const enable = matches[2].toLowerCase() === 'on';
let updatedRules = [];
ruleIds.forEach(id => {
const rule = CONFIG.csp.rules.find(r => r.id === id);
if (rule) {
rule.enabled = enable;
updatedRules.push(id);
}
});
this.saveConfig();
CSPManager.inject();
alert(`规则 ${updatedRules.join(',')} 已更新为 ${enable ? '启用' : '禁用'}`);
location.reload();
} else {
alert("输入格式错误,请按示例输入如'1on'或'123off'");
}
},
resetSettings() {
if(confirm("确定要重置所有设置吗?")) {
Object.assign(CONFIG, {
modules: {
main: false,
dynamicSystem: false,
layoutSystem: false,
frameSystem: false,
mediaSystem: false,
textSystem: false,
thirdPartyBlock: false,
obfuscatedAdSystem: false,
floating: false
},
csp: {
enabled: false,
rules: [
{ id: 1, name: '脚本限制', rule: "script-src 'self'", enabled: true },
{ id: 2, name: '样式限制', rule: "style-src 'self'", enabled: true },
{ id: 3, name: '图片限制', rule: "img-src 'self'", enabled: true },
{ id: 4, name: '字体限制', rule: "font-src 'self'", enabled: false },
{ id: 5, name: '连接限制', rule: "connect-src 'self'", enabled: false },
{ id: 6, name: '框架限制', rule: "frame-src 'none'", enabled: false }
]
}
});
this.saveConfig();
CSPManager.remove();
alert("设置已重置为默认值");
location.reload();
}
},
saveConfig() {
GM_setValue(`adblockConfig_${location.hostname}`, JSON.stringify({
modules: CONFIG.modules,
csp: CONFIG.csp
}));
},
loadConfig() {
try {
const savedConfig = JSON.parse(GM_getValue(`adblockConfig_${location.hostname}`, '{}'));
if (savedConfig.modules) {
Object.keys(CONFIG.modules).forEach(key => {
if (savedConfig.modules.hasOwnProperty(key)) {
CONFIG.modules[key] = savedConfig.modules[key];
}
});
}
if (savedConfig.csp) {
Object.assign(CONFIG.csp, savedConfig.csp);
}
} catch(e) {
console.error('配置加载失败:', e);
}
}
};
(function initSystem() {
UIController.init();
observer.observe(document.documentElement, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['id', 'class', 'style', 'src', 'href']
});
if (CONFIG.mobileOptimizations.lazyLoadImages) {
document.addEventListener('scroll', () => {
[...document.getElementsByTagName('img')].forEach(img => {
if (img.getBoundingClientRect().top < window.innerHeight + 500) {
img.src = img.dataset.src || img.src;
}
});
}, { passive: true });
}
window.addEventListener('beforeunload', () => {
UIController.saveConfig();
});
})();
})();