您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
屏蔽首页被AdGuard/AdBlock等插件部分屏蔽后留下的异常视频卡片,支持完全删除
// ==UserScript== // @name 广告视频卡片提示屏蔽器 // @namespace http://tampermonkey.net/ // @version 2.0.0 // @license MIT // @description 屏蔽首页被AdGuard/AdBlock等插件部分屏蔽后留下的异常视频卡片,支持完全删除 // @author PPPotatooo // @match https://www.bilibili.com/ // @match https://www.bilibili.com/?* // @grant none // @run-at document-start // ==/UserScript== (function() { 'use strict'; let removedCount = 0; const DEBUG = true; // 设置为false可关闭调试输出 function log(message) { if (DEBUG) { console.log(`[B站广告屏蔽] ${message}`); } } // 检测是否为可疑的随机类名 function isSuspiciousClassName(className) { if (!className || typeof className !== 'string') return false; // 定义可疑随机类名的正则模式 const suspiciousPatterns = [ /^[a-z]+\d+[a-z]*$/, // 字母+数字+字母 如: bi4hudfdh2jw4, abc123def /^[a-z]\d+[a-z]+$/, // 单字母+数字+字母 如: b8ernjwo, a1bcdef /^[a-z]{1,3}\d{1,4}[a-z]{1,6}$/, // 1-3字母+1-4数字+1-6字母 /^[a-z]{2,8}\d{1,3}$/, // 2-8字母+1-3数字 /^[a-z]\d[a-z]{2,8}$/, // 1字母+1数字+2-8字母 /^[a-z]{3,}\d+$/, // 3+字母+数字 /^[a-z]+\d{2,}[a-z]+$/, // 字母+2+数字+字母 ]; // 排除正常的类名(以已知前缀开头的) const normalPrefixes = [ 'bili-', 'feed-', 'shortcut-', 'v-', 'no-interest-', 'revert-', 'watch-', 'video-', 'info-', 'stats-', 'image-', 'cover-', 'mask-', 'wrap', 'card-', 'btn-' ]; // 如果是正常类名,直接返回false for (let prefix of normalPrefixes) { if (className.startsWith(prefix)) { return false; } } // 检查是否匹配可疑模式 return suspiciousPatterns.some(pattern => pattern.test(className)); } // 检测元素是否包含可疑的随机类名 function hasSuspiciousClassName(element) { // 检查元素本身的类名 if (element.classList) { for (let className of element.classList) { if (isSuspiciousClassName(className)) { log(`发现可疑类名: ${className}`); return true; } } } // 检查子元素的类名 const allElements = element.querySelectorAll('*'); for (let el of allElements) { if (el.classList) { for (let className of el.classList) { if (isSuspiciousClassName(className)) { log(`发现子元素可疑类名: ${className}`); return true; } } } } return false; } // 检测是否为异常的广告卡片 function isAdCard(card) { try { // 检查1: 检测可疑的随机类名 if (hasSuspiciousClassName(card)) { return true; } // 检查2: 验证是否缺少正常视频卡片的核心内容 const hasTitle = card.querySelector('.bili-video-card__info--tit'); const hasCover = card.querySelector('.bili-video-card__cover, .bili-video-card__image'); const hasAuthor = card.querySelector('.bili-video-card__info--author'); const hasVideoLink = card.querySelector('a[href*="/video/"]'); // 如果是bili-video-card但缺少关键内容,判定为异常 const videoCard = card.querySelector('.bili-video-card'); if (videoCard && (!hasTitle || !hasCover || !hasVideoLink)) { log('发现缺少核心内容的视频卡片'); return true; } // 检查3: 检测内容异常简单的卡片 const cardHTML = card.innerHTML; if (cardHTML.length < 500 && card.querySelector('.bili-video-card')) { log('发现内容异常简单的视频卡片'); return true; } // 检查4: 检测只有空的bili-video-card的情况 if (videoCard) { const videoCardContent = videoCard.innerHTML.replace(/<!--.*?-->/g, '').trim(); if (videoCardContent.length < 100) { log('发现几乎为空的视频卡片'); return true; } } // 检查5: 检测被标记为已屏蔽的卡片 if (card.dataset.adBlocked === 'true' || card.style.display === 'none') { log('发现已被标记为屏蔽的卡片'); return true; } } catch (error) { log(`检测过程中出现错误: ${error.message}`); } return false; } // 获取应该删除的目标元素(优先删除外层feed-card) function getTargetElementToRemove(element) { // 如果当前元素有外层的feed-card,删除外层 const outerFeedCard = element.closest('.feed-card'); if (outerFeedCard) { return outerFeedCard; } // 如果当前元素本身是feed-card,删除自己 if (element.classList.contains('feed-card')) { return element; } // 如果是bili-feed-card,删除自己 if (element.classList.contains('bili-feed-card')) { return element; } // 默认返回元素本身 return element; } // 移除广告卡片 function removeAdCards() { // 查找所有可能的卡片容器 const allCards = [ ...document.querySelectorAll('.feed-card'), ...document.querySelectorAll('.bili-feed-card') ]; let currentRemovedCount = 0; const processedElements = new Set(); allCards.forEach((card, index) => { // 避免重复处理同一个元素 if (processedElements.has(card)) { return; } if (isAdCard(card)) { const targetElement = getTargetElementToRemove(card); // 避免删除已经处理过的元素 if (!processedElements.has(targetElement) && targetElement.parentNode) { processedElements.add(targetElement); processedElements.add(card); // 完全删除元素 targetElement.remove(); currentRemovedCount++; removedCount++; log(`已删除第 ${removedCount} 个广告卡片 (${targetElement.className})`); } } processedElements.add(card); }); if (currentRemovedCount > 0) { log(`本次清理删除了 ${currentRemovedCount} 个广告卡片`); } } // 监听DOM变化 function setupMutationObserver() { const observer = new MutationObserver((mutations) => { let shouldCheck = false; mutations.forEach((mutation) => { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { // 检查是否有新的feed卡片添加 mutation.addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { if (node.classList && ( node.classList.contains('bili-feed-card') || node.classList.contains('feed-card') )) { shouldCheck = true; } else if (node.querySelector && ( node.querySelector('.bili-feed-card') || node.querySelector('.feed-card') )) { shouldCheck = true; } } }); } }); if (shouldCheck) { // 延迟执行,确保内容完全加载 setTimeout(removeAdCards, 100); } }); // 开始观察 observer.observe(document.body, { childList: true, subtree: true }); log('DOM变化监听器已启动'); } // 定期检查(备用机制) function setupPeriodicCheck() { setInterval(() => { removeAdCards(); }, 3000); // 每3秒检查一次 log('定期检查机制已启动'); } // 初始化 function init() { log('B站广告屏蔽插件启动 v2.0'); // 等待页面基本加载完成 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { setTimeout(init, 1000); }); return; } // 立即执行一次清理 setTimeout(removeAdCards, 1000); // 设置监听器 setTimeout(setupMutationObserver, 1500); // 设置定期检查 setTimeout(setupPeriodicCheck, 2000); // 页面滚动时也触发检查 let scrollTimeout; window.addEventListener('scroll', () => { clearTimeout(scrollTimeout); scrollTimeout = setTimeout(removeAdCards, 500); }); log('所有监听机制已设置完成'); } // 添加手动触发按钮(可选,便于调试) function addDebugButton() { if (!DEBUG) return; setTimeout(() => { const button = document.createElement('button'); button.textContent = `清理广告 (${removedCount})`; button.style.cssText = ` position: fixed; top: 10px; right: 10px; z-index: 9999; padding: 8px 12px; background: #ff6b9d; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; box-shadow: 0 2px 6px rgba(0,0,0,0.2); `; button.addEventListener('click', () => { removeAdCards(); button.textContent = `清理广告 (${removedCount})`; }); document.body.appendChild(button); // 定时更新按钮显示 setInterval(() => { button.textContent = `清理广告 (${removedCount})`; }, 1000); }, 3000); } // 测试随机类名检测函数(调试用) function testSuspiciousClassNames() { if (!DEBUG) return; const testCases = [ 'bi4hudfdh2jw4', // 应该被检测到 'b8ernjwo', // 应该被检测到 'abc123def', // 应该被检测到 'bili-video-card', // 不应该被检测到 'feed-card', // 不应该被检测到 'a1b2c3', // 应该被检测到 'shortcut-bg', // 不应该被检测到 'xyz789', // 应该被检测到 ]; log('测试随机类名检测:'); testCases.forEach(className => { const result = isSuspiciousClassName(className); log(`${className}: ${result ? '可疑' : '正常'}`); }); } // 启动插件 init(); // 添加调试按钮 addDebugButton(); // 测试函数 testSuspiciousClassNames(); log('插件加载完成 v2.0'); })();