// ==UserScript== // @name Bilibili 去掉搜索无关视频 // @namespace http://tampermonkey.net/ // @version 2.0.0 // @description 自动隐藏 Bilibili 搜索结果中不包含关键词的无关视频,支持 @UP主 定向筛选、-排除词 与 #Tag 专项筛选,彻底净化搜索体验。 // @author Kirosca // @match *://search.bilibili.com/* // @run-at document-start // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; // 1. 预注入 CSS 规则:秒杀视频流中的商业推广与小火箭广告卡片 const styleElement = document.createElement('style'); styleElement.textContent = ` div[class*="col_"]:has(.bili-video-card__info--ad, svg.bili-video-card__info--ad-creative, a[href*="cm.bilibili.com"]), .video-list-item:has(.bili-video-card__info--ad, svg.bili-video-card__info--ad-creative, a[href*="cm.bilibili.com"]) { display: none !important; } `; document.documentElement.appendChild(styleElement); // 2. 解析当前 URL 中的关键词分类 (普通词 / -排除词 / #Tag词 / @UP主词) function getSearchKeywords() { const params = new URLSearchParams(window.location.search); const rawParam = params.get('keyword') || ''; const rawKeyword = decodeURIComponent(rawParam.replace(/\+/g, ' ')).trim().toLowerCase(); if (!rawKeyword) return { normal: [], exclude: [], tags: [], ups: [] }; const list = rawKeyword.split(/\s+/); const normal = []; const exclude = []; const tags = []; const ups = []; for (const k of list) { if (k.startsWith('-') && k.length > 1) { exclude.push(k.substring(1)); } else if (k.startsWith('#') && k.length > 1) { tags.push(k.substring(1)); } else if (k.startsWith('@') && k.length > 1) { ups.push(k.substring(1)); } else if (k) { normal.push(k); } } return { normal, exclude, tags, ups }; } // 3. 提取发给 B 站接口的搜索词(🌟 智能脱壳:剔除 - 排除词,剥离 @ 与 # 前缀发给 B 站服务器以获取最大候选池) function getCleanApiKeyword() { const params = new URLSearchParams(window.location.search); const rawParam = params.get('keyword') || ''; const rawKeyword = decodeURIComponent(rawParam.replace(/\+/g, ' ')).trim(); if (!rawKeyword) return ''; const list = rawKeyword.split(/\s+/); const searchTerms = []; for (const k of list) { if (k.startsWith('-')) { continue; // 排除词不发 } else if (k.startsWith('@') || k.startsWith('#')) { if (k.length > 1) searchTerms.push(k.substring(1)); // 🌟 脱壳去除符号前缀发送 } else if (k) { searchTerms.push(k); } } return searchTerms.join(' '); } // 4. 网络层拦截:存储 Tag 字典 + 接口层物理剔除广告 const videoTagMap = new Map(); function cleanAdsOnly(list) { if (!Array.isArray(list)) return list; return list.filter(v => { if (!v || typeof v !== 'object') return true; const id = v.bvid || v.aid || v.id || v.season_id || v.roomid; const rawTags = (v.tag || v.keywords || v.tags || '').toLowerCase(); if (id) videoTagMap.set(String(id), rawTags); if (v.is_ad_loc || v.is_promoted || v.goto === 'ad' || v.type === 'ad') return false; return true; }); } const originFetch = window.fetch; window.fetch = async function(...args) { let urlStr = ''; if (typeof args[0] === 'string') { urlStr = args[0]; } else if (args[0] && typeof args[0] === 'object') { urlStr = args[0].url || ''; } const isSearchReq = urlStr.includes('/wbi/search') || urlStr.includes('/search/type') || urlStr.includes('/search/all'); if (isSearchReq) { const apiKeyword = getCleanApiKeyword(); if (apiKeyword) { urlStr = urlStr.replace(/([?&]keyword=)[^&]+/, `$1${encodeURIComponent(apiKeyword)}`); if (typeof args[0] === 'string') { args[0] = urlStr; } else if (args[0] && typeof args[0] === 'object') { args[0] = new Request(urlStr, args[0]); } } } const response = await originFetch.apply(this, args); if (!isSearchReq) return response; try { const clone = response.clone(); const data = await clone.json(); if (data?.data?.result) { if (Array.isArray(data.data.result)) { if (data.data.result.length > 0 && data.data.result[0].data && Array.isArray(data.data.result[0].data)) { for (const cat of data.data.result) { if (cat.result_type === 'video' || cat.result_type === 'live_room' || !cat.result_type) { cat.data = cleanAdsOnly(cat.data); } } } else { data.data.result = cleanAdsOnly(data.data.result); } } return new Response(JSON.stringify(data), { status: response.status, statusText: response.statusText, headers: response.headers }); } } catch (_) {} return response; }; // 5. DOM 层执行安检:覆盖【普通视频 / 课堂课程 / 直播间】 let isProcessing = false; function filterDOMElements() { if (isProcessing) return; isProcessing = true; try { const { normal, exclude, tags, ups } = getSearchKeywords(); if (!normal.length && !exclude.length && !tags.length && !ups.length) return; const elements = document.querySelectorAll('.bili-video-card, .video-list-item, div[class*="col_"], .bili-live-card'); const processedCards = new Set(); elements.forEach(el => { const card = el.closest('[class*="col_"], .video-list-item, .bili-live-card') || el; if (processedCards.has(card)) return; const linkEl = card.querySelector('a[href*="/video/"], a[href*="/cheese/"], a[href*="live.bilibili.com"], a[href*="/live/"]'); if (!linkEl) return; processedCards.add(card); const titleEl = card.querySelector('h3.bili-video-card__info--tit, h3.bili-live-card__info--tit, a[title], .bili-video-card__info--tit, h3'); if (!titleEl) return; const title = (titleEl.getAttribute('title') || titleEl.textContent || '').trim().toLowerCase(); const link = linkEl.href || ''; let id = ''; const matchVideo = link.match(/\/(?:video|play)\/([A-Za-z0-9]+)/); const matchLive = link.match(/live\.bilibili\.com\/([0-9]+)/); if (matchVideo) id = matchVideo[1]; else if (matchLive) id = matchLive[1]; const videoTags = (videoTagMap.get(id) || '').toLowerCase(); // 纯净提取作者名字并彻底剥离日期(如 "· 2-28"、"· 2天前") let rawAuthor = ''; const authorAnchor = card.querySelector('a[href*="space.bilibili.com"], .bili-video-card__info--owner, .up-name, .bili-live-card__info--uname'); if (authorAnchor) { const authorSpan = authorAnchor.querySelector('.bili-video-card__info--author, .bili-live-card__info--uname'); rawAuthor = authorSpan?.getAttribute('title') || authorSpan?.textContent || authorAnchor.getAttribute('title') || authorAnchor.textContent || ''; } else { const authorFallback = card.querySelector('span.bili-video-card__info--author, .bili-video-card__info--uname'); rawAuthor = authorFallback?.getAttribute('title') || authorFallback?.textContent || ''; } const author = rawAuthor.replace(/[\s·•].*$/, '').trim().toLowerCase(); let shouldHide = false; // 四道安检关卡 if (exclude.some(k => title.includes(k))) { shouldHide = true; } else if (normal.length && !normal.every(k => title.includes(k))) { shouldHide = true; } else if (tags.length && !tags.every(k => videoTags.includes(k))) { shouldHide = true; } else if (ups.length && !ups.some(k => author.includes(k))) { shouldHide = true; } if (shouldHide) { card.style.setProperty('display', 'none', 'important'); } else { card.style.removeProperty('display'); } }); } finally { isProcessing = false; } } // 6. 帧级节流监听:使用 requestAnimationFrame 防抖,CPU 占用 0% let rafId = null; const observer = new MutationObserver(() => { if (rafId) cancelAnimationFrame(rafId); rafId = requestAnimationFrame(filterDOMElements); }); observer.observe(document.documentElement, { childList: true, subtree: true }); console.log('[Bilibili 搜索净化] 2.0.0 智能脱壳召回版已启动。'); })();