// ==UserScript== // @name Bilibili-BlackList // @namespace https://github.com/HeavenTTT/bilibili-blacklist // @version 1.1.5 // @author HeavenTTT // @description Bilibili UP屏蔽插件 - 屏蔽UP主视频卡片,支持精确匹配和正则匹配,支持视频页面、分类页面、搜索页面等。 // @match *://*.bilibili.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @icon https://www.bilibili.com/favicon.ico // @require https://update.greasyfork.icu/scripts/533940/Bilibili-BlackList.user.js // @license MIT // @downloadURL none // ==/UserScript== (function () { "use strict"; /* * Bilibili-BlackList -- Bilibili UP屏蔽插件 * 脚本大部分代码由AI生成,作者一点都不懂JavaScript,出现bug请联系Gemini / ChatGPT / DeepSeek * this script is mainly generated by AI, the author doesn't know JavaScript at all, if there are bugs, please contact Gemini / ChatGPT / DeepSeek * 感谢你的使用 * Thank you for using this script * * 本段注释为VS code 自动生成 this is a comment generated by VS code */ // 从存储中获取黑名单 // 默认精确匹配黑名单(区分大小写) let exactBlacklist = GM_getValue("exactBlacklist", [ "绝区零", "崩坏星穹铁道", "崩坏3", "原神", "米哈游miHoYo", ]); // 默认正则匹配黑名单 let regexBlacklist = GM_getValue("regexBlacklist", [ "王者荣耀", "和平精英", "PUBG", "绝地求生", "吃鸡", ]); // 标签屏蔽黑名单 let tNameBlacklist = GM_getValue("tNameBlacklist", ["手机游戏"]); // let globalConfig = { // flagInfo: true, // flagAD: true, // flagTName : true, // flagCM: true, // processQueueInterval: 500, // 单位 ms // }; let globalConfig = GM_getValue("globalConfig", { flagInfo: true, flagAD: true, flagTName: true, flagCM: true, processQueueInterval: 500, // 单位 ms }); // 保存黑名单到存储 function saveBlacklists() { GM_setValue("exactBlacklist", exactBlacklist); GM_setValue("regexBlacklist", regexBlacklist); GM_setValue("tNameBlacklist", tNameBlacklist); } function saveGlobalConfig() { GM_setValue("globalConfig", globalConfig); } //开发用日志,bug:运行该函数后,脚本才能正常工作,原因未知,本插件依赖此bug运行,请勿删除任何引用 function Devlog(...args) { if (GM_info.script.version === "Dev") console.log("[Devlog]", ...args); } //#region 核心功能 - 屏蔽视频卡片 let isShowAll = false; // 是否显示全部视频卡片 let isBlocking = false; // 是否正在执行屏蔽操作 let lastBlockTime = 0; // 上次执行屏蔽的时间戳 let blockedCards = new Set(); // 存储已屏蔽的视频卡片元素 let processedCards = new WeakSet(); // 记录已处理过的卡片(避免重复处理) //给卡片添加屏蔽按钮 function cardAddBlockcContainer(upName, card) { if (!card.querySelector("bilibili-blacklist-block-container")) { const container = document.createElement("div"); container.classList.add("bilibili-blacklist-block-container"); if (!card.querySelector(".bilibili-blacklist-block-btn")) { // 创建屏蔽按钮 if (isVideoPage()) { // 如果是视频页面 if (isInit) { const blockButton = createBlockButton(upName, card); card.querySelector(".card-box").style.position = "relative"; card.querySelector(".card-box").appendChild(container); container.appendChild(blockButton); } } else if (isCategoryPage()) { // 如果是分类页面 const blockButton = createBlockButton(upName, card); // 创建屏蔽按钮 card.querySelector(".bili-video-card").appendChild(container); // 将按钮添加到卡片中信息中 container.appendChild(blockButton); // 将按钮添加到容器中 } else { const blockButton = createBlockButton(upName, card); // 创建屏蔽按钮 card.appendChild(container); // 将按钮添加到卡片中 container.appendChild(blockButton); // 将按钮添加到容器中 } } } } //隐藏卡片 function hideCard(card) { if (isSearchPage()) { // 如果是搜索页面 -> 隐藏父元素 card = card.parentElement; // 获取父元素 } if (isMainPage()) { // 如果是主页 if (card.parentElement.classList.contains("feed-card")) { // 如果父元素是feed-card card = card.parentElement; // 获取父元素 } } if (!blockedCards.has(card)) { blockedCards.add(card); // 将卡片添加到已屏蔽列表 } if (!isShowAll) { card.style.display = "none"; // 隐藏卡片 } } /// 查找所有视频卡片 function querySelectorAllVideoCard(selector) { return document.querySelectorAll(selector); } /// 屏蔽视频卡片 function BlockCard() { const now = Date.now(); if (isBlocking || now - lastBlockTime < 1000) { return; } isBlocking = true; lastBlockTime = now; try { let cards = null; if (isMainPage()) { cards = querySelectorAllVideoCard(".bili-video-card"); } else if (isVideoPage()) { cards = querySelectorAllVideoCard(".video-page-card-small"); } else if (isCategoryPage()) { cards = querySelectorAllVideoCard(".feed-card"); } else if (isSearchPage()) { cards = querySelectorAllVideoCard(".bili-video-card"); } else return; // 如果不是视频页面,则不执行屏蔽操作 cards.forEach((card) => { addButtontTNameQueue(card); if (processedCards.has(card)) { return; // 如果卡片已经处理过,则跳过 } // 获取视频信息 const { upName, title } = GetVideoInfo(card); if (upName && title) { processedCards.add(card); // 将卡片标记为已处理 cardAddBlockcContainer(upName, card); // 添加屏蔽按钮 // 检查是否在黑名单中 if (isBlacklisted(upName, title) && globalConfig.flagInfo) { // 如果在黑名单中,则隐藏卡片 hideCard(card); } if (isCardBlacklistTName(card) && globalConfig.flagTName) { hideCard(card); } } else { //console.warn("未找到UP主名称或视频标题,跳过屏蔽:", card); } }); refreshBlockCountDisplay(); FixedMainPage(); // 修正主页的错位问题 } finally { isBlocking = false; // 重置屏蔽状态 } } //修正主页的错位问题 function FixedMainPage() { if (!isMainPage()) return; // 仅在主页执行 const container = document.querySelector( ".recommended-container_floor-aside .container" // 推荐视频容器 ); // 检查父元素是否存在 if (container) { const children = container.children; // 这是一个 HTMLCollection let visableindex = 0; // 可见子元素的索引 for (let i = 0; i < children.length; i++) { const child = children[i]; if (child.style.display !== "none") { if (visableindex <= 6) { child.style.marginTop = "0px"; } else if (visableindex < 12) { child.style.marginTop = "24px"; } else { break; // 如果可见子元素超过10个,则停止处理 } visableindex++; // 统计可见子元素的数量 } } } } // 更新屏蔽计数显示 function refreshBlockCountDisplay() { if (blockCountDiv) { blockCountDiv.textContent = `${blockedCards.size}`; // 更新右侧导航栏的屏蔽计数 } // 更新面板标题(如果面板已打开) //const panel = document.getElementById("bilibili-blacklist-panel"); if (blockTitle) { blockTitle.textContent = `已屏蔽视频 (${blockedCards.size})`; // 更新面板标题 } } // 暂时取消屏蔽/恢复屏蔽功能 function toggleShowAll() { isShowAll = !isShowAll; blockedCards.forEach((card) => { card.style.display = isShowAll ? "block" : "none"; }); btnTempUnblock.textContent = isShowAll ? "恢复屏蔽" : "取消屏蔽"; btnTempUnblock.style.background = isShowAll ? "#dddddd" : "#fb7299"; // 不需要更新blockCount,因为总数没有变化 } const selectorUpName = [ ".bili-video-card__info--author", // 主页 ".bili-video-card__author", // 分类页面--> span title ".name", // 播放页面 ]; const selectorTitle = [ ".bili-video-card__info--tit", // 主页 ".bili-video-card__title", // 分类页面--> span title ".title", // 播放页面 ]; //获取视频信息 -UP主名称 -视频标题 function GetVideoInfo(card) { let upName = ""; let title = ""; if (card.style.display === "none") return { upName, title }; // 如果卡片已被隐藏,则直接返回空信息 const upNameElement = card.querySelectorAll(selectorUpName.join(", ")); // 使用逗号分隔的选择器 if (upNameElement.length > 0) { upName = upNameElement[0].textContent.trim(); // 获取第一个匹配到的元素的内容,并去除首尾空格 //处理分类页面的UP主名称 if (isCategoryPage()) { upName = upName.split(" · ")[0].trim(); } } const titleElement = card.querySelectorAll(selectorTitle.join(", ")); // 使用逗号分隔的选择器 if (titleElement.length > 0) { title = titleElement[0].textContent.trim(); // 获取第一个匹配到的元素的内容,并去除首尾空格 } return { upName, title }; } function isBlacklisted(upName, title) { // 精确匹配黑名单(无视大小写) // 将 upName 转换为小写,然后与黑名单中的每个项的小写进行比较 const lowerCaseUpName = upName.toLowerCase(); if (exactBlacklist.some((item) => item.toLowerCase() === lowerCaseUpName)) { return true; } // 正则匹配黑名单(无视大小写) // 为正则表达式添加 'i' 标志,表示忽略大小写 if (regexBlacklist.some((regex) => new RegExp(regex, "i").test(upName))) { return true; } if (regexBlacklist.some((regex) => new RegExp(regex, "i").test(title))) { return true; } return false; // 不在黑名单中 } /// 添加UP主到精确黑名单并刷新页面 function addToExactBlacklist(upName, cardElement = null) { try { if (!upName) return; if (!exactBlacklist.includes(upName)) { exactBlacklist.push(upName); saveBlacklists(); refreshAllTabs(); if (cardElement) { hideCard(cardElement); // 隐藏当前卡片 } } } catch (e) { console.error("[bilibili-blacklist] 添加黑名单出错:", e); } } function removeFromExactBlacklist(upName) { try { if (exactBlacklist.includes(upName)) { const index = exactBlacklist.indexOf(upName); exactBlacklist.splice(index, 1); saveBlacklists(); refreshExactList(); } } catch (e) { console.error("[bilibili-blacklist] 移除黑名单出错:", e); } finally { //BlockCard(); } } function addToTNameBlacklist(tname, cardElement = null) { try { if (!tname) { return; } if (!tNameBlacklist.includes(tname)) { tNameBlacklist.push(tname); saveBlacklists(); refreshAllTabs(); if (cardElement) { hideCard(cardElement); //隐藏卡片 } } } catch (e) { console.error("[bilibili-blacklist] 添加标签黑名单出错:", e); } } function removeFromTNameBlacklist(tname) { try { if (tNameBlacklist.includes(tname)) { const index = tNameBlacklist.indexOf(tname); tNameBlacklist.splice(index, 1); saveBlacklists(); refreshTagNameList(); } } catch (e) { console.error("[bilibili-blacklist] 移除标签黑名单出错:", e); } } //#endregion //#region Bv号以及视频信息 let cardSequenceGetJson = new Set(); // 存储卡片队列 let isProcessingCardQueue = false; // 是否正在处理队列 let isPageActive = true; // 页面是否可见 function getCardBv(card) { const bvElement = card.querySelector("a"); if (!bvElement) { return null; } try { const link = bvElement.getAttribute("href"); if (!link) { return null; } else { //判断链接是不是cm.bilili if (link.match(/cm.bilibili.com/) && globalConfig.flagCM) { hideCard(card); return null; } const bv = link.match(/BV\w+/); return bv[0]; } } catch (e) { return null; } } async function getBilibiliVideoAPI(bvid) { if (bvid.length >= 24) { return null; } const url = `https://api.bilibili.com/x/web-interface/view?bvid=${bvid}`; try { const response = await fetch(url); const json = await response.json(); if (json.code === 0) { return json.data; } else { return null; } } catch (error) { console.error("[bilibili-blacklist] API 请求失败:", error); } } function isCardBlacklistTName(card) { const tnameGroup = card.querySelector(".bilibili-blacklist-tname-group"); if (tnameGroup) { const tnameElements = tnameGroup.querySelectorAll( ".bilibili-blacklist-tname" ); for (const tnameElement of tnameElements) { const tname = tnameElement.textContent.trim(); if (tNameBlacklist.includes(tname)) { return true; } } } return false; } // 队列处理函数 let cardSequenceGetJsonDone = new Set(); // 存储卡片队列 async function processCardTNameQueue() { if (isProcessingCardQueue) return; isProcessingCardQueue = true; while (cardSequenceGetJson.size > 0) { if (!isPageActive) { await sleep(1000); // 每秒检查一次 continue; // 不处理当前卡片,重新判断 } const iterator = cardSequenceGetJson.values(); const card = iterator.next().value; cardSequenceGetJson.delete(card); if (!card) continue; if (cardSequenceGetJsonDone.has(card)) { //("卡片已处理过" + card.textContent); continue; } const bv = getCardBv(card); if (!bv) continue; const container = card.querySelector( ".bilibili-blacklist-block-container" ); if (!container) { //Devlog("未找到容器" + card.textContent); continue; } cardSequenceGetJsonDone.add(card); const data = await getBilibiliVideoAPI(bv); if (!data) { // Devlog("未找到数据" + card.textContent); cardSequenceGetJsonDone.remove(card); continue; } // 如果 card 已经处理过 tname group,跳过 // 最终确认 if (!card.querySelector(".bilibili-blacklist-tname-group")) { // 创建 tname group const tnameGroup = document.createElement("div"); tnameGroup.className = "bilibili-blacklist-tname-group"; let hasTname = false; // 添加一级 tname if (data.tname) { //Devlog(`处理 BV: ${bv} - 分类: ${data.tname}`); const btn = createTNameBlockButton(data.tname, card); tnameGroup.appendChild(btn); hasTname = true; } // 添加二级 tname_v2 if (data.tname_v2) { //Devlog(`处理 BV: ${bv} - 分类2: ${data.tname_v2}`); const tnameElement = createTNameBlockButton(data.tname_v2, card); tnameGroup.appendChild(tnameElement); hasTname = true; } // 只有有 tname 才 append group,避免插入空容器 if (hasTname) { container.appendChild(tnameGroup); } } await sleep(globalConfig.processQueueInterval || 100); } isProcessingCardQueue = false; } function addButtontTNameQueue(card) { if (!globalConfig.flagTName) return; const bv = getCardBv(card); if (!bv) return; // 检查是否已在处理中或已有标签组 if (card.querySelector(".bilibili-blacklist-tname-group")) { return; } // 检查是否已在队列中 if (cardSequenceGetJson.has(card)) { return; } cardSequenceGetJson.add(card); if (!isProcessingCardQueue) { processCardTNameQueue(); } } // function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } // 页面可见性监听 document.addEventListener("visibilitychange", () => { isPageActive = !document.hidden; //Devlog(`页面可见状态改变: ${isPageActive ? "活动" : "隐藏"}`); }); // 窗口焦点监听 window.addEventListener("focus", () => { isPageActive = true; //Devlog("窗口获得焦点"); }); window.addEventListener("blur", () => { isPageActive = false; //Devlog("窗口失去焦点"); }); //#endregion //#region 页面修改 //创建屏蔽按钮(悬停在视频卡片上时显示) function createBlockButton(upName, cardElement) { const btn = document.createElement("div"); btn.className = "bilibili-blacklist-block-btn"; btn.innerHTML = "屏蔽"; btn.title = `屏蔽: ${upName}`; // 屏蔽按钮样式 // 点击时添加到黑名单 btn.addEventListener("click", (e) => { e.stopPropagation(); // 防止事件冒泡 addToExactBlacklist(upName, cardElement); }); return btn; } function createTNameBlockButton(tName, cardElement) { const btn = document.createElement("span"); btn.className = "bilibili-blacklist-tname"; btn.innerHTML = `${tName}`; btn.title = `屏蔽: ${tName}`; // 屏蔽按钮样式 // 点击时添加到黑名单 btn.addEventListener("click", (e) => { e.stopPropagation(); // 防止事件冒泡 addToTNameBlacklist(tName, cardElement); }); return btn; } // 在右侧导航栏添加黑名单管理按钮 let blockCountDiv = null; function addBlacklistManagerButton() { if (isVideoPage()) { return; } const rightEntry = document.querySelector(".right-entry"); if (!rightEntry) { console.warn("[bilibili-blacklist] 未找到右侧导航栏"); return; } else if (!rightEntry.querySelector("#bilibili-blacklist-manager")) { //else if(rightEntry.getElementById('bilibili-blacklist-manager')){ const li = document.createElement("li"); li.id = "bilibili-blacklist-manager"; li.style.cursor = "pointer"; li.className = "v-popover-wrap"; const btn = document.createElement("div"); btn.className = "right-entry-item"; btn.style.display = "flex"; btn.style.flexDirection = "column"; btn.style.alignItems = "center"; btn.style.justifyContent = "center"; // 可爱的卡比图标SVG const icon = document.createElement("div"); icon.className = "right-entry__outside"; icon.innerHTML = getKirbySVG(); //icon.style.color = '#fb7299'; // B站粉色 icon.style.marginBottom = "-5px"; blockCountDiv = document.createElement("span"); //const text = document.createElement('div'); blockCountDiv.textContent = `0`; btn.appendChild(icon); btn.appendChild(blockCountDiv); li.appendChild(btn); // 在导航中插入按钮 if (rightEntry.children.length > 1) { rightEntry.insertBefore(li, rightEntry.children[1]); } else { rightEntry.appendChild(li); } // 点击按钮时显示面板 li.addEventListener("click", () => { if (managerPanel.style.display === "none") { managerPanel.style.display = "flex"; } else { managerPanel.style.display = "none"; } }); } } // 创建黑名单管理面板 let btnTempUnblock; let managerPanel; let exactList; //精确匹配列表 let regexList; //正则匹配列表 let tNameList; //tname匹配列表 let configList; //配置列表 let blockTitle; // 工具函数:创建按钮 function createButton(text, bgColor, onClick) { const button = document.createElement("button"); button.textContent = text; button.style.padding = "4px 8px"; button.style.background = bgColor; button.style.color = "#fff"; button.style.border = "none"; button.style.borderRadius = "4px"; button.style.cursor = "pointer"; button.addEventListener("click", onClick); return button; } // 工具函数:创建列表项 function createListItem(contentText, onRemoveClick, isRegex = false) { const item = document.createElement("li"); item.style.display = "flex"; item.style.justifyContent = "space-between"; item.style.alignItems = "center"; item.style.padding = "8px 0"; item.style.borderBottom = "1px solid #f1f2f3"; const content = document.createElement("span"); content.textContent = contentText; content.style.flex = "1"; if (isRegex) { content.style.fontFamily = "monospace"; } const removeBtn = createButton("移除", "#f56c6c", onRemoveClick); item.appendChild(content); item.appendChild(removeBtn); return item; } // 更新精确匹配列表 function refreshExactList() { if (!exactList) { if (!isBlacklistPanelCreated()) { return; } exactList = document.querySelector("#bilibili-blacklist-exact-list"); if (!exactList) { console.warn("[Bilibili-Blacklist] exactList 未定义"); return; } } exactList.innerHTML = ""; exactBlacklist.forEach((upName) => { const item = createListItem(upName, () => { removeFromExactBlacklist(upName); //BlockCard(); //更新屏蔽卡片 }); exactList.appendChild(item); }); Array.from(exactList.children) .reverse() .forEach((item) => exactList.appendChild(item)); if (exactBlacklist.length === 0) { const empty = document.createElement("div"); empty.textContent = "暂无精确匹配屏蔽UP主"; empty.style.textAlign = "center"; empty.style.padding = "16px"; empty.style.color = "#999"; exactList.appendChild(empty); } } // 更新正则匹配列表 function refreshRegexList() { if (!regexList) return; regexList.innerHTML = ""; regexBlacklist.forEach((regex, index) => { const item = createListItem( regex, () => { regexBlacklist.splice(index, 1); saveBlacklists(); refreshRegexList(); //BlockCard(); }, true ); regexList.appendChild(item); }); Array.from(regexList.children) .reverse() .forEach((item) => regexList.appendChild(item)); if (regexBlacklist.length === 0) { const empty = document.createElement("div"); empty.textContent = "暂无正则匹配屏蔽规则"; empty.style.textAlign = "center"; empty.style.padding = "16px"; empty.style.color = "#999"; regexList.appendChild(empty); } } // 更新tname匹配列表 function refreshTagNameList() { if (!tNameList) { if (!isBlacklistPanelCreated()) { return; } tNameList = document.querySelector("#bilibili-blacklist-tname-list"); if (!tNameList) { console.warn("[Bilibili-Blacklist] tNameList 未定义"); return; } } tNameList.innerHTML = ""; tNameBlacklist.forEach((tName) => { const item = createListItem(tName, () => { removeFromTNameBlacklist(tName); }); tNameList.appendChild(item); }); Array.from(tNameList.children) .reverse() .forEach((item) => tNameList.appendChild(item)); if (tNameBlacklist.length === 0) { const empty = document.createElement("div"); empty.textContent = "暂无标签屏蔽规则"; empty.style.textAlign = "center"; empty.style.padding = "16px"; empty.style.color = "#999"; tNameList.appendChild(empty); } } // 更新配置列表 function refreshConfigSettings() { if (!configList) return; // 清空 configContent configList.innerHTML = ""; //开关 const container = document.createElement("div"); container.style.display = "flex"; container.style.alignItems = "center"; container.style.marginBottom = "8px"; container.style.gap = "8px"; container.style.margin = "20px 0"; const label = document.createElement("span"); label.textContent = "临时开关"; label.style.flex = "1"; btnTempUnblock = document.createElement("button"); btnTempUnblock.textContent = isShowAll ? "恢复屏蔽" : "取消屏蔽"; btnTempUnblock.style.background = isShowAll ? "#dddddd" : "#fb7299"; btnTempUnblock.style.padding = "6px 12px"; btnTempUnblock.style.border = "none"; btnTempUnblock.style.cursor = "pointer"; btnTempUnblock.style.color = "#fff"; btnTempUnblock.addEventListener("click", toggleShowAll); container.appendChild(label); container.appendChild(btnTempUnblock); configList.appendChild(container); // 标题 const title = document.createElement("h4"); title.textContent = "全局配置开关(部分功能刷新后生效)"; title.style.fontWeight = "bold"; title.style.marginBottom = "12px"; configList.appendChild(title); // 工具函数:创建一个开关按钮 function createToggleButton(labelText, configKey, title = null) { const container = document.createElement("div"); container.style.display = "flex"; container.style.alignItems = "center"; container.style.marginBottom = "8px"; container.style.gap = "8px"; container.title = title; const label = document.createElement("span"); label.textContent = labelText; label.style.flex = "1"; const button = document.createElement("button"); button.style.padding = "6px 12px"; button.style.border = "none"; button.style.borderRadius = "4px"; button.style.cursor = "pointer"; button.style.color = "#fff"; function refreshButtonAppearance() { button.textContent = globalConfig[configKey] ? "开启" : "关闭"; button.style.backgroundColor = globalConfig[configKey] ? "#fb7299" : "#909399"; } button.addEventListener("click", () => { globalConfig[configKey] = !globalConfig[configKey]; refreshButtonAppearance(); saveGlobalConfig(); // 你可以实现此函数,将globalConfig存储到localStorage或其他 }); refreshButtonAppearance(); container.appendChild(label); container.appendChild(button); return container; } // 创建3个开关 configList.appendChild( createToggleButton("屏蔽标题/Up主名", "flagInfo", "屏蔽标题/Up主名") ); configList.appendChild( createToggleButton("屏蔽分类标签", "flagTName", "通过请求API获取分类标签") ); configList.appendChild( createToggleButton("屏蔽主页推荐", "flagAD", "直播/广告/分区推送") ); configList.appendChild( createToggleButton("屏蔽主页视频软广", "flagCM", "cm.bilibili.com软广") ); // 处理队列请求间隔 const intervalContainer = document.createElement("div"); intervalContainer.style.display = "flex"; intervalContainer.style.alignItems = "center"; intervalContainer.style.marginTop = "16px"; intervalContainer.style.gap = "8px"; intervalContainer.title = "请求API间隔时间,间隔时间越长,屏蔽越快,请求频繁可以会被临时ban,建议值 100ms"; const intervalLabel = document.createElement("span"); intervalLabel.textContent = "视频分类-处理队列请求间隔 (ms):"; intervalLabel.style.flex = "1"; const intervalInput = document.createElement("input"); intervalInput.type = "number"; intervalInput.min = "0"; intervalInput.value = globalConfig.processQueueInterval; intervalInput.style.width = "100px"; intervalInput.style.padding = "6px"; intervalInput.style.border = "1px solid #ddd"; intervalInput.style.borderRadius = "4px"; const saveIntervalBtn = document.createElement("button"); saveIntervalBtn.textContent = "保存"; saveIntervalBtn.style.padding = "6px 12px"; saveIntervalBtn.style.backgroundColor = "#fb7299"; saveIntervalBtn.style.color = "#fff"; saveIntervalBtn.style.border = "none"; saveIntervalBtn.style.borderRadius = "4px"; saveIntervalBtn.style.cursor = "pointer"; saveIntervalBtn.addEventListener("click", () => { const val = parseInt(intervalInput.value, 10); if (!isNaN(val) && val >= 0) { globalConfig.processQueueInterval = val; saveGlobalConfig(); // 保存配置 //alert("处理队列请求间隔已保存!"); } else { alert("请输入有效的非负数字!"); } }); intervalContainer.appendChild(intervalLabel); intervalContainer.appendChild(intervalInput); intervalContainer.appendChild(saveIntervalBtn); configList.appendChild(intervalContainer); return configList; } function refreshAllTabs() { refreshExactList(); refreshRegexList(); refreshTagNameList(); refreshConfigSettings(); } function isBlacklistPanelCreated() { const panelInDom = document.querySelector( "#bilibili-blacklist-manager-panel" ); if (panelInDom) { if (!managerPanel) { managerPanel = panelInDom; } return true; } return false; } // 创建黑名单面板 function createBlacklistPanel() { if (isBlacklistPanelCreated()) { return; } managerPanel = document.createElement("div"); managerPanel.id = "bilibili-blacklist-panel"; managerPanel.style.position = "fixed"; managerPanel.style.top = "50%"; managerPanel.style.left = "50%"; managerPanel.style.transform = "translate(-50%, -50%)"; managerPanel.style.width = "700px"; managerPanel.style.maxHeight = "80vh"; managerPanel.style.backgroundColor = "#fff"; managerPanel.style.borderRadius = "8px"; managerPanel.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.15)"; managerPanel.style.zIndex = "99999"; managerPanel.style.overflow = "hidden"; managerPanel.style.display = "none"; managerPanel.style.flexDirection = "column"; managerPanel.style.backgroundColor = "#ffffffee"; const tabContainer = document.createElement("div"); tabContainer.style.display = "flex"; tabContainer.style.borderBottom = "1px solid #f1f2f3"; // 内容区 const exactContent = document.createElement("div"); exactContent.style.padding = "16px"; exactContent.style.overflowY = "auto"; exactContent.style.flex = "1"; exactContent.style.display = "block"; const regexContent = document.createElement("div"); regexContent.style.padding = "16px"; regexContent.style.overflowY = "auto"; regexContent.style.flex = "1"; regexContent.style.display = "none"; const tnameContent = document.createElement("div"); tnameContent.style.padding = "16px"; tnameContent.style.overflowY = "auto"; tnameContent.style.flex = "1"; tnameContent.style.display = "none"; const configContent = document.createElement("div"); configContent.style.padding = "16px"; configContent.style.overflowY = "auto"; configContent.style.flex = "1"; configContent.style.display = "none"; const tabs = [ { name: "精确匹配(Up名字)", content: exactContent }, { name: "正则匹配(Up/标题)", content: regexContent }, { name: "屏蔽分类", content: tnameContent }, { name: "插件配置", content: configContent }, ]; tabs.forEach((tabData) => { const tab = document.createElement("div"); tab.textContent = tabData.name; tab.style.padding = "12px 16px"; tab.style.cursor = "pointer"; tab.style.fontWeight = "500"; tab.style.borderBottom = tabData.content.style.display === "block" ? "2px solid #fb7299" : "none"; tab.addEventListener("click", () => { tabs.forEach(({ tab: t, content: c }) => { t.style.borderBottom = "none"; c.style.display = "none"; }); tab.style.borderBottom = "2px solid #fb7299"; tabData.content.style.display = "block"; }); tabData.tab = tab; tabContainer.appendChild(tab); }); // Header 区域 const header = document.createElement("div"); header.style.padding = "16px"; header.style.borderBottom = "1px solid #f1f2f3"; header.style.display = "flex"; header.style.justifyContent = "space-between"; header.style.alignItems = "center"; blockTitle = document.createElement("h3"); blockTitle.style.margin = "0"; blockTitle.style.fontSize = "16px"; blockTitle.style.fontWeight = "500"; const closeBtn = document.createElement("button"); closeBtn.textContent = "×"; closeBtn.style.background = "none"; closeBtn.style.border = "none"; closeBtn.style.fontSize = "20px"; closeBtn.style.cursor = "pointer"; closeBtn.style.padding = "0 8px"; closeBtn.addEventListener("click", () => { managerPanel.style.display = "none"; }); header.appendChild(blockTitle); header.appendChild(closeBtn); const contentContainer = document.createElement("div"); contentContainer.style.display = "flex"; contentContainer.style.flexDirection = "column"; contentContainer.style.flex = "1"; contentContainer.style.overflow = "hidden"; // 精确匹配输入框 const addExactContainer = document.createElement("div"); addExactContainer.style.display = "flex"; addExactContainer.style.marginBottom = "16px"; addExactContainer.style.gap = "8px"; const exactInput = document.createElement("input"); exactInput.type = "text"; exactInput.placeholder = "输入要屏蔽的UP主名称"; exactInput.style.flex = "1"; exactInput.style.padding = "8px"; exactInput.style.border = "1px solid #ddd"; exactInput.style.borderRadius = "4px"; const addExactBtn = document.createElement("button"); addExactBtn.textContent = "添加"; addExactBtn.style.padding = "8px 16px"; addExactBtn.style.background = "#fb7299"; addExactBtn.style.color = "#fff"; addExactBtn.style.border = "none"; addExactBtn.style.borderRadius = "4px"; addExactBtn.style.cursor = "pointer"; addExactBtn.addEventListener("click", () => { const upName = exactInput.value.trim(); if (upName) { addToExactBlacklist(upName); exactInput.value = ""; } }); addExactContainer.appendChild(exactInput); addExactContainer.appendChild(addExactBtn); exactContent.appendChild(addExactContainer); // 正则匹配输入框 const addRegexContainer = document.createElement("div"); addRegexContainer.style.display = "flex"; addRegexContainer.style.marginBottom = "16px"; addRegexContainer.style.gap = "8px"; const regexInput = document.createElement("input"); regexInput.type = "text"; regexInput.placeholder = "输入正则表达式 (如: 小小.*Official)"; regexInput.style.flex = "1"; regexInput.style.padding = "8px"; regexInput.style.border = "1px solid #ddd"; regexInput.style.borderRadius = "4px"; const addRegexBtn = document.createElement("button"); addRegexBtn.textContent = "添加"; addRegexBtn.style.padding = "8px 16px"; addRegexBtn.style.background = "#fb7299"; addRegexBtn.style.color = "#fff"; addRegexBtn.style.border = "none"; addRegexBtn.style.borderRadius = "4px"; addRegexBtn.style.cursor = "pointer"; addRegexBtn.addEventListener("click", () => { const regex = regexInput.value.trim(); if (regex && !regexBlacklist.includes(regex)) { try { new RegExp(regex); regexBlacklist.push(regex); saveBlacklists(); regexInput.value = ""; refreshRegexList(); //BlockCard(); } catch (e) { alert("无效的正则表达式: " + e.message); } } }); addRegexContainer.appendChild(regexInput); addRegexContainer.appendChild(addRegexBtn); regexContent.appendChild(addRegexContainer); // 精确匹配列表 exactList = document.createElement("ul"); exactList.id = "bilibili-blacklist-exact-list"; exactList.style.listStyle = "none"; exactList.style.padding = "0"; exactList.style.margin = "0"; // 正则匹配列表 regexList = document.createElement("ul"); regexList.id = "bilibili-blacklist-regex-list"; regexList.style.listStyle = "none"; regexList.style.padding = "0"; regexList.style.margin = "0"; // tname列表 tNameList = document.createElement("ul"); tNameList.id = "bilibili-blacklist-tname-list"; tNameList.style.listStyle = "none"; tNameList.style.padding = "0"; tNameList.style.margin = "0"; // 配置列表 configList = document.createElement("ul"); configList.id = "bilibili-blacklist-config-list"; configList.style.listStyle = "none"; configList.style.padding = "0"; configList.style.margin = "0"; // 更新列表 refreshAllTabs(); exactContent.appendChild(exactList); regexContent.appendChild(regexList); tnameContent.appendChild(tNameList); configContent.appendChild(configList); contentContainer.appendChild(exactContent); contentContainer.appendChild(regexContent); contentContainer.appendChild(tnameContent); contentContainer.appendChild(configContent); managerPanel.appendChild(tabContainer); managerPanel.appendChild(header); managerPanel.appendChild(contentContainer); document.body.appendChild(managerPanel); return managerPanel; } // 添加全局样式 GM_addStyle(` .bilibili-blacklist-block-container { display: none; position: absolute; top: 0; left: 0; width: 100%; height: 20px; margin-top: 5px; padding: 0 5px; font-size: 12px; flex-direction: row; justify-content: space-between; align-items: center; gap: 3px; z-index: 9999; pointer-events: none; text-align:center; } .bili-video-card:hover .bilibili-blacklist-block-container, .card-box:hover .bilibili-blacklist-block-container { display: flex !important; pointer-events: none; } .card-box .bilibili-blacklist-block-container { flex-direction: column; align-items: flex-start; height: 100%; } .card-box .bilibili-blacklist-tname-group { flex-direction: column; align-items: flex-end; bottom: 0; } .card-box .bilibili-blacklist-tname-group .bilibili-blacklist-tname { background-color:rgba(255, 255, 255, 0.87); color: #9499A0; border: 1px solid #9499A0; } .bilibili-blacklist-block-btn { position: static; display: flex; width: 40px; height: 20px; justify-content: center; align-items: center; pointer-events: auto !important; background-color: #fb7299dd; color: white; border-radius: 10%; cursor: pointer; text-align: center; } .bilibili-blacklist-tname-group { display: flex; flex-direction: row; padding:0 5px; gap: 3px; align-items: center; margin-left: auto; max-width: 80%; pointer-events: none; } .bilibili-blacklist-tname { background-color: #fb7299dd; color: white; height: 20px; padding: 0 5px; border-radius: 10%; cursor: pointer; border-radius: 2px; pointer-events: auto; text-align: center; display: flex; justify-content: center; align-items: center; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; } /* 修复视频卡片布局 */ .bili-video-card__cover { contain: layout !important; } /* 面板样式 */ #bilibili-blacklist-panel { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 18px; } /* 按钮悬停效果 */ #bilibili-blacklist-panel button { transition: background-color 0.2s; } #bilibili-blacklist-panel button:hover { opacity: 0.9; } /* 管理按钮悬停效果 */ #bilibili-blacklist-manager:hover svg { transform: scale(1.1); } #bilibili-blacklist-manager svg { transition: transform 0.2s; } /* 输入框聚焦效果 */ #bilibili-blacklist-panel input:focus { outline: none; border-color: #fb7299 !important; } /*灰度效果*/ .bilibili-blacklist-grayscale { filter: grayscale(95%); } `); //可爱的卡比图标 function getKirbySVG() { return ` `; } //#endregion //########################## //#region 观察者 // MutationObserver 检测动态加载的新内容(仅当节点可见时才触发) const observer = new MutationObserver((mutations) => { let shouldCheck = false; if (isVideoPage()) { mutations.forEach((mutation) => { if (mutation.addedNodes.length > 0) { // 检查新增节点是否可见(有宽度或高度) shouldCheck = Array.from(mutation.addedNodes).some((node) => { // 仅检查元素节点(跳过文本节点、注释等) if (node.nodeType !== Node.ELEMENT_NODE) return false; // 检查元素或其子元素是否可见 const hasVisibleContent = node.offsetWidth > 0 || node.offsetHeight > 0 || node.querySelector("[offsetWidth], [offsetHeight]"); return hasVisibleContent; }); } }); } else { mutations.forEach((mutation) => { if (mutation.addedNodes.length > 0) { shouldCheck = true; } }); } // 如果有可见的新内容,延迟 1 秒后执行屏蔽(确保 DOM 完全渲染) if (shouldCheck) { processedCards = new WeakSet(); // 重置已处理卡片集合 setTimeout(() => { BlockCard(); //addBlacklistManagerButton(); // 确保每次都添加黑名单管理按钮 if (isMainPage()) { BlockMainAD(); // 屏蔽页面广告 } if (isVideoPage()) { BlockVideoPageAd(); // 屏蔽视频页面广告 } if (!document.getElementById("bilibili-blacklist-manager")) { addBlacklistManagerButton(); } }, 1000); } }); // 初始化观察者(监视 DOM 变化) let observerError = 0; function initObserver(container) { const rootNode = document.getElementById(container) || document.querySelector(container) || document.documentElement; // 回退到整个文档 if (rootNode) { observer.observe(rootNode, { childList: true, // 监视添加/移除的节点 subtree: true, // 监视所有后代 }); return true; } else { // 如果没找到根节点则重试 setTimeout(() => initObserver(container), 500); console.warn("[bilibili-blacklist] 未找到根节点,正在重试..."); observerError++; if (observerError > 10) { console.error("[bilibili-blacklist] 重试次数过多,停止重试。"); return false; } } } //#endregion //#region 初始化函数 let isInit = false; // 是否已经初始化 function init() { // 重置状态 isBlocking = false; lastBlockTime = 0; blockedCards = new Set(); // 使用 Set 存储已屏蔽的卡片 processedCards = new WeakSet(); cardSequenceGetJson = new Set(); if (isMainPage()) { initMainPage(); // 初始化主页 BlockMainAD(); // 屏蔽主页广告 } else if (isSearchPage()) { initSearchPage(); // 初始化搜索页 } else if (isVideoPage()) { initVideoPage(); // 初始化播放页 //BlockVideoPageAd(); // 屏蔽视频页面广告 } else if (isCategoryPage()) { initCategoryPage(); // 初始化分类页 } else if (isUserSpace()) { initUserSpace(); // 初始化用户空间 //return; // 用户空间不需要屏蔽 } else { return; // 如果不是已知页面则不执行 } BlockCard(); // 初始化时立即执行屏蔽 addBlacklistManagerButton(); // 添加黑名单管理按钮 createBlacklistPanel(); console.log("[bilibili-blacklist] 脚本已加载🥔"); } // 监听页面加载完成事件 document.addEventListener("DOMContentLoaded", init); if ( document.readyState === "complete" || document.readyState === "interactive" ) { init(); } // 检查当前页面是否为B站主页 function isMainPage() { return location.pathname === "/"; } function initMainPage() { initObserver("i_cecream"); // 传入B站主页的主容器ID console.log("[bilibili-blacklist] 主页已加载🍓"); } /// -----搜索页---- function isSearchPage() { return location.hostname === "search.bilibili.com"; } function initSearchPage() { initObserver("i_cecream"); console.log("[bilibili-blacklist] 搜索页已加载🍉"); } /// --- 播放页 --- function isVideoPage() { return location.pathname.startsWith("/video/"); } function initVideoPage() { initObserver("rcmd-tab"); console.log("[bilibili-blacklist] 播放页已加载🍇"); } // ---- 分类页 ---- function isCategoryPage() { return location.pathname.startsWith("/c/"); } function initCategoryPage() { initObserver("win"); console.log("[bilibili-blacklist] 分类页已加载🍊"); } ///---用户空间--- function isUserSpace() { return location.hostname === "space.bilibili.com"; } function initUserSpace() { console.log("[bilibili-blacklist] 用户空间已加载🍎"); const upNameSelector = "#h-name, .nickname"; const observerForUpName = new MutationObserver((mutations, observer) => { const upNameElement = document.querySelector(upNameSelector); if (upNameElement) { observer.disconnect(); addBlockButtonToUserSpace(upNameElement); } }); observerForUpName.observe(document.body, { childList: true, subtree: true, }); const initialUpNameElement = document.querySelector(upNameSelector); if (initialUpNameElement) { observerForUpName.disconnect(); addBlockButtonToUserSpace(initialUpNameElement); } } function addBlockButtonToUserSpace(upNameElement) { const upName = upNameElement.textContent.trim(); //console.log(`当前用户昵称: ${upName}`); if (upNameElement.querySelector(".bilibili-blacklist-up-block-btn")) { return; } // 设置 inline-flex,让文字和按钮一行显示 upNameElement.style.display = "inline-flex"; upNameElement.style.alignItems = "center"; const button = document.createElement("button"); button.className = "bilibili-blacklist-up-block-btn"; button.textContent = "屏蔽"; button.style.color = "#fff"; button.style.width = "100px"; button.style.height = "30px"; button.style.marginLeft = "10px"; button.style.borderRadius = "5px"; button.style.border = "1px solid #fb7299"; const refreshButtonStatus = () => { const blocked = isBlacklisted(upName); if (blocked) { button.textContent = "已屏蔽"; button.style.backgroundColor = "#dddddd"; button.style.border = "1px solid #ccc"; upNameElement.style.textDecoration = "line-through"; // 添加删除线效果 document.body.classList.add("bilibili-blacklist-grayscale"); } else { button.textContent = "屏蔽"; button.style.backgroundColor = "#fb7299"; button.style.border = "1px solid #fb7299"; upNameElement.style.textDecoration = "none"; // 移除删除线效果 document.body.classList.remove("bilibili-blacklist-grayscale"); } }; button.addEventListener("click", (e) => { e.stopPropagation(); const blocked = isBlacklisted(upName); if (blocked) { removeFromExactBlacklist(upName); } else { addToExactBlacklist(upName); } refreshButtonStatus(); }); refreshButtonStatus(); // Set initial button state upNameElement.appendChild(button); } //#endregion //#region 额外功能-屏蔽广告 // 屏蔽广告 function BlockMainAD() { if (!globalConfig.flagAD) return; const adSelectors = [ ".floor-single-card", // 分区推荐 ".bili-live-card", // 直播推广 ]; adSelectors.forEach((selector) => { document.querySelectorAll(selector).forEach((adCard) => { adCard.remove(); }); }); } // 屏蔽视频页面广告(使用数组优化) function BlockVideoPageAd() { if (!globalConfig.flagAD) return; const adSelectors = [ ".video-card-ad-small", // 右上角推广 ".slide-ad-exp", // 大推广 ".video-page-game-card-small", // 游戏推广 ".activity-m-v1", // 活动推广 ".video-page-special-card-small", // 特殊卡片推广 ".ad-floor-exp", // 广告地板 ]; adSelectors.forEach((selector) => { document.querySelectorAll(selector).forEach((adCard) => { adCard.remove(); }); }); } //#endregion })();