// ==UserScript==
// @name 广告终结者
// @namespace http://tampermonkey.net/
// @version 2.6
// @description 完整功能广告拦截器,支持内容关键词过滤与智能检测,增加小说网站UA伪装功能
// @author TMHhz
// @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 *://*.www.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 *://*.www.gov.cn/*
// @exclude *://*.thepaper.cn/*
// @exclude *://*.xinhuanet.com/*
// @exclude *://*.china.com/*
// @exclude *://*.guancha.cn/*
// @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 GPLv3
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_notification
// @grant GM_addStyle
// @grant GM_getResourceText
// @connect self
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
// ======================= 新增小说网站检测与UA修改 =======================
(function detectNovelSite() {
const novelKeywords = [
'novel', 'xiaoshuo', '小说', '阅读',
'book', '章节', '文学', '小说网',
'txt', 'download', '免费小说'
];
const isNovelSite = () => {
// URL检测
const urlCheck = novelKeywords.some(k =>
window.location.href.toLowerCase().includes(k)
);
// 标题检测
const titleCheck = novelKeywords.some(k =>
document.title.toLowerCase().includes(k)
);
// 内容特征检测
const contentCheck = () => {
const metaKeywords = document.querySelector('meta[name="keywords"]')?.content || '';
const metaDescription = document.querySelector('meta[name="description"]')?.content || '';
return novelKeywords.some(k =>
metaKeywords.includes(k) || metaDescription.includes(k)
);
};
return urlCheck || titleCheck || contentCheck();
};
if (isNovelSite()) {
// 塞班系统典型UA
const symbianUA = 'NokiaN8-00/5.0 (Symbian/3; Series60/5.2 Mozilla/5.0; Profile/MIDP-2.1 Configuration/CLDC-1.1) AppleWebKit/533.4 (KHTML, like Gecko) BrowserNG/7.3.1.37';
// UA伪装注入
Object.defineProperty(window.navigator, 'userAgent', {
value: symbianUA,
writable: false,
configurable: false
});
}
})();
// ======================= 原核心代码保持不变 =======================
// 核心配置对象
const CONFIG = {
maxLogs: 150,
adKeywords: [
'ad', 'ads', 'advert', 'banner', 'popup', '推广', '广告', 'gg', 'advertisement', 'sponsor', '推荐', 'adv', 'guanggao', 'syad', 'bfad', '男男', '女女', '弹窗', '悬浮', '浮动', '浮窗', '葡京', 'pop', 'sticky', 'fixed', 'tip', 'tips', 'adbox', 'adsense', 'adserver', 'advertmarket', 'advertising', 'cookie-sync', '偷拍', '黑料', '横幅', '乱伦'
],
protectionRules: {
dynamicIdLength: 12,
zIndexThreshold: 50,
maxFrameDepth: 3,
textAdKeywords: ['限时优惠', '立即下载', '微信', 'vx:', 'telegram', '偷拍', '黑料']
},
contentFilter: {
scanDepth: 3,
minLength: 2,
maxKeywords: 50,
timeout: 300
},
defaultSettings: {
dynamicSystem: true,
layoutSystem: true,
frameSystem: true,
mediaSystem: true,
textSystem: true,
thirdPartyBlock: true,
contentFilter: true
}
};
// ======================= 工具类 =======================
class AdUtils {
static safeRemove(node, module, reason) {
if (!node?.parentNode || this.isWhitelisted(node)) return false;
try {
Logger.logRemoval({
module,
element: {
tag: node.tagName,
id: node.id,
class: node.className,
html: node.outerHTML?.slice(0, 200)
},
reason
});
node.parentNode.removeChild(node);
return true;
} catch(e) {
console.warn('元素移除失败:', e);
return false;
}
}
static isWhitelisted(element) {
return element.closest('[data-protected]') ||
this.hasWhitelistContent(element);
}
static shouldBlockByContent(element) {
if (!Config.get('contentFilter')) return false;
const text = this.getCleanText(element);
const blacklist = KeywordManager.getBlacklist();
const whitelist = KeywordManager.getWhitelist();
if (whitelist.some(k => text.includes(k))) return false;
return blacklist.some(k => text.includes(k));
}
static getCleanText(element) {
const rawText = this.extractText(element).trim();
return this.normalizeText(rawText);
}
static extractText(element, depth = 0) {
if (depth > CONFIG.contentFilter.scanDepth) return '';
return Array.from(element.childNodes).map(n => {
if (n.nodeType === Node.TEXT_NODE) return n.textContent;
if (n.nodeType === Node.ELEMENT_NODE) {
return this.extractText(n, depth + 1);
}
return '';
}).join(' ');
}
static normalizeText(text) {
return text
.replace(/\s+/g, ' ')
.replace(/[【】《》「」“”‘’]/g, '')
.toLowerCase();
}
static hasWhitelistContent(element) {
const text = this.normalizeText(element.textContent);
return KeywordManager.getWhitelist().some(k => text.includes(k));
}
}
// ======================= 核心系统 =======================
class CoreSystem {
constructor() {
this.initObservers();
this.initialClean();
this.setupContentScanner();
this.injectProtectionStyles();
}
initObservers() {
new MutationObserver(mutations => {
mutations.forEach(m => {
m.addedNodes.forEach(n => {
if(n.nodeType === 1) this.processElement(n);
});
});
}).observe(document, {childList: true, subtree: true});
}
initialClean() {
this.checkElements('*', el => this.processElement(el));
this.checkIframes();
this.checkThirdParty();
this.scanForContent(document.documentElement);
}
processElement(el) {
// 动态检测系统
if(Config.get('dynamicSystem')) {
this.checkDynamicId(el);
this.checkAdAttributes(el);
}
// 布局检测系统
if(Config.get('layoutSystem')) {
this.checkZIndex(el);
this.checkFixedPosition(el);
}
// 媒体检测系统
if(Config.get('mediaSystem')) {
this.checkImageAds(el);
this.checkFloatingAds(el);
}
// 文本广告检测
if(Config.get('textSystem')) {
this.checkTextAds(el);
}
}
setupContentScanner() {
if (!Config.get('contentFilter')) return;
new MutationObserver(mutations => {
mutations.forEach(m => {
m.addedNodes.forEach(n => {
if (n.nodeType === 1) this.scanForContent(n);
});
});
}).observe(document, {
childList: true,
subtree: true
});
}
scanForContent(element) {
if (AdUtils.shouldBlockByContent(element)) {
AdUtils.safeRemove(element, 'ContentFilter', {
type: '内容关键词匹配',
detail: '黑名单内容触发'
});
}
Array.from(element.children).forEach(child =>
this.scanForContent(child)
);
}
checkDynamicId(el) {
const id = el.id || '';
if(id.length > CONFIG.protectionRules.dynamicIdLength || /\d{5}/.test(id)) {
AdUtils.safeRemove(el, 'DynamicSystem', {
type: '动态ID检测',
detail: `异常ID: ${id.slice(0, 20)}`
});
}
}
checkAdAttributes(el) {
['id', 'class', 'src'].forEach(attr => {
const val = el.getAttribute(attr) || '';
if(CONFIG.adKeywords.some(k => val.includes(k))) {
AdUtils.safeRemove(el, 'DynamicSystem', {
type: '广告属性检测',
detail: `${attr}=${val.slice(0, 30)}`
});
}
});
}
checkZIndex(el) {
const zIndex = parseInt(getComputedStyle(el).zIndex);
if(zIndex > CONFIG.protectionRules.zIndexThreshold) {
AdUtils.safeRemove(el, 'LayoutSystem', {
type: '高堆叠元素',
detail: `z-index=${zIndex}`
});
}
}
checkFixedPosition(el) {
const style = getComputedStyle(el);
if(style.position === 'fixed' && el.offsetWidth < 200) {
AdUtils.safeRemove(el, 'LayoutSystem', {
type: '固定定位元素',
detail: `尺寸: ${el.offsetWidth}x${el.offsetHeight}`
});
}
}
checkImageAds(el) {
if(el.tagName === 'IMG' && (el.src.includes('ad') || el.src.endsWith('.gif'))) {
AdUtils.safeRemove(el, 'MediaSystem', {
type: '图片广告',
detail: `图片源: ${el.src.slice(0, 50)}`
});
}
}
checkFloatingAds(el) {
const rect = el.getBoundingClientRect();
const style = getComputedStyle(el);
if(['fixed', 'sticky'].includes(style.position) &&
(rect.top < 10 || rect.bottom > window.innerHeight - 10)) {
AdUtils.safeRemove(el, 'MediaSystem', {
type: '浮动广告',
detail: `位置: ${rect.top}px`
});
}
}
checkTextAds(el) {
const text = el.textContent?.toLowerCase() || '';
if (CONFIG.protectionRules.textAdKeywords.some(k => text.includes(k))) {
AdUtils.safeRemove(el, 'TextSystem', {
type: '文本广告',
detail: `关键词: ${text.slice(0, 50)}`
});
}
}
checkIframes() {
if(!Config.get('frameSystem')) return;
document.querySelectorAll('iframe').forEach(iframe => {
let depth = 0, parent = iframe;
while((parent = parent.parentNode)) {
if(parent.tagName === 'IFRAME') depth++;
}
if(depth > CONFIG.protectionRules.maxFrameDepth) {
AdUtils.safeRemove(iframe, 'FrameSystem', {
type: '深层嵌套框架',
detail: `嵌套层级: ${depth}`
});
}
const container = iframe.closest('div, section');
if(container && !AdUtils.isWhitelisted(container)) {
AdUtils.safeRemove(container, 'FrameSystem', {
type: '广告容器',
detail: 'iframe父容器'
});
}
});
}
checkThirdParty() {
if(!Config.get('thirdPartyBlock')) return;
document.querySelectorAll('script, iframe').forEach(el => {
try {
const src = new URL(el.src).hostname;
const current = new URL(location.href).hostname;
if(!src.endsWith(current)) {
AdUtils.safeRemove(el, 'ThirdParty', {
type: '第三方资源',
detail: `源域: ${src}`
});
}
} catch {}
});
}
injectProtectionStyles() {
GM_addStyle(`
[style*="fixed"], [style*="sticky"] {
position: static !important
}
iframe[src*="ad"] {
display: none !important
}
.ad-shield-protected {
border: 2px solid #4CAF50 !important;
}
`);
}
checkElements(selector, fn) {
document.querySelectorAll(selector).forEach(fn);
}
}
// ======================= 配置系统 =======================
class Config {
static get currentDomain() {
return location.hostname.replace(/^www\./, '');
}
static get allKeys() {
return Object.keys(CONFIG.defaultSettings);
}
static get(key) {
const data = GM_getValue('config') || {};
const domainConfig = data[this.currentDomain] || {};
return domainConfig[key] ?? CONFIG.defaultSettings[key];
}
static set(key, value) {
const data = GM_getValue('config') || {};
data[this.currentDomain] = {...CONFIG.defaultSettings, ...data[this.currentDomain], [key]: value};
GM_setValue('config', data);
}
static toggleAll(status) {
const data = GM_getValue('config') || {};
data[this.currentDomain] = Object.fromEntries(
Config.allKeys.map(k => [k, status])
);
GM_setValue('config', data);
}
}
// ======================= 关键词管理 =======================
class KeywordManager {
static getStorageKey(type) {
return `content_${type}_${Config.currentDomain}`;
}
static getBlacklist() {
return this.getKeywords('blacklist');
}
static getWhitelist() {
return this.getKeywords('whitelist');
}
static getKeywords(type) {
const raw = GM_getValue(this.getStorageKey(type), '');
return this.parseKeywords(raw);
}
static parseKeywords(raw) {
return raw.split(',')
.map(k => k.trim())
.filter(k => k.length >= CONFIG.contentFilter.minLength)
.slice(0, CONFIG.contentFilter.maxKeywords)
.map(k => k.toLowerCase());
}
static updateKeywords(type, keywords) {
const valid = [...new Set(keywords)]
.map(k => k.trim())
.filter(k => k.length >= CONFIG.contentFilter.minLength)
.slice(0, CONFIG.contentFilter.maxKeywords);
GM_setValue(
this.getStorageKey(type),
valid.join(',')
);
}
}
// ======================= 用户界面 =======================
class UIController {
static init() {
this.registerMainMenu();
this.registerModuleCommands();
this.registerContentMenu();
this.registerUtilityCommands();
}
static registerMainMenu() {
const allEnabled = Config.allKeys.every(k => Config.get(k));
GM_registerMenuCommand(
`🔘 主开关 [${allEnabled ? '✅' : '❌'}]`,
() => this.toggleAllModules(!allEnabled)
);
}
static registerModuleCommands() {
const modules = [
['dynamicSystem', '动态检测系统 (ID/属性)'],
['layoutSystem', '布局检测系统 (定位/z-index)'],
['frameSystem', '框架过滤系统 (iframe)'],
['mediaSystem', '媒体检测系统 (图片/浮动)'],
['textSystem', '文本广告检测'],
['thirdPartyBlock', '第三方拦截'],
['contentFilter', '内容过滤系统']
];
modules.forEach(([key, name]) => {
GM_registerMenuCommand(
`${name} [${Config.get(key) ? '✅' : '❌'}]`,
() => this.toggleModule(key, name)
);
});
}
static registerContentMenu() {
GM_registerMenuCommand('🔠 内容过滤管理', () => {
GM_registerMenuCommand('➕ 添加黑名单关键词', () =>
this.handleAddKeyword('blacklist'));
GM_registerMenuCommand('➕ 添加白名单关键词', () =>
this.handleAddKeyword('whitelist'));
GM_registerMenuCommand('📋 显示当前关键词', () =>
this.showCurrentKeywords());
GM_registerMenuCommand('🗑️ 清除所有关键词', () =>
this.clearKeywords());
});
}
static registerUtilityCommands() {
GM_registerMenuCommand('📜 查看拦截日志', () => this.showLogs());
GM_registerMenuCommand('🧹 清除当前日志', () => Logger.clear());
GM_registerMenuCommand('⚙️ 重置所有配置', () => this.resetConfig());
}
static toggleModule(key, name) {
const value = !Config.get(key);
Config.set(key, value);
this.showNotification(`${name} ${value ? '✅ 已启用' : '❌ 已禁用'}`);
setTimeout(() => location.reload(), 500);
}
static toggleAllModules(status) {
Config.toggleAll(status);
this.showNotification(`所有模块已${status ? '启用' : '禁用'}`);
setTimeout(() => location.reload(), 500);
}
static handleAddKeyword(type) {
const promptText = type === 'blacklist'
? '输入要屏蔽的关键词(支持中文):'
: '输入要豁免的关键词:';
const input = prompt(promptText);
if (!input) return;
const current = KeywordManager.getKeywords(type);
KeywordManager.updateKeywords(type, [...current, input]);
this.showNotification(
`已添加${type === 'blacklist' ? '黑' : '白'}名单关键词:${input}`
);
}
static showCurrentKeywords() {
const black = KeywordManager.getBlacklist();
const white = KeywordManager.getWhitelist();
alert(`【当前内容过滤规则 - ${location.hostname}】
■ 黑名单 (${black.length}个):
${black.join(', ') || '无'}
■ 白名单 (${white.length}个):
${white.join(', ') || '无'}`);
}
static clearKeywords() {
if (!confirm('确定清除所有关键词吗?')) return;
['blacklist', 'whitelist'].forEach(type => {
GM_setValue(KeywordManager.getStorageKey(type), '');
});
this.showNotification('已清除所有关键词');
}
static resetConfig() {
if (!confirm('确定重置所有配置吗?')) return;
const data = GM_getValue('config') || {};
delete data[Config.currentDomain];
GM_setValue('config', data);
this.showNotification('配置已重置');
setTimeout(() => location.reload(), 500);
}
static showLogs() {
const logs = Logger.getLogs();
alert(logs.length ?
`📃 最近${CONFIG.maxLogs}条拦截记录:\n\n${logs.map(l =>
`[${l.time}] ${l.module}\n类型: ${l.type}\n元素: ${l.element}`
).join('\n\n')}` :
'暂无拦截记录'
);
}
static showNotification(text, duration = 2000) {
GM_notification({
title: '广告终结者',
text: text,
silent: true,
timeout: duration
});
}
}
// ======================= 日志系统 =======================
class Logger {
static logRemoval(data) {
const logs = GM_getValue('logs', []);
logs.push({
time: new Date().toLocaleTimeString(),
module: data.module,
type: data.reason.type,
detail: data.reason.detail,
element: `${data.element.tag}#${data.element.id}`
});
GM_setValue('logs', logs.slice(-CONFIG.maxLogs));
}
static getLogs() {
return GM_getValue('logs', []);
}
static clear() {
GM_setValue('logs', []);
UIController.showNotification('日志已清空');
}
}
// ======================= 初始化 =======================
(function init() {
new CoreSystem();
UIController.init();
})();
})();