您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
一个用于B站的用户脚本,提供快速屏蔽和管理UP主内容的功能,以及便捷的UP主访问方式
// ==UserScript== // @name bilibili-up-filter // @namespace https://github.com/sansan0/useful-userscripts // @version 3.0 // @description 一个用于B站的用户脚本,提供快速屏蔽和管理UP主内容的功能,以及便捷的UP主访问方式 // @author sansan // @match https://www.bilibili.com/* // @grant GM_addStyle // @license GPL-3.0 License // @icon data:image/png;base64,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 // ==/UserScript== (function () { "use strict"; // 如果当前窗口不是顶层窗口,则直接退出 if (window !== window.top) { return; } /** * 常量定义 * 定义了程序中使用的各种固定值 */ const CONSTANTS = { BLOCKED_UPS_KEY: "blocked_up_users", TOAST_DISPLAY_DURATION: 2000, TOAST_ANIMATION_DURATION: 300, HOVER_DELAY: 100, }; /** * 添加样式到页面 * 包含了所有UI组件的样式:按钮、复选框、提示框、动画等 */ function addStyles() { GM_addStyle(` /* 基础按钮样式 */ .id-block-toggle, .blocked-ups-toggle, .following-toggle { position: fixed; right: 10px; padding: 10px; border-radius: 8px; cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,0.1); z-index: 9999; display: flex; align-items: center; justify-content: space-between; width: 100px; box-sizing: border-box; transition: all 0.3s ease; color: white; } /* ID屏蔽按钮特定样式 */ .id-block-toggle { top: calc(50% - 80px); background: #9254de; } .id-block-toggle:hover { background: #a872e8; box-shadow: 0 4px 12px rgba(0,0,0,0.2); } /* 已屏蔽按钮特定样式 */ .blocked-ups-toggle { top: 50%; transform: translateY(-50%); background: #00aeec; } .blocked-ups-toggle:hover { background: #0095cc; box-shadow: 0 4px 12px rgba(0,0,0,0.2); } /* 关注者按钮特定样式 */ .following-toggle { top: calc(50% + 40px); background: #FB7299; } .following-toggle:hover { background: #fc8bab; box-shadow: 0 4px 12px rgba(0,0,0,0.2); } /* ID屏蔽输入框样式 */ .id-block-input { position: absolute; left: 0; top: 0; width: 100%; height: 100%; background: rgba(146, 84, 222, 0.9); border: none; color: white; padding: 10px; border-radius: 8px; outline: none; display: none; font-size: 14px; box-sizing: border-box; } .id-block-input::placeholder { color: rgba(255, 255, 255, 0.8); } .id-block-toggle.input-mode .id-block-input { display: block; } .id-block-toggle.input-mode span { opacity: 0; } /* 隐藏状态样式 */ .id-block-toggle.hide, .blocked-ups-toggle.hide, .following-toggle.hide { opacity: 0; pointer-events: none; transition: opacity 0.3s ease; } /* 复选框样式 */ .block-checkbox { position: absolute; top: -40px; bottom: auto; left: 0; background: white; padding: 6px 10px; border: 1px solid #e3e5e7; border-radius: 6px; display: none; z-index: 10; box-shadow: 0 2px 8px rgba(0,0,0,0.1); transition: all 0.3s ease; font-size: 13px; color: #18191c; width: 130px; white-space: nowrap; box-sizing: border-box; pointer-events: auto; } .bili-video-card__info--owner { position: relative; z-index: 9; } .block-checkbox:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.15); } .block-checkbox label { display: flex; align-items: center; cursor: pointer; user-select: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width: 100%; } .block-checkbox input[type="checkbox"] { flex-shrink: 0; appearance: none; -webkit-appearance: none; width: 16px; height: 16px; border: 2px solid #c9ccd0; border-radius: 3px; margin-right: 6px; position: relative; cursor: pointer; transition: all 0.2s ease; padding: 0; } .block-checkbox input[type="checkbox"]:checked { background-color: #00aeec; border-color: #00aeec; } .block-checkbox input[type="checkbox"]:checked::after { content: ""; position: absolute; left: 4px; top: 1px; width: 5px; height: 9px; border: solid white; border-width: 0 2px 2px 0; transform: rotate(45deg); } .block-checkbox input[type="checkbox"]:hover { border-color: #00aeec; } .block-checkbox input[type="checkbox"]:focus { outline: none; box-shadow: 0 0 0 2px rgba(0,174,236,0.2); } .block-checkbox input[type="checkbox"]:disabled { background-color: #f5f5f5; border-color: #e0e0e0; cursor: not-allowed; } .block-checkbox label span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; letter-spacing: 0.3px; } /* 提示框样式 */ .toast-notification { position: fixed; top: 16px; right: 16px; background: rgba(0, 0, 0, 0.8); color: white; padding: 12px 24px; border-radius: 8px; z-index: 9999999; font-size: 14px; opacity: 0; transform: translateX(100%); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); display: flex; align-items: center; gap: 8px; pointer-events: none; box-shadow: 0 4px 12px rgba(0,0,0,0.15); } .toast-notification.show { opacity: 1; transform: translateX(0); } .toast-notification.hide { opacity: 0; transform: translateX(100%); } .toast-icon { width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; font-size: 16px; } .toast-block { color: #ff6b6b; } .toast-unblock { color: #69db7c; } /* 动画效果 */ @keyframes fadeInUp { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } } @keyframes fadeOutDown { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(5px); } } .block-checkbox.show { animation: fadeInUp 0.3s ease forwards; } .block-checkbox.hide { animation: fadeOutDown 0.3s ease forwards; } /* 面板样式 */ .blocked-ups-panel, .following-panel { position: fixed; right: -350px; top: 0; width: 320px; height: 100vh; background: white; box-shadow: -2px 0 8px rgba(0,0,0,0.1); z-index: 9998; transition: right 0.3s ease; display: flex; flex-direction: column; } .blocked-ups-panel.show, .following-panel.show { right: 0; } .blocked-ups-header { padding: 16px; background: #f6f7f8; border-bottom: 1px solid #e3e5e7; font-weight: bold; display: flex; justify-content: space-between; align-items: center; } .blocked-ups-list { flex: 1; overflow-y: auto; padding: 12px; } .blocked-up-item { display: flex; justify-content: space-between; align-items: center; padding: 10px; border-bottom: 1px solid #f0f1f2; transition: background-color 0.2s ease; } .blocked-up-item:hover { background-color: #f6f7f8; } .blocked-up-info { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #18191c; text-decoration: none; margin-right: 10px; } .blocked-up-remove { padding: 4px 8px; border: none; background: #ff6b6b; color: white; border-radius: 4px; cursor: pointer; margin-left: 8px; transition: background-color 0.2s ease; } .blocked-up-remove:hover { background: #ff5252; } .follow-time { color: #99a2aa; font-size: 12px; margin-left: auto; padding-left: 10px; white-space: nowrap; } /* 滚动条样式 */ .blocked-ups-list::-webkit-scrollbar { width: 6px; } .blocked-ups-list::-webkit-scrollbar-track { background: #f1f1f1; } .blocked-ups-list::-webkit-scrollbar-thumb { background: #c0c0c0; border-radius: 3px; } .blocked-ups-list::-webkit-scrollbar-thumb:hover { background: #a0a0a0; } /* 移除动画 */ @keyframes removeItem { 0% { opacity: 1; transform: translateX(0); } 100% { opacity: 0; transform: translateX(100%); height: 0; padding: 0; margin: 0; } } .blocked-up-item.removing { animation: removeItem 0.3s ease-out forwards; overflow: hidden; } /* Toast动画 */ @keyframes toastIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes toastOut { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } } .toast-notification.show { animation: toastIn 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards; } .toast-notification.hide { animation: toastOut 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards; } `); } /** * 数据存储与管理模块 * 处理与localStorage相关的所有操作 */ const DataManager = { /** * 从localStorage获取被屏蔽的UP主列表 * @returns {Array} 返回被屏蔽UP主的数组,每个元素包含id和name */ getBlockedUps() { try { const blockedUps = localStorage.getItem(CONSTANTS.BLOCKED_UPS_KEY); return blockedUps ? JSON.parse(blockedUps) : []; } catch (error) { console.error("Error getting blocked UPs:", error); return []; } }, /** * 添加UP主到屏蔽列表 * @param {string} upId - UP主的ID * @param {string} upName - UP主的昵称 */ addBlockedUp(upId, upName) { const blockedUps = this.getBlockedUps(); const existingIndex = blockedUps.findIndex((up) => up.id === upId); if (existingIndex > -1 && !blockedUps[existingIndex].name) { blockedUps[existingIndex].name = upName; } else if (existingIndex === -1) { blockedUps.push({ id: upId, name: upName }); } localStorage.setItem( CONSTANTS.BLOCKED_UPS_KEY, JSON.stringify(blockedUps) ); if (document.body.hasAttribute("data-panel-initialized")) { UIManager.updateBlockedUpsList(); } }, /** * 从屏蔽列表中移除指定UP主 * @param {string} upId - 需要移除的UP主ID */ removeBlockedUp(upId) { const blockedUps = this.getBlockedUps(); const filteredUps = blockedUps.filter((up) => up.id !== upId); localStorage.setItem( CONSTANTS.BLOCKED_UPS_KEY, JSON.stringify(filteredUps) ); if (document.body.hasAttribute("data-panel-initialized")) { UIManager.updateBlockedUpsList(); } }, /** * 检查UP主是否被屏蔽 * @param {string} upId - UP主ID * @returns {boolean} 如果被屏蔽返回true,否则返回false */ isBlocked(upId) { return this.getBlockedUps().some((up) => up.id === upId); }, }; /** * UI管理模块 * 处理所有界面元素的创建、更新和交互 */ const UIManager = { blockedUpsPanel: null, /** * 显示操作提示框 * @param {string} upName - UP主昵称 * @param {boolean} isBlocking - true表示屏蔽操作,false表示取消屏蔽 */ showToast(upName, isBlocking) { const existingToast = document.querySelector(".toast-notification"); if (existingToast) existingToast.remove(); const toast = document.createElement("div"); toast.className = "toast-notification"; const icon = isBlocking ? "🚫" : "✅"; const action = isBlocking ? "已屏蔽" : "已取消屏蔽"; toast.innerHTML = ` <span class="toast-icon ${ isBlocking ? "toast-block" : "toast-unblock" }">${icon}</span> <span>${action} UP主:${upName}</span> `; document.body.appendChild(toast); toast.offsetHeight; // 触发重绘 requestAnimationFrame(() => toast.classList.add("show")); setTimeout(() => { toast.classList.add("hide"); setTimeout(() => toast.remove(), CONSTANTS.TOAST_ANIMATION_DURATION); }, CONSTANTS.TOAST_DISPLAY_DURATION); }, /** * 更新被屏蔽UP主列表显示 */ updateBlockedUpsList() { const panel = document.querySelector(".blocked-ups-panel"); const listContainer = panel?.querySelector(".blocked-ups-list"); if (!listContainer) return; const blockedUps = DataManager.getBlockedUps(); const reversedUps = [...blockedUps].reverse(); listContainer.innerHTML = reversedUps .map( (up) => ` <div class="blocked-up-item" data-up-id="${up.id}"> <a href="https://space.bilibili.com/${up.id}" target="_blank" class="blocked-up-info"> ${up.name || `ID: ${up.id}`} </a> <button class="blocked-up-remove">移除</button> </div> ` ) .join(""); const toggleButton = document.querySelector(".blocked-ups-toggle"); if (toggleButton) { toggleButton.querySelector("span:last-child").textContent = blockedUps.length; } const countElement = panel.querySelector(".blocked-count"); if (countElement) { countElement.textContent = blockedUps.length; } }, /** * 创建UP主屏蔽控制的复选框 * @param {string} upId - UP主ID * @param {string} upName - UP主昵称 * @returns {HTMLElement} 返回创建的复选框元素 */ createCheckbox(upId, upName) { const checkbox = document.createElement("div"); checkbox.className = "block-checkbox"; const isBlocked = DataManager.isBlocked(upId); checkbox.innerHTML = ` <label> <input type="checkbox" ${isBlocked ? "checked" : ""}> <span>屏蔽该UP主</span> </label> `; const input = checkbox.querySelector("input"); input.addEventListener("change", (e) => { if (e.target.checked) { DataManager.addBlockedUp(upId, upName); this.showToast(upName, true); } else { DataManager.removeBlockedUp(upId); this.updateCheckboxStatus(upId); this.showToast(upName, false); } ContentFilter.filterBlockedContent(); }); return checkbox; }, /** * 更新所有相关复选框的状态 * @param {string} upId - UP主ID */ updateCheckboxStatus(upId) { document .querySelectorAll(".bili-video-card__info--owner") .forEach((link) => { const href = link.getAttribute("href"); if (!href) return; const currentUpId = href.match(/\/(\d+)$/)?.[1]; if (currentUpId === upId) { const checkbox = link.querySelector( '.block-checkbox input[type="checkbox"]' ); if (checkbox) { checkbox.checked = false; } } }); }, /** * 处理UP主链接的鼠标悬停事件 * @param {HTMLElement} element - UP主链接元素 */ handleHover(element) { const href = element.getAttribute("href"); if (!href) return; const upId = href.match(/\/(\d+)$/)?.[1]; if (!upId) return; const authorSpan = element.querySelector( ".bili-video-card__info--author" ); const upName = authorSpan?.getAttribute("title") || "未知UP主"; let checkbox = element.querySelector(".block-checkbox"); if (!checkbox) { checkbox = this.createCheckbox(upId, upName); element.style.position = "relative"; element.appendChild(checkbox); } else { const input = checkbox.querySelector('input[type="checkbox"]'); const isBlocked = DataManager.isBlocked(upId); input.checked = isBlocked; } let hideTimeout; let isOverElement = false; let isOverCheckbox = false; const showCheckbox = () => { checkbox.style.display = "block"; requestAnimationFrame(() => { checkbox.style.opacity = "1"; checkbox.style.transform = "translateY(0)"; }); }; const hideCheckbox = () => { checkbox.style.opacity = "0"; checkbox.style.transform = "translateY(5px)"; hideTimeout = setTimeout(() => { checkbox.style.display = "none"; }, CONSTANTS.TOAST_ANIMATION_DURATION); }; const checkVisibility = () => { setTimeout(() => { if (!isOverElement && !isOverCheckbox) { hideCheckbox(); } }, CONSTANTS.HOVER_DELAY); }; element.addEventListener("mouseenter", () => { isOverElement = true; clearTimeout(hideTimeout); showCheckbox(); }); element.addEventListener("mouseleave", () => { isOverElement = false; checkVisibility(); }); checkbox.addEventListener("mouseenter", () => { isOverCheckbox = true; clearTimeout(hideTimeout); }); checkbox.addEventListener("mouseleave", () => { isOverCheckbox = false; checkVisibility(); }); element.setAttribute("data-hover-initialized", "true"); }, /** * 切换所有按钮的显示/隐藏状态 * @param {boolean} hide - 为true时隐藏按钮,为false时显示按钮 */ toggleAllButtons(hide = true) { const followingToggle = document.querySelector(".following-toggle"); const blockedToggle = document.querySelector(".blocked-ups-toggle"); const idBlockToggle = document.querySelector(".id-block-toggle"); if (hide) { followingToggle?.classList.add("hide"); blockedToggle?.classList.add("hide"); idBlockToggle?.classList.add("hide"); } else { followingToggle?.classList.remove("hide"); blockedToggle?.classList.remove("hide"); idBlockToggle?.classList.remove("hide"); } }, /** * 创建并管理被屏蔽UP主列表面板 * @returns {Object} 返回包含更新面板方法的对象 */ createBlockedUpsPanel() { // 创建ID屏蔽按钮 const idBlockButton = document.createElement("div"); idBlockButton.className = "id-block-toggle"; idBlockButton.innerHTML = ` <span>手动屏蔽ID</span> <span></span> <input type="text" class="id-block-input" placeholder="输入UP主ID" pattern="[0-9]*"> `; // 获取输入框元素 const idInput = idBlockButton.querySelector(".id-block-input"); // 处理ID输入框事件 this._setupIdInputEvents(idBlockButton, idInput); document.body.appendChild(idBlockButton); // 创建已屏蔽按钮 const toggleButton = document.createElement("div"); toggleButton.className = "blocked-ups-toggle"; toggleButton.innerHTML = ` <span>已屏蔽</span> <span>${DataManager.getBlockedUps().length}</span> `; // 创建屏蔽列表面板 const panel = document.createElement("div"); panel.className = "blocked-ups-panel"; panel.innerHTML = ` <div class="blocked-ups-header"> <span>已屏蔽的UP主</span> <span class="blocked-count">${ DataManager.getBlockedUps().length }</span> </div> <div class="blocked-ups-list"></div> `; // 设置面板交互 this._setupPanelInteraction(toggleButton, panel); this._setupRemoveButtons(panel); document.body.appendChild(toggleButton); document.body.appendChild(panel); this.updateBlockedUpsList(); document.body.setAttribute("data-panel-initialized", "true"); return { updateBlockedUpsList: () => this.updateBlockedUpsList(), }; }, /** * 设置ID输入框的事件处理 * @private * @param {HTMLElement} idBlockButton - ID屏蔽按钮元素 * @param {HTMLElement} idInput - 输入框元素 */ _setupIdInputEvents(idBlockButton, idInput) { // 处理鼠标进入事件 idBlockButton.addEventListener("mouseenter", () => { idBlockButton.classList.add("input-mode"); idInput.focus(); }); // 处理鼠标离开事件 idBlockButton.addEventListener("mouseleave", () => { if (document.activeElement !== idInput) { idBlockButton.classList.remove("input-mode"); idInput.value = ""; } }); // 处理输入事件 idInput.addEventListener("input", (e) => { e.target.value = e.target.value.replace(/\D/g, ""); }); // 处理键盘事件 idInput.addEventListener("keydown", (e) => { if (e.key === "Enter") { const upId = e.target.value.trim(); if (upId) { DataManager.addBlockedUp(upId, null); this.showToast(`ID: ${upId}`, true); e.target.value = ""; idBlockButton.classList.remove("input-mode"); this.updateBlockedUpsList(); ContentFilter.filterBlockedContent(); } } else if (e.key === "Escape") { idBlockButton.classList.remove("input-mode"); e.target.value = ""; idBlockButton.blur(); } }); // 处理失去焦点事件 idInput.addEventListener("blur", () => { setTimeout(() => { if (document.activeElement !== idInput) { idBlockButton.classList.remove("input-mode"); idInput.value = ""; } }, 200); }); }, /** * 设置面板交互事件 * @private * @param {HTMLElement} toggleButton - 触发按钮 * @param {HTMLElement} panel - 面板元素 */ _setupPanelInteraction(toggleButton, panel) { let mouseInPanel = false; toggleButton.addEventListener("mouseenter", () => { panel.classList.add("show"); this.toggleAllButtons(true); this.updateBlockedUpsList(); }); panel.addEventListener("mouseenter", () => { mouseInPanel = true; }); panel.addEventListener("mouseleave", () => { mouseInPanel = false; setTimeout(() => { if (!mouseInPanel && !toggleButton.matches(":hover")) { panel.classList.remove("show"); this.toggleAllButtons(false); } }, 200); }); toggleButton.addEventListener("mouseleave", () => { setTimeout(() => { if (!mouseInPanel) { panel.classList.remove("show"); this.toggleAllButtons(false); } }, 200); }); }, /** * 设置移除按钮的事件处理 * @private * @param {HTMLElement} panel - 面板元素 */ _setupRemoveButtons(panel) { panel.addEventListener("click", (e) => { if (e.target.classList.contains("blocked-up-remove")) { const upItem = e.target.closest(".blocked-up-item"); if (!upItem) return; const upId = upItem.dataset.upId; const upName = upItem .querySelector(".blocked-up-info") .textContent.trim(); upItem.classList.add("removing"); setTimeout(() => { DataManager.removeBlockedUp(upId); this.updateCheckboxStatus(upId); this.showToast(upName, false); this.updateBlockedUpsList(); ContentFilter.filterBlockedContent(); }, 300); } }); }, /** * 添加屏蔽复选框到UP主头像 */ addCheckboxToUpAvatar() { const upAvatar = document.querySelector("a.up-avatar"); if (!upAvatar) return; const href = upAvatar.getAttribute("href"); if (!href) return; const upId = href.match(/\/(\d+)$/)?.[1]; if (!upId) return; const upNameElement = document.querySelector(".up-name"); const upName = upNameElement?.textContent?.trim() || "未知UP主"; const createAndAddCheckbox = () => { let checkbox = upAvatar.querySelector(".block-checkbox"); if (!checkbox) { checkbox = this.createCheckbox(upId, upName); upAvatar.style.position = "relative"; upAvatar.appendChild(checkbox); checkbox.style.display = "block"; checkbox.style.opacity = "1"; checkbox.style.transform = "translateY(0)"; } }; // 立即创建一次 createAndAddCheckbox(); // 创建专门监听 up-avatar 的观察器 const avatarObserver = new MutationObserver(() => { createAndAddCheckbox(); }); // 配置观察器 avatarObserver.observe(upAvatar, { childList: true, // 监听子元素变化 subtree: true, // 监听所有后代元素 characterData: true, // 监听文本内容变化 attributes: true, // 监听属性变化 }); }, /** * 创建并管理关注列表面板 * @returns {Promise<void>} */ async createFollowingPanel() { if (this.followingPanelInitialized) return; try { const statsContainer = await DOMUtils.waitForElement( ".logged-in > .stats" ); if (!statsContainer) return; const followLink = statsContainer.querySelector("a"); if (!followLink) { console.warn("未找到关注链接"); return; } const userId = followLink.href.match( /space\.bilibili\.com\/(\d+)/ )?.[1]; if (!userId) { console.warn("无法获取用户ID"); return; } const followCount = followLink.querySelector(".stats-number")?.textContent; const followTotal = parseInt(followCount) || 0; const toggleButton = document.createElement("div"); toggleButton.className = "following-toggle"; toggleButton.innerHTML = ` <span>关注者</span> <span>${followTotal}</span> `; const panel = document.createElement("div"); panel.className = "following-panel"; panel.innerHTML = ` <div class="blocked-ups-header"> <span>关注的UP主</span> <span class="following-count">${ followTotal > 50 ? "最近 50 位" : followTotal }</span> </div> <div class="blocked-ups-list"></div> `; this._setupFollowingPanelEvents(toggleButton, panel, userId); document.body.appendChild(toggleButton); document.body.appendChild(panel); this.followingPanelInitialized = true; } catch (error) { console.warn("创建关注者面板失败:", error); } }, /** * 设置关注面板的事件处理和数据加载 * @private * @param {HTMLElement} toggleButton - 触发按钮 * @param {HTMLElement} panel - 面板元素 * @param {string} userId - 用户ID */ _setupFollowingPanelEvents(toggleButton, panel, userId) { let mouseInPanel = false; // 获取关注列表 const updateFollowingList = async () => { const listContainer = panel.querySelector(".blocked-ups-list"); const data = await APIManager.fetchFollowingList(userId); if (!data) return; const headerText = panel.querySelector( ".blocked-ups-header span:first-child" ); headerText.textContent = "关注的UP主"; listContainer.innerHTML = data.list .map( (up) => ` <div class="blocked-up-item"> <a href="https://space.bilibili.com/${up.mid}" target="_blank" class="blocked-up-info"> ${up.uname} </a> <span class="follow-time">${DOMUtils.formatDate( up.mtime )}</span> </div> ` ) .join(""); }; // 设置面板交互 toggleButton.addEventListener("mouseenter", () => { panel.classList.add("show"); this.toggleAllButtons(true); updateFollowingList(); }); panel.addEventListener("mouseenter", () => { mouseInPanel = true; }); panel.addEventListener("mouseleave", () => { mouseInPanel = false; setTimeout(() => { if (!mouseInPanel && !toggleButton.matches(":hover")) { panel.classList.remove("show"); this.toggleAllButtons(false); } }, 200); }); toggleButton.addEventListener("mouseleave", () => { setTimeout(() => { if (!mouseInPanel) { panel.classList.remove("show"); this.toggleAllButtons(false); } }, 200); }); }, }; /** * 内容过滤器模块 * 负责屏蔽和过滤网页上的内容 */ const ContentFilter = { /** * 过滤并移除被屏蔽UP主的内容 */ filterBlockedContent() { // 移除特定类型的卡片 document .querySelectorAll(".floor-single-card, .bili-live-card") .forEach((card) => card.remove()); const blockedUps = DataManager.getBlockedUps(); const filteredUps = new Set(); document .querySelectorAll(".bili-video-card__info--owner") .forEach((link) => { const upId = link.getAttribute("href")?.match(/\/(\d+)$/)?.[1]; if (upId && blockedUps.some((up) => up.id === upId)) { const authorSpan = link.querySelector( ".bili-video-card__info--author" ); const upName = authorSpan?.getAttribute("title") || "未知UP主"; DataManager.addBlockedUp(upId, upName); const targetCard = link.closest(".feed-card") || link.closest(".bili-video-card"); if (targetCard && !filteredUps.has(upId)) { targetCard.remove(); UIManager.showToast(upName, true); filteredUps.add(upId); } } }); }, /** * 初始化MutationObserver以监视DOM变化并过滤内容 */ initContentObserver() { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.nodeType === 1) { // 移除不需要的卡片 if ( node.classList?.contains("floor-single-card") || node.classList?.contains("bili-live-card") ) { node.remove(); return; } // 移除子节点中的卡片 node .querySelectorAll?.(".floor-single-card, .bili-live-card") .forEach((card) => card.remove()); // 处理视频卡片中的UP主链接 node .querySelectorAll?.(".bili-video-card__info--owner") .forEach((link) => { if (!link.getAttribute("data-hover-initialized")) { UIManager.handleHover(link); } }); } }); this.filterBlockedContent(); }); }); // 启动观察器 observer.observe(document.body, { childList: true, subtree: true, }); }, }; /** * API管理模块 * 处理与外部API的交互 */ const APIManager = { /** * 获取用户关注列表 * @param {string} userId - 用户ID * @returns {Promise<Object|null>} 关注列表数据或null(出错时) */ async fetchFollowingList(userId) { try { // 无论总数多少,始终只请求最近的50个 const response = await fetch( `https://api.bilibili.com/x/relation/followings?vmid=${userId}&ps=50`, { credentials: "include", headers: { Accept: "application/json" }, } ); if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); const data = await response.json(); if (data.code !== 0) throw new Error( `API error! code: ${data.code}, message: ${data.message}` ); return data.data; } catch (error) { console.error("Error fetching following list:", error); return null; } }, }; /** * DOM工具模块 * 提供DOM操作相关的辅助函数 */ const DOMUtils = { /** * 等待指定元素加载完成 * @param {string} selector - 元素选择器 * @param {number} maxAttempts - 最大尝试次数 * @param {number} interval - 尝试间隔(毫秒) * @param {number} initialDelay - 初始延迟(毫秒) * @returns {Promise<HTMLElement|null>} 返回找到的元素或null */ waitForElement( selector, maxAttempts = 3, interval = 1000, initialDelay = 1000 ) { return new Promise((resolve, reject) => { let attempts = 0; const checkElement = () => { const element = document.querySelector(selector); if (element) { resolve(element); return; } attempts++; if (attempts >= maxAttempts) { reject( new Error( `Element ${selector} not found after ${maxAttempts} attempts` ) ); return; } setTimeout(checkElement, interval); }; setTimeout(checkElement, initialDelay); }); }, /** * 格式化时间戳为日期字符串 * @param {number} timestamp - 时间戳(秒) * @returns {string} 格式化后的日期字符串(YYYY-MM-DD) */ formatDate(timestamp) { const date = new Date(timestamp * 1000); // 转换为毫秒 const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0"); return `${year}-${month}-${day}`; }, }; /** * 初始化函数 * 程序入口点,初始化各个组件 */ function initialize() { // 添加样式 addStyles(); // 初始化视频卡片上的UP主触发器 document .querySelectorAll(".bili-video-card__info--owner") .forEach((element) => UIManager.handleHover(element)); // 添加UP主头像复选框 UIManager.addCheckboxToUpAvatar(); // 过滤屏蔽内容 ContentFilter.filterBlockedContent(); // 初始化内容观察器 ContentFilter.initContentObserver(); // 页面加载完成后创建面板 window.addEventListener("load", async () => { await UIManager.createFollowingPanel(); UIManager.blockedUpsPanel = UIManager.createBlockedUpsPanel(); }); } // 启动程序 initialize(); })();