// ==UserScript==
// @name 广告终结者 v1.1
// @namespace http://tampermonkey.net/
// @version 1.1
// @description [完美整合] 动态检测、框架过滤、堆叠拦截、第三方拦截四大系统
// @author TMHhz
// @match *://*/*
// @license GPLv3
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_notification
// ==/UserScript==
(function() {
'use strict';
// 核心配置
const CONFIG = {
maxLogs: 50,
adKeywords: ['ad', 'ads', 'advert', 'banner', 'popup', '推广', '广告', 'gg', 'advertisement', 'sponsor', '推荐', 'adv', 'guanggao', 'syad', 'bfad'],
checkAttributes: ['id', 'class', 'src', 'href', 'data-ad', 'name'],
protectionRules: {
minWidth: 50,
minHeight: 20,
iframeSizeThreshold: 2,
zIndexThreshold: 50,
sizeThresholdRatio: 0.15,
similarityThreshold: 0.8,
dynamicIdLength: 10,
dynamicIdDigits: 5
},
whitelist: {
scriptNamespaces: ['pswMgrDialog', 'userscript-'],
protectedAttributes: [
{name: 'id', values: ['pswMgrDialog', 'userscript-quickFill']},
{name: 'class', values: ['userscript-quickFill']}
],
selectors: ['#pswMgrDialog', '.userscript-quickFill', '.userscript-pswmgrDlg'],
thirdparty: []
},
defaultSettings: {
// 动态检测系统
dynamicIdDetection: true,
attributeSimilarity: true,
sizeAnomaly: true,
adAttributeDetection: true,
// 框架过滤系统
iframeAttributeCheck: true,
parentContainerCheck: true,
// 堆叠拦截系统
highZIndexDetection: true,
fixedPositionCheck: true,
overlayDetection: true,
// 新增第三方拦截
thirdpartyCheck: true
}
};
// ======================= 工具类 =======================
class AdUtils {
static safeRemove(node, module, reason) {
if (!node || !node.parentNode || this.isWhitelisted(node)) {
console.debug('[拦截跳过] 白名单元素:', node);
return false;
}
try {
Logger.logRemoval({
module,
element: {
tag: node.tagName,
id: node.id,
class: node.className,
html: node.outerHTML.slice(0, 500)
},
reason
});
node.parentNode.removeChild(node);
this.cleanAncestors(node.parentNode);
console.log('[成功移除]', module, node);
return true;
} catch (e) {
console.error('元素移除失败:', e);
return false;
}
}
static cleanAncestors(node) {
let current = node;
while (current && current !== document.documentElement) {
if (current.children.length === 0 &&
!current.hasAttribute('data-keep-empty')) {
const parent = current.parentNode;
parent?.removeChild(current);
current = parent;
} else {
break;
}
}
}
static isWhitelisted(element) {
let currentElement = element;
while (currentElement && currentElement !== document.documentElement) {
const src = currentElement.getAttribute('src');
if (src && CONFIG.whitelist.thirdparty.includes(src)) return true;
if (CONFIG.whitelist.selectors.some(s => currentElement.matches(s))) return true;
if (CONFIG.whitelist.protectedAttributes.some(attr => {
const attrValue = currentElement.getAttribute(attr.name);
return attrValue && attr.values.some(v =>
attr.name === 'class' ?
currentElement.classList.contains(v) :
attrValue.startsWith(v)
);
})) return true;
if (CONFIG.whitelist.scriptNamespaces.some(ns => {
const id = currentElement.id || '';
const className = currentElement.className || '';
return id.startsWith(ns) || className.startsWith(ns);
})) return true;
currentElement = currentElement.parentElement;
}
return false;
}
static calculateSimilarity(a, b) {
const setA = new Set(a.split(''));
const setB = new Set(b.split(''));
const intersection = new Set([...setA].filter(x => setB.has(x)));
return intersection.size / Math.max(setA.size, setB.size);
}
}
// ======================= 第三方拦截模块 =======================
class ThirdpartyInterceptor {
constructor() {
this.baseHost = this.getCurrentBaseHost();
this.initObserver();
}
getCurrentBaseHost() {
const host = location.hostname;
return this.isIP(host) ? host : this.getBaseDomain(host);
}
isIP(host) {
return /^(?:\d{1,3}\.){3}\d{1,3}$/.test(host);
}
getBaseDomain(url) {
try {
const parsed = new URL(url.startsWith('http') ? url : `http://${url}`);
const parts = parsed.hostname.split('.');
if (parts.length <= 1) return parsed.hostname;
return parts.slice(-2).join('.');
} catch {
return this.baseHost;
}
}
isThirdparty(src) {
if (!src || CONFIG.whitelist.thirdparty.includes(src)) return false;
try {
return this.getBaseDomain(src) !== this.baseHost;
} catch {
return false;
}
}
processNode(node) {
const tag = node.tagName.toUpperCase();
if (['SCRIPT', 'IFRAME'].includes(tag)) {
const src = node.getAttribute('src');
if (src && this.isThirdparty(src)) {
AdUtils.safeRemove(node, 'thirdparty', {
type: '第三方资源',
detail: `拦截源: ${this.getBaseDomain(src)}`
});
}
}
}
initObserver() {
const observer = new MutationObserver(mutations => {
mutations.forEach(m => {
m.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
this.processNode(node);
node.querySelectorAll('script, iframe').forEach(n => this.processNode(n));
}
});
});
});
observer.observe(document, {
childList: true,
subtree: true
});
// 初始扫描
document.querySelectorAll('script, iframe').forEach(n => this.processNode(n));
}
}
// ======================= 核心拦截系统 =======================
class CoreCleaner {
constructor() {
if (DomainConfig.getConfig('thirdpartyCheck')) {
this.thirdpartyInterceptor = new ThirdpartyInterceptor();
}
if (this.isAnyModuleEnabled()) {
this.initEnhancedObserver();
this.initialCleanup();
}
}
isAnyModuleEnabled() {
return Object.keys(CONFIG.defaultSettings).some(
key => DomainConfig.getConfig(key)
);
}
initEnhancedObserver() {
this.observer = new MutationObserver(mutations => {
mutations.forEach(m => {
m.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
// Shadow DOM支持
if (node.shadowRoot) {
this.observer.observe(node.shadowRoot, {
childList: true,
subtree: true
});
}
this.checkGenericElements(node);
if (node.tagName === 'IFRAME') this.checkIframes(node);
}
});
});
});
this.observer.observe(document, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['style', 'class', 'id', 'src', 'href']
});
}
initialCleanup() {
if (!this.isAnyModuleEnabled()) return;
this.runDetectionSystems();
this.checkElements('iframe', this.checkIframes.bind(this));
this.checkElements('*', this.checkGenericElements.bind(this));
}
runDetectionSystems() {
if (DomainConfig.getConfig('highZIndexDetection')) this.checkHighZIndex();
if (DomainConfig.getConfig('fixedPositionCheck')) this.checkFixedPosition();
if (DomainConfig.getConfig('overlayDetection')) this.checkOverlay();
}
checkElements(selector, checker) {
if (!this.isAnyModuleEnabled()) return;
document.querySelectorAll(selector).forEach(node => checker(node));
}
// ========== 框架过滤系统 ==========
checkIframes(iframe) {
if (DomainConfig.getConfig('iframeAttributeCheck')) {
this.checkIframeAttributes(iframe);
}
if (DomainConfig.getConfig('parentContainerCheck')) {
this.checkParentContainers(iframe);
}
}
checkParentContainers(iframe) {
const parents = ['div', 'section', 'article']
.map(s => iframe.closest(s))
.filter(Boolean);
parents.forEach(parent => {
if (!AdUtils.isWhitelisted(parent)) {
AdUtils.safeRemove(parent, 'frame-parent', {
type: '父容器过滤',
detail: '可疑iframe容器'
});
}
});
}
checkIframeAttributes(iframe) {
const hasAdSrc = CONFIG.adKeywords.some(kw => iframe.src.includes(kw));
const isHidden = iframe.offsetWidth < CONFIG.protectionRules.iframeSizeThreshold ||
iframe.offsetHeight < CONFIG.protectionRules.iframeSizeThreshold;
if (isHidden || hasAdSrc) {
AdUtils.safeRemove(iframe, 'frame-attr', {
type: 'iframe属性',
detail: hasAdSrc ?
`广告源: ${iframe.src.slice(0, 50)}` :
`隐藏iframe: ${iframe.offsetWidth}x${iframe.offsetHeight}`
});
}
}
// ========== 动态检测系统 ==========
checkGenericElements(element) {
if (DomainConfig.getConfig('dynamicIdDetection')) {
this.checkDynamicId(element);
}
if (DomainConfig.getConfig('attributeSimilarity')) {
this.checkAttributeSimilarity(element);
}
if (DomainConfig.getConfig('sizeAnomaly')) {
this.checkSizeAnomaly(element);
}
if (DomainConfig.getConfig('adAttributeDetection')) {
this.checkAdAttributes(element);
}
}
checkDynamicId(element) {
const id = element.id || '';
if (id.length > CONFIG.protectionRules.dynamicIdLength ||
/\d{5,}/.test(id)) {
AdUtils.safeRemove(element, 'dynamic-id', {
type: '动态ID检测',
detail: `ID特征异常: ${id.slice(0, 50)}`
});
}
}
checkAttributeSimilarity(element) {
const id = element.id || '';
const className = element.className || '';
if (AdUtils.calculateSimilarity(id, className) > CONFIG.protectionRules.similarityThreshold) {
AdUtils.safeRemove(element, 'attr-similarity', {
type: '属性相似度',
detail: `ID与class相似度过高`
});
}
}
checkSizeAnomaly(element) {
const rect = element.getBoundingClientRect();
if (rect.width * rect.height >
window.innerWidth * window.innerHeight * CONFIG.protectionRules.sizeThresholdRatio) {
AdUtils.safeRemove(element, 'size-anomaly', {
type: '尺寸异常',
detail: `占用面积 ${Math.round(rect.width*rect.height)}px²`
});
}
}
checkAdAttributes(element) {
const attrCheck = CONFIG.checkAttributes.find(attr => {
const value = element.getAttribute(attr) || '';
return CONFIG.adKeywords.some(kw => value.includes(kw));
});
if (attrCheck) {
AdUtils.safeRemove(element, 'attr-match', {
type: '属性匹配',
attribute: attrCheck,
value: element.getAttribute(attrCheck).slice(0, 100)
});
}
}
// ========== 堆叠拦截系统 ==========
checkHighZIndex() {
document.querySelectorAll('body *').forEach(el => {
const zIndex = parseInt(getComputedStyle(el).zIndex);
if (!isNaN(zIndex) && zIndex > CONFIG.protectionRules.zIndexThreshold) {
AdUtils.safeRemove(el, 'z-index', {
type: '高堆叠',
detail: `z-index: ${zIndex}`
});
}
});
}
checkFixedPosition() {
document.querySelectorAll('*').forEach(el => {
const position = getComputedStyle(el).position;
if (position === 'fixed' && !AdUtils.isWhitelisted(el)) {
AdUtils.safeRemove(el, 'fixed-pos', {
type: '固定定位',
detail: 'position: fixed'
});
}
});
}
checkOverlay() {
document.querySelectorAll('div, section').forEach(el => {
const style = getComputedStyle(el);
if (style.backgroundColor !== 'rgba(0, 0, 0, 0)' &&
parseFloat(style.opacity) > 0.5 &&
el.offsetWidth >= window.innerWidth * 0.8) {
AdUtils.safeRemove(el, 'overlay', {
type: '遮罩层',
detail: `背景色: ${style.backgroundColor}`
});
}
});
}
destructor() {
if (this.observer) this.observer.disconnect();
}
}
// ======================= 配置系统 =======================
class DomainConfig {
static get currentDomain() {
return location.hostname.replace(/^www\./, '');
}
static getConfig(key) {
const allConfigs = GM_getValue('domainConfigs', {});
const domainConfig = allConfigs[this.currentDomain] || {...CONFIG.defaultSettings};
return key in domainConfig ? domainConfig[key] : CONFIG.defaultSettings[key];
}
static updateConfig(key, value) {
const allConfigs = GM_getValue('domainConfigs', {});
if (!allConfigs[this.currentDomain]) {
allConfigs[this.currentDomain] = {...CONFIG.defaultSettings};
}
allConfigs[this.currentDomain][key] = value;
GM_setValue('domainConfigs', allConfigs);
}
static resetConfig() {
const allConfigs = GM_getValue('domainConfigs', {});
delete allConfigs[this.currentDomain];
GM_setValue('domainConfigs', allConfigs);
}
}
// ======================= 用户界面 =======================
class UIController {
static init() {
this.registerDomainControls();
this.registerGlobalControls();
this.registerInfoButton();
this.registerMasterSwitch();
}
static registerMasterSwitch() {
const allEnabled = Object.keys(CONFIG.defaultSettings).every(
key => DomainConfig.getConfig(key)
);
GM_registerMenuCommand(
`🔘 一键${allEnabled ? '禁用' : '启用'}所有模块`,
() => this.toggleAllModules()
);
}
static toggleAllModules() {
const allKeys = Object.keys(CONFIG.defaultSettings);
const currentState = allKeys.every(key => DomainConfig.getConfig(key));
allKeys.forEach(key => {
DomainConfig.updateConfig(key, !currentState);
});
GM_notification({
title: '总开关已切换',
text: `所有模块已${!currentState ? '启用' : '禁用'}`,
timeout: 2000
});
window.location.reload();
}
static registerInfoButton() {
GM_registerMenuCommand('📘 功能说明', () => this.showInstructions());
}
static showInstructions() {
const instructions = `【广告终结者功能说明】\n\n
====== 动态检测系统 ======\n
1. 动态ID检测
- 检测长度超过10字符的ID
- 包含5个以上连续数字的ID
2. 属性相似度
- 比较ID和class的相似度
- 相似度阈值:80%
3. 尺寸异常检测
- 检测超过视窗15%面积的元素
4. 广告属性检测
- 扫描属性:id/class/src/href/data-ad/name
====== 框架过滤系统 ======\n
5. iframe属性检测
- 检测src包含广告关键词
6. 父容器检测
- 检查iframe父级容器
====== 堆叠拦截系统 ======\n
7. 高z-index检测
- 阈值:z-index > 50
8. 固定定位检测
- 检测position: fixed元素
9. 遮罩层检测
- 背景色不透明检测
====== 第三方拦截系统 ======\n
10. 跨域资源拦截
- 自动识别主域名
- 拦截第三方脚本和iframe
【系统配置】
- 各模块独立开关
- 域名级配置存储`;
alert(instructions.replace(/ {4,}/g, ''));
}
static registerDomainControls() {
const SEPARATOR = '───────────────';
GM_registerMenuCommand(SEPARATOR, () => {});
this.registerModuleControls([
'thirdpartyCheck',
'dynamicIdDetection',
'attributeSimilarity',
'sizeAnomaly',
'adAttributeDetection'
]);
GM_registerMenuCommand(SEPARATOR, () => {});
this.registerModuleControls([
'iframeAttributeCheck',
'parentContainerCheck'
]);
GM_registerMenuCommand(SEPARATOR, () => {});
this.registerModuleControls([
'highZIndexDetection',
'fixedPositionCheck',
'overlayDetection'
]);
GM_registerMenuCommand(SEPARATOR, () => {});
GM_registerMenuCommand('🔄 重置当前网站设置', () => {
DomainConfig.resetConfig();
GM_notification({
title: '配置已重置',
text: `${DomainConfig.currentDomain} 设置已恢复默认`,
timeout: 2000
});
window.location.reload();
});
}
static registerModuleControls(keys) {
keys.forEach(key => {
const currentValue = DomainConfig.getConfig(key);
GM_registerMenuCommand(
` ${this.getKeyName(key)} [${currentValue ? '✅' : '❌'}]`,
() => this.toggleConfig(key)
);
});
}
static registerGlobalControls() {
GM_registerMenuCommand('📜 查看拦截日志', this.showLogs.bind(this));
GM_registerMenuCommand('🧹 清除当前日志', this.clearLogs.bind(this));
}
static toggleConfig(key) {
const current = DomainConfig.getConfig(key);
DomainConfig.updateConfig(key, !current);
GM_notification({
title: '配置已更新',
text: `${this.getKeyName(key)} 已${!current ? '启用' : '禁用'}`,
timeout: 1500
});
window.location.reload();
}
static getKeyName(key) {
const names = {
thirdpartyCheck: '第三方拦截',
dynamicIdDetection: '动态ID检测',
attributeSimilarity: '属性相似度',
sizeAnomaly: '尺寸异常',
adAttributeDetection: '广告属性检测',
iframeAttributeCheck: 'iframe属性',
parentContainerCheck: '父容器检测',
highZIndexDetection: '高z-index',
fixedPositionCheck: '固定定位',
overlayDetection: '遮罩层检测'
};
return names[key] || key;
}
static showLogs() {
const logs = Logger.getDomainLogs();
alert(logs.length ?
`【${DomainConfig.currentDomain}】拦截记录:\n\n${Logger.formatLogs(logs)}` :
'当前无拦截记录');
}
static clearLogs() {
Logger.clearDomainLogs();
GM_notification({title: '日志已清除', timeout: 1500});
}
}
// ======================= 日志系统 =======================
class Logger {
static logRemoval(record) {
const allLogs = GM_getValue('removalLogs', {});
const domainLogs = allLogs[DomainConfig.currentDomain] || [];
domainLogs.push({
timestamp: new Date().toLocaleString(),
...record
});
if (domainLogs.length > CONFIG.maxLogs) domainLogs.shift();
allLogs[DomainConfig.currentDomain] = domainLogs;
GM_setValue('removalLogs', allLogs);
}
static getDomainLogs() {
const allLogs = GM_getValue('removalLogs', {});
return allLogs[DomainConfig.currentDomain] || [];
}
static clearDomainLogs() {
const allLogs = GM_getValue('removalLogs', {});
delete allLogs[DomainConfig.currentDomain];
GM_setValue('removalLogs', allLogs);
}
static formatLogs(logs) {
return logs.map((log, index) =>
`[${index + 1}] ${log.timestamp}\n模块: ${log.module}\n` +
`类型: ${log.reason.type}\n详情: ${log.reason.detail || ''}`
).join('\n\n');
}
}
// ======================= 系统初始化 =======================
let coreCleaner;
function initialize() {
if (coreCleaner) coreCleaner.destructor();
coreCleaner = new CoreCleaner();
}
initialize();
UIController.init();
})();