隐藏B站动态瀑布流中的广告、评论区广告、充电内容以及美化首页
// ==UserScript==
// @name BiliCleaner
// @namespace https://greatest.deepsurf.us/scripts/511437/
// @description 隐藏B站动态瀑布流中的广告、评论区广告、充电内容以及美化首页
// @version 2.6.2
// @author chemhunter
// @match *://t.bilibili.com/*
// @match *://space.bilibili.com/*
// @match *://www.bilibili.com/*
// @match *://live.bilibili.com/*
// @match *://message.bilibili.com/*
// @connect cdn.jsdelivr.net
// @connect raw.githubusercontent.com
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @icon https://i0.hdslb.com/bfs/static/jinkela/long/images/favicon.ico
// @license GPL-3.0 License
// @run-at document-start
// @noframes
// ==/UserScript==
(function() {
'use strict';
// --- 新增:声明全局变量 ---
let keywordRegex, keywordRegexGlobal, userSettings, biliAdWordsConfig, whiteList, messageDiv;
let commentAppObserver, dynamicPanelObserver, panelCardObserver, setupIntervalId;
let lastPathname = '';
let hiddenAdCount = 0;
let lastActiveUpName = null;
let setMainWidth = false;
let liveGiftObserver = null;
// --- 1. 定义默认配置与用户设置 (细化版) ---
const defaultSettings = {
global: {
label: "🖥️ 全局与首页_屏蔽项",
enable: true,
sub: {
swipe: { label: "首页大屏轮播", enable: true },
feed: { label: "首页推广动态卡片", enable: true },
nav: { label: "导航栏广告/会员入口", enable: true },
sidebar: { label: "侧边栏:热搜、公告等", enable: true }, // 合并了视频页和动态页的侧边栏
}
},
dynamic: {
label: "⚡ 动态瀑布流_屏蔽项",
enable: true,
sub: {
goods: { label: "商品推广", enable: true },
charge: { label: "充电专属", enable: true },
reverse: { label: "预约动态", enable: true },
widen: { label: "动态页面宽屏美化", enable: true },
popup: { label: "导航栏悬浮”动态“窗", enable: true } // 新增:控制 watchDynamicAllPanel
}
},
comment: {
label: "📺 视频评论区_屏蔽项",
enable: true,
sub: {
adBlock: { label: "评论区置顶广告", enable: true },
banner: { label: "评论区上方活动横幅", enable: true } // 新增:控制 .activity-m-v1 等
}
},
live: {
label: "🎥 直播间_屏蔽项",
enable: true,
sub: {
rank: { label: "上方榜单精简", enable: true },
giftTip: { label: "聊天栏礼物播报", enable: true },
giftBar: { label: "下方礼物栏隐藏", enable: true },
recommend: { label: "下方直播推荐隐藏", enable: true }
}
}
};
function synchronizeSettings(defaults, stored) {
if (!stored || typeof stored !== 'object') {
return JSON.parse(JSON.stringify(defaults));
}
const result = {};
for (let key in defaults) {
const defaultCategory = defaults[key];
const storedCategory = stored[key];
result[key] = {
label: defaultCategory.label,
enable: storedCategory && typeof storedCategory.enable === 'boolean' ? storedCategory.enable : defaultCategory.enable,
sub: {}
};
if (defaultCategory.sub) {
for (let subKey in defaultCategory.sub) {
const defaultSub = defaultCategory.sub[subKey];
const storedSub = storedCategory && storedCategory.sub ? storedCategory.sub[subKey] : null;
result[key].sub[subKey] = {
label: defaultSub.label,
enable: storedSub && typeof storedSub.enable === 'boolean' ? storedSub.enable : defaultSub.enable
};
}
}
}
return result;
}
// 定义统一的存储接口
const storage = {
get(key, defaultValue = null) {
try {
const value = GM_getValue(key, null);
if (value === null) return defaultValue;
if (typeof value === 'string' && (value.startsWith('{') || value.startsWith('['))) {
try { return JSON.parse(value); } catch(e) { return value; }
}
return value;
} catch(e) {
console.error('GM_getValue error', e);
const local = localStorage.getItem(key);
return local ? JSON.parse(local) : defaultValue;
}
},
set(key, value) {
try {
GM_setValue(key, JSON.stringify(value));
} catch(e) {
console.error('GM_setValue error', e);
localStorage.setItem(key, JSON.stringify(value));
}
}
};
function migrateFromLocalStorage() {
//防止重复无效迁移
if (localStorage.getItem('biliCleanerConfigMigrated', null)) return;
const keysConfig = {
'biliCleanerSettings': { type: 'object', strategy: 'overwrite_if_empty', targetKey: 'biliCleanerSettings'},
'biliUpWhiteList': { type: 'array', strategy: 'union', targetKey: 'biliUpWhiteList' },
'localConfig': { type: 'object', strategy: 'keep_newest', targetKey: 'localConfig' }
};
for (const [oldKey, config] of Object.entries(keysConfig)) {
const raw = localStorage.getItem(oldKey);
if (raw === null || raw === undefined) continue;
let localValue;
try {
localValue = JSON.parse(raw);
} catch (e) {
console.warn(`[BiliCleaner] 解析 localStorage.${oldKey} 失败,跳过迁移`, e);
continue;
}
const targetKey = config.targetKey;
const existing = storage.get(targetKey, null);
let merged;
if (existing === null) {
merged = localValue;
} else {
switch (config.strategy) {
case 'union':
if (Array.isArray(existing) && Array.isArray(localValue)) {
merged = [...new Set([...existing, ...localValue])];
} else {
merged = existing;
}
break;
case 'keep_newest':
if (typeof existing === 'object' && typeof localValue === 'object') {
const existingTime = existing.time || 0;
const localTime = localValue.time || 0;
merged = localTime > existingTime ? localValue : existing;
} else {
merged = existing;
}
break;
case 'overwrite_if_empty':
merged = existing;
break;
default:
merged = existing;
}
}
storage.set(targetKey, merged);
console.log(`[BiliCleaner] ✅ 已迁移并合并 localStorage.${oldKey} -> GM.${targetKey}`);
}
localStorage.setItem('biliCleanerConfigMigrated', true);
}
userSettings = synchronizeSettings(defaultSettings, storage.get('biliCleanerSettings', {}));
function saveSettings() {
storage.set('biliCleanerSettings', userSettings);
}
const defaultConfig = {
keywordStr: `淘宝|京东|天猫|美团|外卖|补贴|密令|折扣|福利|专属|下单|运(费?)险|[领惠叠]券|[低特好底保降差性]价`,
biliAdLinks: ['taobao.com', 'tb.cn', 'jd.com', 'pinduodilo.com','zhuanzhuan.com', 'mall.bilibili.com', 'gaoneng.bilibili.com'],
time: 0
};
async function fetchConfigFromGit(timeoutMs = 1500) { // 可配置超时时间,默认1000ms
let lastError = null;
const gitMirror = [
'https://cdn.jsdelivr.net/gh/chemhunter/biliadskip@main/biliadwordslinks.json',
'https://raw.githubusercontent.com/chemhunter/biliadskip/main/biliadwordslinks.json',
];
for (const source of gitMirror) {
const url = `${source}?t=${Date.now()}`;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, { signal: controller.signal });
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP错误! 状态码: ${response.status}`);
}
const text = await response.text();
try {
const configData = JSON.parse(text);
log(`✅ 从git镜像: ${source} 获取到广告基础配置`);
return configData;
} catch (parseError) {
throw new Error(`JSON解析失败: ${parseError.message}`);
}
} catch (error) {
clearTimeout(timeoutId);
lastError = error;
if (error.name === 'AbortError') {
console.warn(`镜像源 ${source} 请求超时 (${timeoutMs}ms),尝试下一个...`);
} else {
console.warn(`镜像源 ${source} 请求失败:`, error.message);
}
continue;
}
}
throw new Error(`所有镜像源均无法访问: ${lastError?.message || '未知错误'}`);
}
async function getConfigWithFallback(maxRetries = 2) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const res = await fetchConfigFromGit();
return res;
} catch (error) {
console.error(`尝试 ${attempt} 失败:`, error.message);
if (attempt >= maxRetries) {
console.warn('⚠️ 所有尝试均失败,将使用本地缓存或默认配置');
return null;
}
await new Promise(resolve => setTimeout(resolve, 500 * attempt));
}
}
return;
}
async function getAdWordsConfig() {
try {
const localConfig = storage.get('localConfig', null);
const lastUpdateTime = localConfig && localConfig.time || 0;
if (Date.now() - lastUpdateTime >= 24 * 3600 * 1000) {
const res = await getConfigWithFallback();
if (res) {
log(`⚙️ 配置信息:`, res);
biliAdWordsConfig = {
...res,
keywordStr: Object.values(res.keywordStr).join('|'),
time: Date.now()
};
localStorage.setItem("localConfig", JSON.stringify(biliAdWordsConfig));
storage.set('localConfig', biliAdWordsConfig);
} else {
log(`git读取失败,读取本地广告词缓存`);
biliAdWordsConfig = {...localConfig};
if (!biliAdWordsConfig.time) {
biliAdWordsConfig = defaultConfig;
}
}
} else {
log(`更新冷却中,读取本地广告词缓存`);
biliAdWordsConfig = {...localConfig};
if (!biliAdWordsConfig.time) {
biliAdWordsConfig = defaultConfig;
}
}
} catch (error) {
console.error("获取广告词配置失败:", error);
}
keywordRegex = new RegExp(biliAdWordsConfig.keywordStr.replace(/\s+/g, ''), 'i');
keywordRegexGlobal = new RegExp(biliAdWordsConfig.keywordStr.replace(/\s+/g, ''), 'gi');
}
function log(...args) {
console.log('[BiliCleaner] ', ...args);
}
function hideItem(element) {
if (element && element.style.display !== 'none') {
element.style.display = 'none';
}
}
function showMessage(msg) {
messageDiv.textContent = msg;
messageDiv.style.display = 'block';
setTimeout(() => {
messageDiv.style.display = 'none';
}, 3000);
}
function hideUnwantedElements() {
const rules = {
// 1. 顶部导航栏 (Global -> Nav)
nav: [
'li.v-popover-wrap.left-loc-entry', // 左侧定位/广告
'ul.left-entry > li.v-popover-wrap:last-child', // 下载客户端
'ul.right-entry > .vip-wrap', // 大会员
".bili-dyn-version-control__reminding", // 动态页新版提醒
],
// 2. 侧边栏 (Global -> Sidebar)
sidebar: [
".video-page-game-card-small", // 视频页:右侧游戏卡片
'.video-page-special-card-small', // 视频页:右侧特殊卡片
'.slide-ad-exp', // 视频页:右侧广告块
//'.video-share-wrap', // 视频页:分享按钮(可选)
'.video-card-ad-small', // 视频页:弹幕列表下小广告
'bili-dyn-home--member .right', // 动态页:右侧个人信息/公告
//'.bili-dyn-banner',
'aside.right > section > .bili-dyn-banner',// 动态页:右侧公告
'.bili-dyn-search-trendings', // 动态页:右侧热搜
],
// 3. 评论区横幅 (Comment -> Banner)
commentBanner: [
'.ad-report.strip-ad', // 视频下方广告上报条
'.activity-m-v1', // 评论区上方活动推广
'.reply-notice', // 动态评论区提醒条
'.w-100.over-hidden.p-relative.flip-view', // 直播间下方广告横条
],
// 4.1 直播间礼物栏 (Live -> GiftBar)
liveGiftBar: [
'gift-control-vm', // 下方送礼栏
'.gift-control-section', //
'.gift-menu-root', // 礼物列表
],
// 4.2 直播间聊天栏送礼物提示 (Live -> GiftTip)
liveGiftTip: [
'.live-room-app .app-body .aside-area .chat-history-panel .chat-history-list .chat-items .gift-item', // 聊天栏礼物消息 .chat-item
'.border-box.convention-msg.chat-item', //直播间上方红字系统防骗提醒
],
// 5. 直播间推荐 (Live -> Recommend)
liveRecommend: [
'.room-info-ctnr', // 下方推荐直播 4x2
],
// 6. 直播间榜单 (Live -> Rank)
liveRank: [
'rank-list-ctnr-box .tab-content.ts-dot-2', // 右上角/上方榜单内容
// "rank-list-vm", // (原代码注释掉的,如需开启可解注)
// ".rank-list-section",
],
};
const { hostname, pathname } = location;
const isVideoPage = pathname.startsWith('/video/');
const isDynamicPage = hostname === 't.bilibili.com' || pathname.startsWith('/opus/');
const isLivePage = hostname === 'live.bilibili.com' || pathname.startsWith('/live/');
let selectorsToApply = [];
// --- 应用逻辑 ---
// 全局开关检查
if (userSettings.global.enable) {
// 导航栏
if (userSettings.global.sub.nav.enable) {
selectorsToApply.push(...rules.nav);
}
// 侧边栏 (视频页 或 动态页热搜)
if (userSettings.global.sub.sidebar.enable) {
if (isVideoPage || isDynamicPage) {
selectorsToApply.push(...rules.sidebar);
}
}
}
// 评论区开关检查
if (userSettings.comment.enable) {
if (userSettings.comment.sub.banner.enable) {
selectorsToApply.push(...rules.commentBanner);
}
}
// 直播间开关检查
if (isLivePage && userSettings.live.enable) {
initLiveCleaner();
if (userSettings.live.sub.giftBar.enable) selectorsToApply.push(...rules.liveGiftBar);
if (userSettings.live.sub.giftTip.enable) selectorsToApply.push(...rules.liveGiftTip);
if (userSettings.live.sub.recommend.enable) selectorsToApply.push(...rules.liveRecommend);
if (userSettings.live.sub.rank.enable) {
selectorsToApply.push(...rules.liveRank);
// 直播间榜单高度调整逻辑
const parentElement = document.getElementById('rank-list-vm');
const childElement = document.getElementById('rank-list-ctnr-box');
// const scrollbarHeight = document.getElementById('.ps__scrollbar-y-rail');
// if ( scrollbarHeight ) scrollbarHeight.style.height = '432px';
if (parentElement && childElement) {
let height = parseFloat(window.getComputedStyle(childElement).height);
height = !parentElement.dataset.heightModified ? height/3 - 1 : height;
parentElement.style.height = `${height}px`;
childElement.style.height = `${height}px`;
parentElement.dataset.heightModified = 'true';
}
}
}
// 执行隐藏
for (const selector of selectorsToApply) {
const element = document.querySelector(selector);
if (element) {
hideItem(element);
}
}
}
function initLiveCleaner() {
if (userSettings.live.sub.giftTip.enable) {
observeLiveGiftTips();
}
}
function stopLiveCleaner() {
if (liveGiftObserver) {
liveGiftObserver.disconnect();
liveGiftObserver = null;
}
}
function observeLiveGiftTips() {
if (liveGiftObserver) return;
const container = document.querySelector('.live-room-app .app-body .aside-area .chat-items');
if (!container) return;
// 先清理已有礼物消息
container.querySelectorAll('.chat-item.gift-item').forEach(hideItem);
liveGiftObserver = new MutationObserver(mutations => {
for (const mutation of mutations) {
mutation.addedNodes.forEach(node => {
if (node.nodeType !== 1) return;
if (node.classList.contains('gift-item')) {
hideItem(node);
}
});
}
});
liveGiftObserver.observe(container, { childList: true });
}
// 检查评论区
function checkCommentTopAdsOld() {
const commentAds = document.querySelectorAll('.dynamic-card-comment .comment-list.has-limit .list-item.reply-wrap.is-top');
commentAds.forEach(comment => {
const links = comment.querySelectorAll('a');
links.forEach(link => {
const href = link.getAttribute('href');
if (href && biliAdWordsConfig.biliAdLinks.some(blocked => href.includes(blocked))) {
hideItem(comment);
log('评论区置顶广告+1(链接)')
return true;
}
});
});
return false;
}
// 新版本动态 commentapp or bili-dyn-comment; 旧版本动态 dynamic-card-comment
function checkCommentsForAds() {
// --- 对应 adBlock 开关 ---
if (!userSettings.comment.enable || !userSettings.comment.sub.adBlock.enable) return false;
const dynCommentOldVersion = document.querySelector('.dynamic-card-comment');
if (dynCommentOldVersion) {
const result = checkCommentTopAdsOld();
if (result) {
hiddenAdCount++;
}
return result;
}
const commentsContainer = document.querySelector('#commentapp > bili-comments') || document.querySelector('.bili-dyn-comment > bili-comments');
if (commentsContainer && commentsContainer.shadowRoot) {
const headerElement = commentsContainer.shadowRoot.querySelector("#header > bili-comments-header-renderer");
if (headerElement && headerElement.shadowRoot) {
const noticeElement = headerElement.shadowRoot.querySelector("#notice > bili-comments-notice");
if (noticeElement && noticeElement.shadowRoot) {
const closeElement = noticeElement.shadowRoot.querySelector("#close");
if (closeElement) {
log("评论区横条,自动关闭");
closeElement.click();
}
}
}
const thread = commentsContainer.shadowRoot.querySelector('bili-comment-thread-renderer');
if (thread && window.getComputedStyle(thread).display !== 'none' && thread.shadowRoot) {
const commentRenderer = thread.shadowRoot.querySelector('#comment');
if (commentRenderer && commentRenderer.shadowRoot) {
const richText = commentRenderer.shadowRoot.querySelector('#content > bili-rich-text');
if (richText && richText.shadowRoot) {
const contentsElement = richText.shadowRoot.querySelector('#contents');
if (contentsElement) {
let foundAd;
const links = contentsElement.querySelectorAll('a');
links.forEach(link => {
const href = link.getAttribute('href');
if (href && biliAdWordsConfig.biliAdLinks.some(blocked => href.includes(blocked))) {
foundAd = true;
}
});
if (!foundAd) {
const commentText = contentsElement.textContent.trim();
const matchedKeywords = findAdwords(commentText);
if (matchedKeywords.length >=2) {
foundAd = true;
log('广告评论:', commentText.slice(0, 60));
log('匹配关键词 >=2:', matchedKeywords)
}
}
if (foundAd) {
hideItem(thread);
hiddenAdCount++;
log('评论区置顶广告 +1')
const isVideoPage = window.location.pathname.startsWith('/video/');
if (isVideoPage) {
window.MyObserver.disconnect();
}
let message = `隐藏广告 x ${hiddenAdCount}`;
showMessage(message);
return true;
}
}
}
}
}
}
return false;
}
function findAdwords(text) {
const notAd = ['评论','评论区','产品'];
const matches = text.match(keywordRegexGlobal);
if (!matches) return [];
return matches.filter(match => !notAd.includes(match));
}
function processFeedCards() {
const adSpans = document.querySelectorAll('span.bili-video-card__stats--text');
adSpans.forEach(span => {
if (span.textContent.trim() === '广告') {
const targetCard = span.closest('.bili-feed-card') || span.closest('.feed-card');
if (targetCard) {
hideItem(targetCard);
}
}
});
const allFeedCards = document.querySelectorAll('.feed-card');
allFeedCards.forEach(card => {
const hasVideoWrap = card.querySelector('.bili-video-card__wrap');
if (!hasVideoWrap) {
hideItem(card);
return;
}
});
}
function logCurrentActiveUp() {
if (window.location.hostname === 't.bilibili.com') {
const upListContainer = document.querySelector('.bili-dyn-up-list__window');
if (!upListContainer) {
return;
}
const activeUpElement = document.querySelector('.bili-dyn-up-list__item.active .bili-dyn-up-list__item__name');
let currentActiveUpName = null;
if (activeUpElement) {
currentActiveUpName = activeUpElement.textContent.trim();
} else {
// 检查是否是“全部动态”被激活
const allDynamicActive = document.querySelector('.bili-dyn-up-list__item.active .bili-dyn-up-list__item__face.all');
if (allDynamicActive) {
currentActiveUpName = '全部动态';
}
}
// 只有的当前激活的UP主与上次不同时才输出日志
if (currentActiveUpName && currentActiveUpName !== lastActiveUpName) {
const inWhiteList = whiteList.includes(currentActiveUpName)? " (白名单)" : '';
console.log(
`[BiliCleaner] UP: %c${currentActiveUpName}${inWhiteList}`,
'background: #009688; color: #fff; padding: 2px 5px; border-radius: 2px; font-weight: bold;'
);
lastActiveUpName = currentActiveUpName;
} else if (!currentActiveUpName && lastActiveUpName !== null) {
lastActiveUpName = null;
}
} else {
if (lastActiveUpName !== null) {
lastActiveUpName = null;
}
}
}
function checkForContentToHide() {
let hiddenChargeCount = 0;
let hiddenAdCount = 0;
const hostname = window.location.hostname;
const pathname = window.location.pathname;
// 0. 执行CSS清理
hideUnwantedElements();
//B站首页
if (hostname === 'www.bilibili.com' && !pathname.startsWith('/video/')) {
if (userSettings.global.enable) {
// ---首页Feed流卡片 ---
if (userSettings.global.sub.feed.enable) {
processFeedCards();
document.querySelectorAll('.floor-single-card').forEach(card => hideItem(card));
// 隐藏无视频包裹的卡片
document.querySelectorAll('.feed-card').forEach(card => {
if (!card.querySelector('.bili-video-card__wrap')) hideItem(card);
});
}
// --- 首页大屏轮播 ---
if (userSettings.global.sub.swipe.enable) {
const targetElement = document.querySelector('.recommended-swipe');
hideItem(targetElement);
}
}
//动态和个人空间页面
} else if (['t.bilibili.com', 'space.bilibili.com'].includes(hostname)) {
if (hostname === 't.bilibili.com') {
logCurrentActiveUp();
// 宽屏美化
if (userSettings.dynamic.enable && userSettings.dynamic.sub.widen.enable) {
if (!setMainWidth) {
const dynMain = document.querySelector('.bili-dyn-home--member > main')
if (dynMain) {
const currentWidth = parseInt(getComputedStyle(dynMain).width, 10);
dynMain.style.width = (currentWidth + 260) + 'px';
setMainWidth = true;
}
}
const contentDiv = document.querySelector("#app > div.content");
if (contentDiv && contentDiv.style.width !== '900px') {
contentDiv.style.width = '900px';
}
}
}
checkCommentsForAds();
// --- 动态过滤逻辑 ---
if (userSettings.dynamic.enable) {
const items = document.querySelectorAll('.bili-dyn-list__item');
items.forEach(item => {
if (window.getComputedStyle(item).display !== 'none') {
const titleElement = item.querySelector('.bili-dyn-title');
if (titleElement && whiteList.includes(titleElement.textContent.trim())) return;
function isAdItem(item) { return item.querySelector('.bili-dyn-card-goods, .dyn-goods, bili-dyn-card-goods, dyn-goods'); }
function isChargeItem(item) {
if (item.querySelector('.dyn-blocked-mask, .bili-dyn-upower-common, .bili-dyn-upower-lottery, .dyn-icon-badge__renderimg.bili-dyn-item__iconbadge, .bili-dyn-card-common')) return true;
const badge = item.querySelector('.bili-dyn-card-video__badge');
if (badge && /专属|抢先看/.test(badge.textContent)) return true;
const lotteryTitle = item.querySelector('.dyn-upower-lottery__title');
if (lotteryTitle && lotteryTitle.textContent.includes('专属')) return true;
return false;
}
// 商品过滤
if (userSettings.dynamic.sub.goods.enable) {
if (isAdItem(item)) {
hideItem(item);
log('广告卡片 +1');
hiddenAdCount++;
return;
}
// 过期预约也归类在此
const disabled = item.querySelector('.uncheck.disabled');
if (disabled) {
hideItem(item);
return;
}
}
// --- 充电/专属过滤开关 ---
if (userSettings.dynamic.sub.charge.enable) {
if (isChargeItem(item)) {
const titleElement = item.querySelector('.bili-dyn-card-video__title');
if (titleElement) {
const videoTitle = titleElement.textContent.trim();
log(`充电专属 +1: \n ----->"${videoTitle}"`);
} else {
log(`充电专属 +1`);
}
hideItem(item);
hiddenChargeCount++;
return;
}
}
// 辅助函数:在指定容器中检查广告并隐藏
function checkAndHideAd(container, type) {
let richtext = container.querySelector('.bili-rich-text .bili-rich-text__content')?.textContent?.trim();
if ( !richtext ) {
richtext = container.querySelector('.dyn-card-opus')?.textContent?.trim();
}
if (richtext) {
const isLotteryCard = (item) => {
const text = item.innerText || '';
if (text.includes('抽奖')) return true;
return false;
};
if (isLotteryCard(container)) return false;
const matchedKeywords = findAdwords(richtext);
if (matchedKeywords.length >=2) {
log(`广告关键词 +1(${type}) \n ----> ${richtext.slice(0, 60)}`);
log('匹配词 >=2: ', matchedKeywords)
hideItem(item);
hiddenAdCount++;
return true;
}
}
return false;
}
// --- 【修改】关键词/链接过滤开关 ---
if (userSettings.dynamic.sub.goods.enable) {
//查找动态主体内容 bili-dyn-content
const bili_dyn_content = item.querySelector('.bili-dyn-content');
// 注意:这里需要加个判空,防止报错
if (bili_dyn_content) {
const origContent = bili_dyn_content.querySelector('.bili-dyn-content__orig.reference');
const orig = origContent ? '转发' : '原创'
if(checkAndHideAd(bili_dyn_content, orig)) return; // 如果隐藏了,直接返回
}
const spans = item.querySelectorAll('span');
spans.forEach(span => {
const dataUrl = span.getAttribute('data-url');
if (dataUrl && biliAdWordsConfig.biliAdLinks.some(blocked => dataUrl.includes(blocked))) {
hideItem(item);
log('广告链接 +1')
hiddenAdCount++;
} else if (span.textContent.includes('专属')) {
hideItem(item);
log('充电专属 +1')
hiddenChargeCount++;
return;
}
});
}
}
});
}
//视频页面
} else if (pathname.startsWith('/video/BV')) {
// --- 【修改】评论区开关 ---
if (userSettings.comment.enable) {
if (!checkCommentsForAds()) {
setTimeout(() => {
checkCommentsForAds();
}, 2000);
}
}
}
let message = '';
if (hiddenChargeCount > 0) {
message += `隐藏充电 x ${hiddenChargeCount} `;
}
if (hiddenAdCount > 0) {
message += `隐藏广告 x ${hiddenAdCount} `;
}
if (message) {
showMessage(message.trim());
} else {
logCurrentActiveUp();
}
}
// 元素是否可见
function isVisible(el) {
return el && el.offsetParent !== null;
}
/** 过滤单个动态卡片链接 */
function filterSingleDynamicLink(linkElement) {
//if (!isVisible(linkElement)) return;
const title = linkElement.getAttribute('title') || '';
const tagSpan = linkElement.querySelector('.all-in-one-article-title > .article-tag');
const isArticle = tagSpan && tagSpan.textContent.trim() === '专栏';
if (!isArticle) return;
log(title);
if (keywordRegex.test(title)) {
const authorElement = linkElement.querySelector('.user-name a[title]');
const author = authorElement ? authorElement.getAttribute('title') : '未知作者';
log(`🚫 [动态弹窗] 广告卡片: 「${author}」- ${title.slice(0, 20)}...`);
linkElement.style.display = 'none';
}
}
/* 停用代码,通过更精确的网络api拦截动态按钮广告, "type":64
function watchDynamicAllPanel() {
// --- 对应 popup 开关 ---
if (!userSettings.dynamic.enable || !userSettings.dynamic.sub.popup.enable) return;
// ...
}
*/
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
function initObserver() {
const mainObserver = new MutationObserver(debounce(checkForContentToHide, 300));
mainObserver.observe(document.body,{ childList: true, subtree: true, });
return mainObserver;
}
function restartMainObserver() {
log('页面内容更新,重启观察器');
if (window.MyObserver) {
window.MyObserver.disconnect();
}
const mainObserver = initObserver();
window.MyObserver = mainObserver;
}
// 监听 commentapp 元素的变化
function initCommentAppObserver() {
const commentApp = document.querySelector('#commentapp');
if (commentApp) {
commentAppObserver = new MutationObserver(restartMainObserver);
commentAppObserver.observe(commentApp, { childList: true, subtree: true, attributes: true, attributeFilter: ['class']});
log('启动观察commentapp');
}
}
/** 显示白名单管理菜单*/
async function whiteListMenu() {
// 添加到白名单
function addToWhiteList(upId) {
if (!whiteList.includes(upId)) {
whiteList.push(upId);
//localStorage.setItem('biliUpWhiteList', JSON.stringify(whiteList));
storage.set('biliUpWhiteList', whiteList);
updateWhiteListDisplay();
} else {
alert(`${upId} 已在白名单中`);
}
syncConfigToPage();
}
// 从白名单中移除
function removeFromWhiteList(upId) {
const index = whiteList.indexOf(upId);
if (index !== -1) {
whiteList.splice(index, 1);
//localStorage.setItem('biliUpWhiteList', JSON.stringify(whiteList));
storage.set('biliUpWhiteList', whiteList);
updateWhiteListDisplay();
} else {
alert(`${upId} 不在白名单中`);
}
syncConfigToPage();
}
// 更新白名单显示
function updateWhiteListDisplay() {
const listDisplay = document.getElementById('whiteListDisplay');
if (listDisplay) {
listDisplay.textContent = whiteList.join(', ') || '白名单为空';
}
const currentUserRow = document.getElementById('bili-current-up-display');
const upInfo = getUpInfo();
if (currentUserRow) {
if (upInfo && upInfo.name) {
currentUserRow.innerHTML = `当前页面UP主: <b style="color: #00a1d6;">${upInfo.name}</b>`;
} else {
currentUserRow.innerHTML = '';
}
}
// 2. 【核心新增】更新“添加/移除当前页UP”按钮的状态
const currentUpBtn = document.getElementById('bili-add-current-up-btn');
if (currentUpBtn) {
const upInfo = getUpInfo();
if (upInfo && upInfo.name) {
currentUpBtn.style.display = '';
if (whiteList.includes(upInfo.name)) {
currentUpBtn.textContent = `移除当前UP`;
currentUpBtn.style.backgroundColor = '#e74c3c'; // 红色
} else {
currentUpBtn.textContent = `添加当前UP`;
currentUpBtn.style.backgroundColor = '#2eac31'; // 绿色
}
} else {
currentUpBtn.style.display = 'none';
}
}
}
function getUpInfo() {
const isSpacePage = window.location.href.match(/space.bilibili.com\/(\d+)/);
const isVideoPage = window.location.href.includes('/video/BV');
const container = isSpacePage ? document.querySelector('.upinfo__main') : document.querySelector('.up-panel-container');
if (isVideoPage) {
// 单个UP主的情况
const singleUp = container.querySelector('.up-detail .up-detail-top .up-name');
if (singleUp) {
const clone = singleUp.cloneNode(true);
clone.querySelectorAll('span').forEach(span => span.remove());
const name = clone.textContent.trim();
const href = singleUp.getAttribute('href');
const idMatch = href.match(/space\.bilibili\.com\/(\d+)/);
const id = idMatch ? idMatch[1] : null;
return { name, id};
} else {
//多个UP的情况
const allMemberCards = container.querySelectorAll('.membersinfo-upcard');
const firstUpCard = allMemberCards[0];
if (firstUpCard) {
const nameElement = firstUpCard.querySelector('.staff-name');
const name = nameElement ? nameElement.textContent.trim() : null;
const idElement = firstUpCard.querySelector('a[href*="space.bilibili.com"]');
const href = idElement ? idElement.getAttribute('href') : null;
const id = href ? href.match(/\/\/space\.bilibili\.com\/(\d+)/)?.[1] : null;
return { name, id};
}
}
}
return null;
}
const UpWhiteListContainer = document.createElement('div');
UpWhiteListContainer.id = 'UpWhiteListContainer';
UpWhiteListContainer.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 500px;
padding: 20px;
background: #fff;
border: 1px solid #ccc;
border-radius: 10px;
z-index: 10000;
font-size: 16px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
`;
const Title = document.createElement('h3');
Title.textContent = `手动管理白名单(跳过检测)`;
Title.style.cssText = 'text-align: center; margin-bottom: 20px; font-weight: bold; cursor: move; user-select: none;';
UpWhiteListContainer.appendChild(Title);
const toggleUpRow = document.createElement('div');
toggleUpRow.style.cssText = `display: flex; align-items: center; margin-bottom: 10px; gap: 10px;`;
const toggleUpLabel = document.createElement('label');
toggleUpLabel.textContent = '添加/移除UP主:';
toggleUpLabel.style.cssText = `flex-shrink: 0;`;
// 为“执行”按钮绑定智能的切换逻辑
const handleToggle = () => {
const upName = toggleUpInput.value.trim();
if (!upName) return;
if (whiteList.includes(upName)) {
removeFromWhiteList(upName);
} else {
addToWhiteList(upName);
}
toggleUpInput.value = '';
};
const toggleUpInput = document.createElement('input');
toggleUpInput.type = 'text';
toggleUpInput.id = 'toggleUpInput';
toggleUpInput.placeholder = '输入UP主昵称';
toggleUpInput.style.cssText = 'flex-grow: 1; min-width: 200; max-width: 240px; border: 1px solid #ccc;';
toggleUpInput.addEventListener('keydown', (event) => {
if (event.key === 'Enter') handleToggle();
});
const toggleButton = createButton('执行', handleToggle);
toggleButton.style.minWidth = '80px';
toggleUpRow.appendChild(toggleUpLabel);
toggleUpRow.appendChild(toggleUpInput);
toggleUpRow.appendChild(toggleButton);
UpWhiteListContainer.appendChild(toggleUpRow);
// 白名单列表显示区域
const listDiv = document.createElement('div');
listDiv.id = 'whiteListDisplay';
listDiv.style.cssText = `
text-align: left;
color: #30b688;
margin: 20px 0;
padding: 5px;
border: 1px dashed #ccc;
border-radius: 5px;
font-size: 14px;
word-break: break-word;
max-height: 150px;
overflow-y: auto;`;
listDiv.textContent = whiteList.join(', ') || '白名单为空';
UpWhiteListContainer.appendChild(listDiv);
const currentUpInfo = getUpInfo();
if (currentUpInfo && currentUpInfo.name) {
const currentUserRow = document.createElement('div');
currentUserRow.id = 'bili-current-up-display';
currentUserRow.style.cssText = `text-align: center; font-size: 16px; color: #555; margin: 5px 0; padding: 5px;`;
UpWhiteListContainer.appendChild(currentUserRow);
}
const buttonContainer = document.createElement('div');
buttonContainer.style.cssText = 'display: flex; justify-content: center; margin: 10px 0; gap: 10px';
function createButton(text, onClick) {
const button = document.createElement('button');
button.textContent = text;
button.style.cssText = `padding: 3px 3px; border: 1px solid #ccc; background: #f0f0f0; border-radius: 4px; cursor: pointer; font-size: 14px;`;
if (onClick) button.onclick = onClick;
return button;
}
const finishButton = createButton('关闭界面', () => {
document.body.removeChild(UpWhiteListContainer);
})
if (currentUpInfo && currentUpInfo.name) {
const upName = currentUpInfo.name;
const toggleCurrentUpButton = document.createElement('button');
toggleCurrentUpButton.id = 'bili-add-current-up-btn';
toggleCurrentUpButton.style.cssText = `color: white; padding: 4px 5px; margin: 0 5px; border: none; border-radius: 4px;`;
toggleCurrentUpButton.addEventListener('click', () => {
if (whiteList.includes(upName)) {
removeFromWhiteList(upName);
} else {
addToWhiteList(upName);
}
});
buttonContainer.appendChild(toggleCurrentUpButton);
}
buttonContainer.appendChild(finishButton);
UpWhiteListContainer.appendChild(buttonContainer);
document.body.appendChild(UpWhiteListContainer);
updateWhiteListDisplay();
}
function prepareForWork() {
// 迁移旧localStorage数据(合并+去重)
migrateFromLocalStorage();
// 初始化白名单
//whiteList = JSON.parse(localStorage.getItem('biliUpWhiteList')) || [];
whiteList = storage.get('biliUpWhiteList', []);
// --- 注册设置菜单 ---
GM_registerMenuCommand("⚙️ 功能开关", openSettingsMenu);
// 注册菜单命令
GM_registerMenuCommand("🛡️ UP白名单", whiteListMenu);
//创建提醒元素
messageDiv = document.createElement('div');
Object.assign(messageDiv.style, {
position: 'fixed',
top: '10px',
right: '10px',
padding: '10px',
backgroundColor: 'rgba(0, 0, 0, 0.7)',
color: 'white',
borderRadius: '5px',
zIndex: '9999',
display: 'none'
});
if (document.body) {
document.body.appendChild(messageDiv);
} else {
document.addEventListener('DOMContentLoaded', () => {
document.body.appendChild(messageDiv);
});
}
lastPathname = window.location.pathname;
syncConfigToPage();
}
// --- 【新增】2. 设置菜单 UI ---
function openSettingsMenu() {
// 创建背景遮罩
const backdrop = document.createElement('div');
backdrop.style.cssText = 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.4); z-index: 10000;';
// 创建菜单容器
const container = document.createElement('div');
container.id = 'BiliCleanerSettings';
container.style.cssText = 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 500px; padding: 20px; background: #fff; border-radius: 10px; z-index: 10001; font-size: 15px; box-shadow: 0 4px 12px rgba(0,0,0,0.2); max-height: 80vh; overflow-y: auto;';
// 标题
const title = document.createElement('h3');
title.textContent = 'BiliCleaner 功能开关';
title.style.cssText = 'text-align: center; margin-bottom: 15px; font-weight: bold; border-bottom: 1px solid #eee; padding-bottom: 10px;';
container.appendChild(title);
const content = document.createElement('div');
// 遍历生成开关
for (const [key, group] of Object.entries(userSettings)) {
const groupDiv = document.createElement('div');
groupDiv.style.cssText = "margin-bottom: 10px; padding: 10px; background: #f9f9f9; border-radius: 6px;";
// 父级开关行
const header = document.createElement('div');
header.style.cssText = "display: flex; align-items: center; font-weight: bold; margin-bottom: 5px;";
const groupCb = document.createElement('input');
groupCb.type = 'checkbox';
groupCb.checked = group.enable;
groupCb.style.marginRight = '8px';
const groupLabel = document.createElement('span');
groupLabel.textContent = group.label;
header.appendChild(groupCb);
header.appendChild(groupLabel);
groupDiv.appendChild(header);
const subContainer = document.createElement('div');
subContainer.style.cssText = `margin-left: 24px; display: ${group.enable ? 'block' : 'none'};`;
groupCb.onchange = (e) => {
group.enable = e.target.checked;
subContainer.style.display = group.enable ? 'block' : 'none';
saveSettings();
};
for (const [subKey, subItem] of Object.entries(group.sub)) {
const subRow = document.createElement('div');
subRow.style.margin = "5px 0";
const subCb = document.createElement('input');
subCb.type = 'checkbox';
subCb.checked = subItem.enable;
subCb.style.marginRight = '8px';
const subLabel = document.createElement('label');
subLabel.textContent = subItem.label;
subLabel.style.cursor = 'pointer';
subLabel.onclick = () => subCb.click();
subCb.onchange = (e) => {
subItem.enable = e.target.checked;
saveSettings();
};
subRow.appendChild(subCb);
subRow.appendChild(subLabel);
subContainer.appendChild(subRow);
}
groupDiv.appendChild(subContainer);
content.appendChild(groupDiv);
}
container.appendChild(content);
// 关闭按钮
const closeBtn = document.createElement('button');
closeBtn.textContent = "保存并生效";
closeBtn.style.cssText = `display: block; width: 100%; padding: 10px; background: #00a1d6; color: white; border: none; border-radius: 5px; cursor: pointer; margin-top: 15px;`;
const closeAction = () => {
saveSettings();
document.body.removeChild(backdrop);
document.body.removeChild(container);
//checkForContentToHide();
showMessage("设置已保存");
reinitializeAllObservers();
syncConfigToPage();
};
closeBtn.onclick = closeAction;
backdrop.onclick = closeAction;
container.appendChild(closeBtn);
document.body.appendChild(backdrop);
document.body.appendChild(container);
}
function reinitializeAllObservers() {
log('执行观察器初始化...');
// 1. 断开所有可能存在的旧观察器
if (window.MyObserver) window.MyObserver.disconnect();
if (commentAppObserver) commentAppObserver.disconnect();
if (dynamicPanelObserver) dynamicPanelObserver.disconnect();
stopLiveCleaner();
// 2. 重新运行所有初始化逻辑
window.MyObserver = initObserver();
initCommentAppObserver();
checkForContentToHide();
}
// 导航观察器 ---
function setupNavigationObserver() {
const observer = new MutationObserver(() => {
const currentPath = window.location.pathname.replace(/\/$/, '');
const lastPath = lastPathname.replace(/\/$/, '');
if (!lastPathname || currentPath !== lastPath) {
if (lastPathname) log(`检测到页面导航: ${lastPathname} -> ${window.location.pathname}`);
lastPathname = window.location.pathname;
debounce(reinitializeAllObservers, 1500)();
}
});
observer.observe(document.body, { childList: true, subtree: true });
log('✅ 主导航观察器已启动');
}
function injectBiliInterceptor() {
const interceptorLogic = `
(function() {
// 防重复注入
if (window.__biliInterceptorInjected) return;
window.__biliInterceptorInjected = true;
const originalFetch = window.fetch;
console.log('[BiliCleaner] 🚀 网络拦截器已加载');
const targetReplyUrl = '/x/v2/reply/wbi/main';
const targetDynUrl = '/x/polymer/web-dynamic/v1/feed/all';
const targetNavUrl = '/x/polymer/web-dynamic/v1/feed/nav';
const targetSpaceUrl = '/x/polymer/web-dynamic/v1/feed/space';
// 默认配置(所有拦截相关开关默认为 true)
const DEFAULT_INTERCEPTOR_SETTINGS = {
dynamic: { enable: true, sub: { goods: { enable: true }, charge: { enable: true }, reverse: { enable: true } } },
comment: { enable: true, sub: { adBlock: { enable: true } } }
};
const defaultConfig = {
keywordStr: '淘宝|京东|天猫|美团|外卖|补贴|密令|折扣|福利|专属|下单|运(费?)险|[领惠叠]券|[低特好底保降差性]价',
biliAdLinks: ['taobao.com', 'tb.cn', 'jd.com', 'pinduodilo.com','zhuanzhuan.com', 'mall.bilibili.com', 'gaoneng.bilibili.com'],
time:0,
};
let runtimeSettings = null;
let runtimeWhiteList = [];
let runtimeRegex = null;
const biliAdWordsConfig = JSON.parse(localStorage.getItem("localConfig")) || defaultConfig;
const keywordRegex = new RegExp(biliAdWordsConfig.keywordStr.replace(/\s+/g, ''), 'i');
const keywordRegexGlobal = new RegExp(biliAdWordsConfig.keywordStr.replace(/\s+/g, ''), 'gi');
function refreshRuntimeConfig() {
try {
// 从页面全局变量获取配置(由主脚本通过 syncConfigToPage 同步)
const pageConfig = window.__biliCleanerConfig || {};
const rawSettings = pageConfig.settings;
const rawWhiteList = pageConfig.whiteList;
const keywordPattern = pageConfig.keywords;
// 处理设置对象(确保结构完整)
if (rawSettings) {
// 深拷贝,避免后续修改影响原始对象
runtimeSettings = JSON.parse(JSON.stringify(rawSettings));
// 确保必要的子对象存在(防止用户配置结构不完整)
if (!runtimeSettings.dynamic) runtimeSettings.dynamic = { enable: true, sub: {} };
if (!runtimeSettings.dynamic.sub) runtimeSettings.dynamic.sub = {};
if (runtimeSettings.dynamic.sub.goods === undefined) runtimeSettings.dynamic.sub.goods = { enable: true };
if (runtimeSettings.dynamic.sub.charge === undefined) runtimeSettings.dynamic.sub.charge = { enable: true };
if (runtimeSettings.dynamic.sub.reverse === undefined) runtimeSettings.dynamic.sub.reverse = { enable: true };
if (!runtimeSettings.comment) runtimeSettings.comment = { enable: true, sub: {} };
if (!runtimeSettings.comment.sub) runtimeSettings.comment.sub = {};
if (runtimeSettings.comment.sub.adBlock === undefined) runtimeSettings.comment.sub.adBlock = { enable: true };
} else {
// 无配置时,使用默认配置(所有开关为 true)
runtimeSettings = JSON.parse(JSON.stringify(DEFAULT_INTERCEPTOR_SETTINGS));
}
// 处理白名单
runtimeWhiteList = Array.isArray(rawWhiteList) ? rawWhiteList : [];
// 处理关键词正则
if (keywordPattern && typeof keywordPattern === 'string') {
const cleaned = keywordPattern.replace(/\s+/g, '');
runtimeRegex = new RegExp(cleaned, 'gi');
} else {
runtimeRegex = null;
}
} catch (e) {
console.error('[BiliCleaner] refreshRuntimeConfig 失败:', e);
// 降级到默认配置
runtimeSettings = JSON.parse(JSON.stringify(DEFAULT_INTERCEPTOR_SETTINGS));
runtimeWhiteList = [];
runtimeRegex = null;
}
}
// 深度提取动态 item 中的所有文本内容(用于广告关键词匹配)
function extractAllTextFromItem(item) {
const textParts = [];
const modules = item.modules || {};
const dynamic = modules.module_dynamic || {};
// 1. 动态描述文本 (desc)
if (dynamic.desc?.text) {
textParts.push(dynamic.desc.text);
}
if (dynamic.desc?.rich_text_nodes) {
dynamic.desc.rich_text_nodes.forEach(node => {
if (node.text) textParts.push(node.text);
});
}
// 2. 主要内容的文本 (major)
const major = dynamic.major || {};
if (major.opus) {
// 专栏/图文
const opus = major.opus;
if (opus.title) textParts.push(opus.title);
if (opus.summary?.text) textParts.push(opus.summary.text);
if (opus.summary?.rich_text_nodes) {
opus.summary.rich_text_nodes.forEach(node => {
if (node.text) textParts.push(node.text);
});
}
}
if (major.archive) {
// 视频
if (major.archive.title) textParts.push(major.archive.title);
if (major.archive.desc) textParts.push(major.archive.desc);
}
if (major.article) {
// 旧版文章
if (major.article.title) textParts.push(major.article.title);
if (major.article.summary) textParts.push(major.article.summary);
}
// 3. 附加信息 (additional) 中的商品名称
const additional = dynamic.additional;
if (additional?.goods?.items) {
additional.goods.items.forEach(good => {
if (good.name) textParts.push(good.name);
if (good.brief) textParts.push(good.brief);
});
}
// 4. 原始动态中的原动态(转发内容) - 如果是转发,orig 字段可能存在
if (item.orig) {
const origText = extractAllTextFromItem(item.orig);
if (origText) textParts.push(origText);
}
// 合并所有文本,并过滤掉表情
const rawText = textParts.filter(t => t && typeof t === 'string').join(';');
const filteredText = rawText.replace(/\[[^\[\]\s]+\]/g, '');
return filteredText;
}
function getMatchedAdKeywords(text) {
const notAd = ['评论', '评论区', '产品'];
const matches = text.matchAll(runtimeRegex);
const keywordSet = new Set();
for (const match of matches) {
const word = match[0];
if (!notAd.includes(word)) {
keywordSet.add(word);
}
}
return Array.from(keywordSet);
}
function filterDynamic(json, settings, whiteList, regex) {
if (!json?.data?.items || !settings?.dynamic?.enable) return json;
const originalCount = json.data.items.length;
// 辅助函数:检查一个动态对象(item 或 orig)是否包含抽奖节点
function hasLotteryNode(dynamicObj) {
if (!dynamicObj) return false;
try {
const summary = dynamicObj.modules?.module_dynamic?.major?.opus?.summary;
if (summary && Array.isArray(summary.rich_text_nodes)) {
return summary.rich_text_nodes.some(node => node.type === 'RICH_TEXT_NODE_TYPE_LOTTERY');
}
} catch (e) {}
return false;
}
// 判断整个动态(含转发)是否为抽奖相关
function isLotteryDynamic(item) {
if (hasLotteryNode(item)) return true;
if (item.orig && hasLotteryNode(item.orig)) return true;
return false;
}
const enableGoods = !(settings.dynamic.sub.goods?.enable === false);
const enableCharge = !(settings.dynamic.sub.charge?.enable === false);
const enableReverse = !(settings.dynamic.sub.reverse?.enable === false);
json.data.items = json.data.items.filter(item => {
const authorName = item.modules?.module_author?.name || '未知用户';
if (authorName && whiteList?.includes(authorName)) return true;
const pubTime = item.modules?.module_author?.pub_time || '';
const jump_url = 'https:' + (item.basic?.jump_url || item.modules?.module_dynamic?.major?.archive?.jump_url || item.orig?.basic?.jump_url)
if (isLotteryDynamic(item)) {
console.log('BiliCleaner] 🎁 动态放行-抽奖', authorName, jump_url);
return true;
}
// 1. 结构化拦截:商品、预约、充电专属
const dyn = item.modules?.module_dynamic;
const addType = dyn?.additional?.type;
if (enableGoods) {
if (addType === "ADDITIONAL_TYPE_GOODS") {
console.log('[BiliCleaner] 🚫 网络拦截-商品推广', authorName, pubTime, jump_url);
return false;
}
}
if (enableReverse) {
if (addType === "ADDITIONAL_TYPE_RESERVE") {
console.log('[BiliCleaner] 🚫 网络拦截-预约动态', authorName, pubTime, jump_url);
return false;
}
}
if (enableCharge) {
if (item.type === 'DYNAMIC_TYPE_COMMON_SQUARE' ||
dyn?.major?.type === "MAJOR_TYPE_BLOCKED" ||
item.basic?.is_only_fans ||
dyn?.major?.archive?.badge?.text === "充电专属") {
const title = dyn?.major?.archive?.title || ''
console.log('[BiliCleaner] 🚫 网络拦截-充电专属:', authorName, pubTime, title, jump_url);
return false;
}
}
// 2. 文本关键词过滤(仅当商品推广开关启用时)
if (enableGoods && regex) {
const allText = extractAllTextFromItem(item);
if (allText) {
const matches = getMatchedAdKeywords(allText);
if (matches && matches.length >= 2) {
const pubTime = item.modules?.module_author?.pub_time || '';
console.log('[BiliCleaner] 🚫 网络拦截-关键词命中:' , authorName , pubTime, '->', allText.slice(0, 80)+ ' ... ', jump_url);
console.log('[BiliCleaner] 🎯 匹配关键词:' , matches);
return false;
}
}
}
return true;
});
const blockedCount = originalCount - json.data.items.length;
if (blockedCount > 0) console.log('[BiliCleaner] 🧹 动态流已净化: 移除 ' + blockedCount + ' 条广告');
return json;
}
function filterReply(json, settings, whiteList, keywordRegex) {
if (!json?.data || !settings?.comment?.sub?.adBlock?.enable) return json;
const checkAd = (r, source) => {
if (!r?.content) return false;
const u = r.member?.uname || '未知用户';
if (whiteList.includes(u)) return false;
if (r.content.jump_url && Object.keys(r.content.jump_url).length > 0) {
console.log('[BiliCleaner] 🚫 网络拦截-置顶带货(' + source + '):', u);
return true;
}
if (keywordRegex && r.content.message?.match(keywordRegex)) {
const isReal = r.content.message.match(keywordRegex).some(m => !['评论','评论区','产品'].includes(m));
if (isReal) {
console.log('[BiliCleaner] 🚫 网络拦截-置顶关键词(' + source + '):', u);
return true;
}
}
return false;
};
// 清洗 data.top.upper
if (json.data.top?.upper && checkAd(json.data.top.upper, 'upper')) {
json.data.top.upper = null;
}
// 清洗 data.top_replies
if (Array.isArray(json.data.top_replies)) {
json.data.top_replies = json.data.top_replies.filter(r => !checkAd(r, 'top_replies'));
}
return json;
}
function filterNav(json, settings, whiteList, keywordRegex) {
let items = json?.data?.items ?? json?.items;
if (!items) return json;
const goodsEnabled = settings?.dynamic?.sub?.goods?.enable !== false;
const popupEnabled = settings?.dynamic?.sub?.popup?.enable !== false;
if (!goodsEnabled || !popupEnabled) {
console.log('[BiliCleaner] ⏭️ 导航动态过滤跳过: goods=%o, popup=%o', goodsEnabled, popupEnabled);
return json;
}
const originalCount = json.data.items.length;
json.data.items = json.data.items.filter(item => {
const authorName = item.author?.name;
if (authorName && whiteList.includes(authorName)) return true;
if (item.type === 64) { // 核心:过滤 type === 64 的广告项
console.log('[BiliCleaner] 🚫 网络拦截-导航动态广告:', authorName, '->', item.title);
return false;
}
return true;
});
const blockedCount = originalCount - json.data.items.length;
if (blockedCount > 0) {
if (json.data.items.length === 0) {
json.data.has_more = false;
json.data.offset = null;
}
}
return json;
}
// 3. fetch 拦截逻辑
window.fetch = async function(...args) {
const url = (typeof args[0] === 'string') ? args[0] : args[0].url;
// 刷新配置的条件
const urlMatched = url && (url.includes(targetDynUrl) || url.includes(targetSpaceUrl) || url.includes(targetReplyUrl) || url.includes(targetNavUrl))
if (urlMatched) {
refreshRuntimeConfig();
}
const response = await originalFetch.apply(this, args);
if (urlMatched) {
try {
const clone = response.clone();
let json = await clone.json();
if (url.includes(targetDynUrl) || url.includes(targetSpaceUrl)) {
json = filterDynamic(json, runtimeSettings, runtimeWhiteList, runtimeRegex);
} else if (url.includes(targetNavUrl)) {
json = filterNav(json, runtimeSettings, runtimeWhiteList, runtimeRegex);
} else {
json = filterReply(json, runtimeSettings, runtimeWhiteList, runtimeRegex);
}
return new Response(JSON.stringify(json), {
status: response.status,
statusText: response.statusText,
headers: response.headers
});
} catch (e) {
return response;
}
}
return response;
};
})();
`;
const script = document.createElement('script');
script.textContent = interceptorLogic;
(document.head || document.documentElement).appendChild(script);
script.remove();
}
function syncConfigToPage() {
const config = {
settings: userSettings,
whiteList: whiteList,
keywords: biliAdWordsConfig?.keywordStr || null
};
const script = document.createElement('script');
script.textContent = `window.__biliCleanerConfig = ${JSON.stringify(config)};`;
(document.head || document.documentElement).appendChild(script);
script.remove();
}
async function initApp() {
log('脚本加载,初始化');
await getAdWordsConfig();
prepareForWork();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
reinitializeAllObservers();
setupNavigationObserver();
});
} else {
reinitializeAllObservers();
setupNavigationObserver();
}
}
injectBiliInterceptor();
initApp().catch(console.error);
})();