Greasy Fork

来自缓存

Greasy Fork is available in English.

Bilibili-BlackList

Bilibili UP屏蔽插件 - 屏蔽UP主视频卡片,支持精确匹配和正则匹配,支持视频页面、分类页面、搜索页面等。

当前为 2025-06-15 提交的版本,查看 最新版本

// ==UserScript==
// @name         Bilibili-BlackList
// @namespace    https://github.com/HeavenTTT/bilibili-blacklist
// @version      1.1.8
// @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
// @license      MIT
// ==/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
   */

  //#region 常量和全局变量

  // 从存储中获取黑名单
  // 默认精确匹配黑名单(区分大小写)
  let exactMatchBlacklist = GM_getValue("exactBlacklist", [
    "绝区零",
    "崩坏星穹铁道",
    "崩坏3",
    "原神",
    "米哈游miHoYo",
  ]);
  // 默认正则匹配黑名单(不区分大小写)
  let regexMatchBlacklist = GM_getValue("regexBlacklist", [
    "王者荣耀",
    "和平精英",
    "PUBG",
    "绝地求生",
    "吃鸡",
  ]);
  // 默认标签名黑名单
  let tagNameBlacklist = GM_getValue("tNameBlacklist", ["手机游戏"]);

  // 从存储中获取全局配置
  let globalPluginConfig = GM_getValue("globalConfig", {
    flagInfo: true, // 启用/禁用按UP主名/标题屏蔽
    flagAD: true, // 启用/禁用屏蔽一般广告
    flagTName: true, // 启用/禁用按标签名屏蔽(需要API调用)
    flagCM: true, // 启用/禁用屏蔽cm.bilibili.com软广
    flagKirby: true, // 启用/禁用被屏蔽视频的卡比覆盖模式
    processQueueInterval: 200, // 处理队列中单个卡片的延迟时间(毫秒)
    blockScanInterval: 200, // BlockCard扫描新卡片的间隔时间(毫秒)
    flagHideOnLoad: true, // 启用/禁用页面加载时自动隐藏
    flagVertical: true, // 启用/禁用屏蔽竖屏视频
    verticalScaleThreshold: 0.7 || 0.7, // 竖屏视频的宽高比阈值(0-1)
  });

  // UI元素(稍后初始化)
  let tempUnblockButton;
  let managerPanel;
  let exactMatchListElement;
  let regexMatchListElement;
  let tagNameListElement;
  let configListElement;
  let blockCountTitleElement;
  let blockCountDisplayElement = null;

  // 内部状态变量
  let isShowAllVideos = false; // 是否显示全部视频卡片
  let isBlockingOperationInProgress = false; // 是否正在执行BlockCard扫描操作
  let lastBlockScanExecutionTime = 0; // 上次执行BlockCard扫描的时间戳
  let blockedVideoCards = new Set(); // 存储已屏蔽的视频卡片元素
  let processedVideoCards = new WeakSet(); // 记录已处理过的卡片(避免重复处理,包括 UP主/标题检查和 tname 获取)
  let videoCardProcessQueue = new Set(); // 存储待处理的卡片,用于统一的队列处理
  let isVideoCardQueueProcessing = false; // 是否正在处理队列
  let isPageCurrentlyActive = true; // 页面是否可见
  let observerRetryCount = 0; // 观察器重试计数
  let countBlockInfo = 0; // 已屏蔽视频计数
  let countBlockAD = 0; // 已屏蔽广告计数
  let countBlockTName = 0; // 已屏蔽标签名计数
  let countBlockCM = 0; // 已屏蔽cm.bilibili.com软广计数

  // 用于不同页面URL选择器

  // 用于不同页面UP主名称选择器
  const UP_NAME_SELECTORS = [
    ".bili-video-card__info--author", // 主页
    ".bili-video-card__author", // 分类页面 -> span title
    ".name", // 视频播放页
  ];

  // 用于不同页面视频标题选择器
  const VIDEO_TITLE_SELECTORS = [
    ".bili-video-card__info--tit", // 主页
    ".bili-video-card__title", // 分类页面 -> span title
    ".title", // 视频播放页
  ];

  //#endregion

  //#region 存储管理

  // 将黑名单保存到存储中
  function saveBlacklistsToStorage() {
    GM_setValue("exactBlacklist", exactMatchBlacklist);
    GM_setValue("regexBlacklist", regexMatchBlacklist);
    GM_setValue("tNameBlacklist", tagNameBlacklist);
  }

  // 将全局配置保存到存储中
  function saveGlobalConfigToStorage() {
    GM_setValue("globalConfig", globalPluginConfig);
  }

  //#endregion

  //#region 核心屏蔽功能

  /**
   * 为视频卡片添加屏蔽按钮容器。
   * @param {string} upName - UP主名称。
   * @param {HTMLElement} cardElement - 视频卡片元素。
   * @returns {HTMLElement} 创建的容器元素。
   */
  function addBlockContainerToCard(upName, cardElement) {
    if (!cardElement.querySelector(".bilibili-blacklist-block-container")) {
      const container = document.createElement("div");
      container.classList.add("bilibili-blacklist-block-container");

      if (!cardElement.querySelector(".bilibili-blacklist-block-btn")) {
        const blockButton = createBlockUpButton(upName, cardElement);
        if (isCurrentPageVideo()) {
          // 视频播放页面的视频卡片结构特殊,需要调整位置
          cardElement.querySelector(".card-box").style.position = "relative";
          cardElement.querySelector(".card-box").appendChild(container);
        } else if (isCurrentPageCategory()) {
          // 分类页面的视频卡片结构特殊,需要调整位置
          cardElement.querySelector(".bili-video-card").appendChild(container);
        } else {
          cardElement.appendChild(container);
        }
        container.appendChild(blockButton);
      }
      return cardElement.querySelector(".bilibili-blacklist-block-container");
    }
    return cardElement.querySelector(".bilibili-blacklist-block-container");
  }

  /**
   * 隐藏给定的视频卡片。
   * @param {HTMLElement} cardElement - 要隐藏的视频卡片元素。
   * @param {string} tpye - 隐藏类型,默认为"info"。
   * @returns {void}
   *
   */
  function hideVideoCard(cardElement, type = "none") {
    const realCardToBlock = getRealVideoCardElement(cardElement);
    if (!blockedVideoCards.has(realCardToBlock)) {
      blockedVideoCards.add(realCardToBlock);
    } else {
      return;
    }
    if (!realCardToBlock) {
      console.warn(
        "[bililili-blacklist] hideVideoCard: realCardToBlock is null"
      );
      return;
    }
    if (type === "info") {
      countBlockInfo++;
    }
    if (type === "ad") {
      countBlockAD++;
    }
    if (type === "tname") {
      countBlockTName++;
    }
    if (type === "cm") {
      countBlockCM++;
    }
    if (type === "vertical") {
      countBlockTName++;
    }
    //console.log(tpye);

    if (globalPluginConfig.flagKirby) {
      addKirbyOverlayToCard(cardElement);
    } else {
      realCardToBlock.style.display = "none";
    }
  }

  /**
   * 获取应该被屏蔽的卡片的真正父元素。
   * @param {HTMLElement} cardElement - 视频卡片元素。
   * @returns {HTMLElement} 应用显示更改的实际元素。
   */
  function getRealVideoCardElement(cardElement) {
    // 搜索页面的视频卡片父元素是上一级
    if (isCurrentPageSearch()) {
      return cardElement.parentElement;
    }
    // 主页视频卡片可能有多层父元素
    if (isCurrentPageMain()) {
      if (cardElement.parentElement.classList.contains("bili-feed-card")) {
        cardElement = cardElement.parentElement;
        if (cardElement.parentElement.classList.contains("feed-card")) {
          cardElement = cardElement.parentElement;
        }
      }
    }
    return cardElement;
  }

  /**
   * 根据当前页面选择所有视频卡片。
   * @returns {NodeListOf<HTMLElement> | null} 视频卡片元素的NodeList,如果不是识别的页面则返回null。
   */
  function queryAllVideoCards() {
    if (isCurrentPageMain()) {
      return document.querySelectorAll(".bili-video-card");
    } else if (isCurrentPageVideo()) {
      return document.querySelectorAll(".video-page-card-small");
    } else if (isCurrentPageCategory()) {
      return document.querySelectorAll(".feed-card");
    } else if (isCurrentPageSearch()) {
      return document.querySelectorAll(".bili-video-card");
    }
    return null;
  }

  /**
   * 扫描并处理视频卡片进行屏蔽。
   */
  function scanAndBlockVideoCards() {
    const now = Date.now();
    // 限制扫描频率,防止性能问题
    if (
      isBlockingOperationInProgress ||
      now - lastBlockScanExecutionTime < globalPluginConfig.blockScanInterval
    ) {
      return;
    }

    isBlockingOperationInProgress = true;
    lastBlockScanExecutionTime = now;

    try {
      const videoCards = queryAllVideoCards();
      if (!videoCards) return;

      videoCards.forEach((card) => {
        // 如果卡片已经处理过,则跳过
        if (processedVideoCards.has(card)) {
          return;
        }
        const { upName, videoTitle } = getVideoCardInfo(card);
        // 如果获取到UP主名称和视频标题,则添加屏蔽按钮
        if (upName && videoTitle) {
          addBlockContainerToCard(upName, card);

          // --- 根据 flagHideOnLoad 开关决定是否立即隐藏卡片 ---
          const realCard = getRealVideoCardElement(card);
          if (globalPluginConfig.flagHideOnLoad && !isShowAllVideos) {
            // 只有在“显示全部”模式关闭时才执行
            if (globalPluginConfig.flagKirby) {
              addKirbyOverlayToCard(card); // 卡比模式下添加遮罩
              realCard.style.display = "block"; // 确保卡片本身是显示的
            } else {
              realCard.style.display = "none"; // 非卡比模式下直接隐藏
            }
          }
        }
        // --- 立即隐藏卡片的逻辑结束 ---

        // 将卡片添加到处理队列
        videoCardProcessQueue.add(card);
      });

      // 如果队列中有待处理的卡片且当前未在处理中,则开始处理队列
      if (videoCardProcessQueue.size > 0 && !isVideoCardQueueProcessing) {
        processVideoCardQueue();
      }

      // 刷新屏蔽计数显示
      refreshBlockCountDisplay();
      // 修正主页布局
      fixMainPageLayout();
    } finally {
      isBlockingOperationInProgress = false;
    }
  }

  /**
   * 修正主页在屏蔽后的布局。
   */
  function fixMainPageLayout() {
    if (!isCurrentPageMain()) return;
    const container = document.querySelector(
      ".recommended-container_floor-aside .container"
    );

    if (container) {
      const children = container.children;
      let visibleIndex = 0;
      // 调整可见卡片的边距,使布局更紧凑
      for (let i = 0; i < children.length; i++) {
        const child = children[i];
        if (child.style.display !== "none") {
          if (visibleIndex <= 6) {
            child.style.marginTop = "0px";
          } else if (visibleIndex < 12) {
            child.style.marginTop = "24px";
          } else {
            break;
          }
          visibleIndex++;
        }
      }
    }
  }

  /**
   * 切换所有被屏蔽视频卡片的显示。
   */
  function toggleShowAllBlockedVideos() {
    isShowAllVideos = !isShowAllVideos;
    blockedVideoCards.forEach((card) => {
      if (globalPluginConfig.flagKirby) {
        const kirbyOverlay = card.querySelector("#bilibili-blacklist-kirby");
        if (kirbyOverlay) {
          kirbyOverlay.style.display = isShowAllVideos ? "none" : "block";
        }
        card.style.display = "block";
      } else {
        card.style.display = isShowAllVideos ? "block" : "none";
      }
    });
    tempUnblockButton.textContent = isShowAllVideos ? "恢复屏蔽" : "取消屏蔽";
    tempUnblockButton.style.background = isShowAllVideos
      ? "#dddddd"
      : "#fb7299";
  }

  /**
   * 从视频卡片中检索UP主名称和视频标题。
   * @param {HTMLElement} cardElement - 视频卡片元素。
   * @returns {{upName: string, videoTitle: string}} 包含UP主名称和视频标题的对象。
   */
  function getVideoCardInfo(cardElement) {
    let upName = "";
    let videoTitle = "";

    const upNameElements = cardElement.querySelectorAll(
      UP_NAME_SELECTORS.join(", ")
    );
    if (upNameElements.length > 0) {
      upName = upNameElements[0].textContent.trim();
      if (isCurrentPageCategory()) {
        // 分类页面的UP主名称可能包含其他信息,需要进一步处理
        upName = upName.split(" · ")[0].trim();
      }
    }

    const titleElements = cardElement.querySelectorAll(
      VIDEO_TITLE_SELECTORS.join(", ")
    );
    if (titleElements.length > 0) {
      videoTitle = titleElements[0].textContent.trim();
    }
    return { upName, videoTitle };
  }

  /**
   * 检查UP主名称或标题是否在黑名单中。
   * @param {string} upName - 要检查的UP主名称。
   * @param {string} title - 要检查的视频标题。
   * @returns {boolean} 如果在黑名单中则返回true,否则返回false。
   */
  function isBlacklisted(upName, title) {
    const lowerCaseUpName = upName.toLowerCase();
    // 检查精确匹配黑名单
    if (
      exactMatchBlacklist.some((item) => item.toLowerCase() === lowerCaseUpName)
    ) {
      return true;
    }

    // 检查正则匹配黑名单
    if (
      regexMatchBlacklist.some((regex) => new RegExp(regex, "i").test(upName))
    ) {
      return true;
    }
    if (
      regexMatchBlacklist.some((regex) => new RegExp(regex, "i").test(title))
    ) {
      return true;
    }
    return false;
  }

  /**
   * 将UP主名称添加到精确匹配黑名单并刷新。
   * @param {string} upName - 要添加的UP主名称。
   * @param {HTMLElement} [cardElement=null] - 添加后要隐藏的视频卡片元素。
   */
  function addToExactBlacklist(upName, cardElement = null) {
    try {
      if (!upName) return;
      if (!exactMatchBlacklist.includes(upName)) {
        exactMatchBlacklist.push(upName);
        saveBlacklistsToStorage();
        refreshAllPanelTabs();
        if (cardElement) {
          hideVideoCard(cardElement);
        }
      }
    } catch (e) {
      console.error("[bilibili-blacklist] 添加黑名单出错:", e);
    }
  }

  /**
   * 从精确匹配黑名单中移除UP主名称。
   * @param {string} upName - 要移除的UP主名称。
   */
  function removeFromExactBlacklist(upName) {
    try {
      if (exactMatchBlacklist.includes(upName)) {
        const index = exactMatchBlacklist.indexOf(upName);
        exactMatchBlacklist.splice(index, 1);
        saveBlacklistsToStorage();
        refreshExactMatchList();
      }
    } catch (e) {
      console.error("[bilibili-blacklist] 移除黑名单出错:", e);
    }
  }

  /**
   * 将标签名添加到黑名单并刷新。
   * @param {string} tagName - 要添加的标签名。
   * @param {HTMLElement} [cardElement=null] - 添加后要隐藏的视频卡片元素。
   */
  function addToTagNameBlacklist(tagName, cardElement = null) {
    try {
      if (!tagName) {
        return;
      }
      if (!tagNameBlacklist.includes(tagName)) {
        tagNameBlacklist.push(tagName);
        saveBlacklistsToStorage();
        refreshAllPanelTabs();
        if (cardElement) {
          hideVideoCard(cardElement);
        }
      }
    } catch (e) {
      console.error("[bilibili-blacklist] 添加标签黑名单出错:", e);
    }
  }

  /**
   * 从黑名单中移除标签名。
   * @param {string} tagName - 要移除的标签名。
   */
  function removeFromTagNameBlacklist(tagName) {
    try {
      if (tagNameBlacklist.includes(tagName)) {
        const index = tagNameBlacklist.indexOf(tagName);
        tagNameBlacklist.splice(index, 1);
        saveBlacklistsToStorage();
        refreshTagNameList();
      }
    } catch (e) {
      console.error("[bilibili-blacklist] 移除标签黑名单出错:", e);
    }
  }

  //#endregion

  //#region 视频数据获取
  /**
   * 获取视频卡片的链接。
   * @param {HTMLElement} cardElement - 视频卡片元素。
   * @returns {string|null} 视频链接,如果未找到则返回null。
   */
  function getCardHrefLink(cardElement) {
    const hrefLink = cardElement.querySelector("a");
    if (hrefLink) {
      return hrefLink.getAttribute("href");
    }
    return null;
  }

  function checkLinkCM(link) {
    if (!link) return false;
    // 如果是cm.bilibili.com的链接,且启用了CM广告屏蔽,则隐藏卡片
    if (link.match(/cm.bilibili.com/) && globalPluginConfig.flagCM) {
      return true;
    }
    return false;
  }
  /**
   * 从视频卡片的链接中提取BV ID。
   * 还处理cm.bilibili.com广告的屏蔽。
   * @param {HTMLElement} cardElement - 视频卡片元素。
   * @returns {string|null} BV ID,如果未找到/被屏蔽则返回null。
   */
  function getLinkBvId(link) {
    try {
      if (!link) {
        return null;
      } else {
        const bv = link.match(/BV\w+/);
        return bv ? bv[0] : null;
      }
    } catch (e) {
      return null;
    }
  }

  /**
   * 使用BV ID从Bilibili API获取视频信息。
   * @param {string} bvid - 视频的BV ID。
   * @returns {Promise<object|null>} 解析为视频数据或null的Promise。
   */
  async function getBilibiliVideoApiData(bvid) {
    if (!bvid || 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);
    }
  }

  /**
   * 检查卡片是否包含任何黑名单标签。
   * @param {HTMLElement} cardElement - 视频卡片元素。
   * @returns {boolean} 如果有任何标签被列入黑名单,则返回true,否则返回false。
   */
  function isCardBlacklistedByTagName(cardElement) {
    const tnameGroup = cardElement.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 (tagNameBlacklist.includes(tname)) {
          return true;
        }
      }
    }
    return false;
  }

  /**
   * 处理视频卡片队列进行屏蔽。
   */
  async function processVideoCardQueue() {
    if (isVideoCardQueueProcessing) return;
    isVideoCardQueueProcessing = true;

    while (videoCardProcessQueue.size > 0) {
      // 如果页面不可见,则暂停处理
      if (!isPageCurrentlyActive) {
        await sleep(1000);
        continue;
      }

      const iterator = videoCardProcessQueue.values();
      const card = iterator.next().value;
      videoCardProcessQueue.delete(card);

      if (!card || processedVideoCards.has(card)) {
        continue;
      }

      let shouldHide = false;
      let blockType = "none";
      // 如果启用了标签屏蔽且当前卡片未被隐藏
      const link = getCardHrefLink(card);
      if (checkLinkCM(link)) {
        shouldHide = true;
        blockType = "cm";
      }
      const { upName, videoTitle } = getVideoCardInfo(card);
      if (upName && videoTitle && !shouldHide) {
        // 如果UP主名称或标题在黑名单中,且启用了信息屏蔽
        if (isBlacklisted(upName, videoTitle) && globalPluginConfig.flagInfo) {
          shouldHide = true;
          blockType = "info";
        }
      } else {
        // 如果无法获取UP主名称和标题,但卡片已被隐藏或有Kirby覆盖,则也认为应该隐藏
        if (
          getRealVideoCardElement(card).style.display === "none" &&
          !globalPluginConfig.flagKirby
        ) {
          shouldHide = true;
        } else if (
          getRealVideoCardElement(card).querySelector(
            "#bilibili-blacklist-kirby"
          )
        ) {
          shouldHide = true;
        }
      }

      if (
        (globalPluginConfig.flagTName || globalPluginConfig.flagVertical) &&
        !shouldHide
      ) {
        const bvId = getLinkBvId(link);
        // 如果存在BV ID且卡片尚未添加标签组
        if (bvId && !card.querySelector(".bilibili-blacklist-tname-group")) {
          const data = await getBilibiliVideoApiData(bvId);
          if (data) {
            const container = card.querySelector(
              ".bilibili-blacklist-block-container"
            );
            if (container) {
              const tnameGroup = document.createElement("div");
              tnameGroup.className = "bilibili-blacklist-tname-group";
              let hasTname = false;

              if (data.tname) {
                const btn = createTNameBlockButton(data.tname, card);
                tnameGroup.appendChild(btn);
                hasTname = true;
              }
              if (data.tname_v2) {
                const tnameElement = createTNameBlockButton(
                  data.tname_v2,
                  card
                );
                tnameGroup.appendChild(tnameElement);
                hasTname = true;
              }
              if (hasTname) {
                container.appendChild(tnameGroup);
              }
            }

            if (isCardBlacklistedByTagName(card)) {
              shouldHide = true;
              blockType = "tname";
            }
            // 如果启用了垂直视频屏蔽
            if (
              data.dimension.width &&
              data.dimension.height &&
              !shouldHide &&
              globalPluginConfig.flagVertical
            ) {
              const dimension = data.dimension.width / data.dimension.height;
              if (dimension < globalPluginConfig.verticalScaleThreshold) {
                shouldHide = true;
                blockType = "vertical";
              }
            }
          }
        }
      }

      if (shouldHide) {
        hideVideoCard(card, blockType);
      } else {
        const realCardToDisplay = getRealVideoCardElement(card);
        if (blockedVideoCards.has(realCardToDisplay)) {
          blockedVideoCards.delete(realCardToDisplay);
        }
        if (globalPluginConfig.flagKirby) {
          removeKirbyOverlay(card);
        }
        realCardToDisplay.style.display = "block";
      }

      processedVideoCards.add(card); // 标记卡片已处理

      await sleep(globalPluginConfig.processQueueInterval || 100);
    }
    isVideoCardQueueProcessing = false;
    refreshBlockCountDisplay();
  }

  // 异步等待函数
  function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  //#endregion

  //#region 页面可见性和焦点监听器

  // 监听页面可见性变化
  document.addEventListener("visibilitychange", () => {
    isPageCurrentlyActive = !document.hidden;
  });

  // 监听窗口焦点获取
  window.addEventListener("focus", () => {
    isPageCurrentlyActive = true;
  });

  // 监听窗口焦点失去
  window.addEventListener("blur", () => {
    isPageCurrentlyActive = false;
  });

  //#endregion

  //#region UI元素创建和管理

  /**
   * 为UP主创建屏蔽按钮,显示在视频卡片上。
   * @param {string} upName - UP主名称。
   * @param {HTMLElement} cardElement - 视频卡片元素。
   * @returns {HTMLDivElement} 创建的按钮元素。
   */
  function createBlockUpButton(upName, cardElement) {
    const button = document.createElement("div");
    button.className = "bilibili-blacklist-block-btn";
    button.innerHTML = "屏蔽";
    button.title = `屏蔽: ${upName}`;

    button.addEventListener("click", (e) => {
      e.stopPropagation(); // 阻止事件冒泡,防止触发视频点击事件
      addToExactBlacklist(upName, cardElement);
    });

    return button;
  }

  /**
   * 为标签名创建屏蔽按钮,显示在视频卡片上。
   * @param {string} tagName - 标签名。
   * @param {HTMLElement} cardElement - 视频卡片元素。
   * @returns {HTMLSpanElement} 创建的按钮元素。
   */
  function createTNameBlockButton(tagName, cardElement) {
    const button = document.createElement("span");
    button.className = "bilibili-blacklist-tname";
    button.innerHTML = `${tagName}`;
    button.title = `屏蔽: ${tagName}`;

    button.addEventListener("click", (e) => {
      e.stopPropagation(); // 阻止事件冒泡
      addToTagNameBlacklist(tagName, cardElement);
    });

    return button;
  }

  /**
   * 将黑名单管理器按钮添加到右侧导航条。
   */
  function addBlacklistManagerButton() {
    const rightEntry = document.querySelector(".right-entry");
    if (!rightEntry) {
      console.warn("[bilibili-blacklist] 未找到右侧导航栏");
      return;
    } else if (
      !rightEntry.querySelector("#bilibili-blacklist-manager-button")
    ) {
      const listItem = document.createElement("li");
      listItem.id = "bilibili-blacklist-manager-button";
      listItem.style.cursor = "pointer";
      listItem.className = "v-popover-wrap";

      const button = document.createElement("div");
      button.className = "right-entry-item";
      button.style.display = "flex";
      button.style.flexDirection = "column";
      button.style.alignItems = "center";
      button.style.justifyContent = "center";

      const icon = document.createElement("div");
      icon.className = "right-entry__outside";
      icon.innerHTML = getKirbySVG(); // 获取卡比SVG图标
      icon.style.marginBottom = "-5px";

      blockCountDisplayElement = document.createElement("span");
      blockCountDisplayElement.textContent = `0`;

      button.appendChild(icon);
      button.appendChild(blockCountDisplayElement);
      listItem.appendChild(button);

      // 将按钮插入到导航栏的特定位置
      if (rightEntry.children.length > 1) {
        rightEntry.insertBefore(listItem, rightEntry.children[1]);
      } else {
        rightEntry.appendChild(listItem);
      }

      // 点击按钮显示/隐藏管理面板
      listItem.addEventListener("click", () => {
        if (managerPanel.style.display === "none") {
          managerPanel.style.display = "flex";
        } else {
          managerPanel.style.display = "none";
        }
      });
    }
  }

  /**
   * 更新已屏蔽视频的显示计数。
   */
  function refreshBlockCountDisplay() {
    if (blockCountDisplayElement) {
      blockCountDisplayElement.textContent = `${blockedVideoCards.size}`;
    }
    countBlockInfo;
    if (blockCountTitleElement) {
      blockCountTitleElement.textContent = `已屏蔽视频 (${blockedVideoCards.size} = ${countBlockInfo} + ${countBlockAD} + ${countBlockCM} + ${countBlockTName})`;
    }
  }

  // 辅助函数:创建通用按钮
  function createPanelButton(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 createBlacklistListItem(contentText, onRemoveClick) {
    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";
    const removeBtn = createPanelButton("移除", "#f56c6c", onRemoveClick);

    item.appendChild(content);
    item.appendChild(removeBtn);
    return item;
  }

  /**
   * 刷新面板中的精确匹配黑名单显示。
   */
  function refreshExactMatchList() {
    if (!exactMatchListElement) {
      if (!isBlacklistPanelCreated()) {
        return;
      }
      exactMatchListElement = document.querySelector(
        "#bilibili-blacklist-exact-list"
      );
      if (!exactMatchListElement) {
        console.warn("[Bilibili-Blacklist] exactMatchListElement 未定义");
        return;
      }
    }
    exactMatchListElement.innerHTML = "";
    exactMatchBlacklist.forEach((upName) => {
      const item = createBlacklistListItem(upName, () => {
        removeFromExactBlacklist(upName);
      });
      exactMatchListElement.appendChild(item);
    });
    // 反转列表顺序,使最新添加的显示在顶部
    Array.from(exactMatchListElement.children)
      .reverse()
      .forEach((item) => exactMatchListElement.appendChild(item));

    if (exactMatchBlacklist.length === 0) {
      const empty = document.createElement("div");
      empty.textContent = "暂无精确匹配屏蔽UP主";
      empty.style.textAlign = "center";
      empty.style.padding = "16px";
      empty.style.color = "#999";
      exactMatchListElement.appendChild(empty);
    }
  }

  /**
   * 刷新面板中的正则匹配黑名单显示。
   */
  function refreshRegexMatchList() {
    if (!regexMatchListElement) {
      if (!isBlacklistPanelCreated()) {
        return;
      }
      regexMatchListElement = document.querySelector(
        "#bilibili-blacklist-regex-list"
      );
      if (!regexMatchListElement) {
        console.warn("[Bilibili-Blacklist] regexMatchListElement 未定义");
        return;
      }
    }
    regexMatchListElement.innerHTML = "";

    regexMatchBlacklist.forEach((regex, index) => {
      const item = createBlacklistListItem(regex, () => {
        regexMatchBlacklist.splice(index, 1);
        saveBlacklistsToStorage();
        refreshRegexMatchList();
      });
      regexMatchListElement.appendChild(item);
    });

    // 反转列表顺序,使最新添加的显示在顶部
    Array.from(regexMatchListElement.children)
      .reverse()
      .forEach((item) => regexMatchListElement.appendChild(item));

    if (regexMatchBlacklist.length === 0) {
      const empty = document.createElement("div");
      empty.textContent = "暂无正则匹配屏蔽规则";
      empty.style.textAlign = "center";
      empty.style.padding = "16px";
      empty.style.color = "#999";
      regexMatchListElement.appendChild(empty);
    }
  }

  /**
   * 刷新面板中的标签名黑名单显示。
   */
  function refreshTagNameList() {
    if (!tagNameListElement) {
      if (!isBlacklistPanelCreated()) {
        return;
      }
      tagNameListElement = document.querySelector(
        "#bilibili-blacklist-tname-list"
      );
      if (!tagNameListElement) {
        console.warn("[Bilibili-Blacklist] tagNameListElement 未定义");
        return;
      }
    }
    tagNameListElement.innerHTML = "";

    tagNameBlacklist.forEach((tagName) => {
      const item = createBlacklistListItem(tagName, () => {
        removeFromTagNameBlacklist(tagName);
      });
      tagNameListElement.appendChild(item);
    });
    // 反转列表顺序,使最新添加的显示在顶部
    Array.from(tagNameListElement.children)
      .reverse()
      .forEach((item) => tagNameListElement.appendChild(item));

    if (tagNameBlacklist.length === 0) {
      const empty = document.createElement("div");
      empty.textContent = "暂无标签屏蔽规则";
      empty.style.textAlign = "center";
      empty.style.padding = "16px";
      empty.style.color = "#999";
      tagNameListElement.appendChild(empty);
    }
  }

  // 辅助函数:为设置创建切换按钮
  function createSettingToggleButton(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 = globalPluginConfig[configKey] ? "开启" : "关闭";
      button.style.backgroundColor = globalPluginConfig[configKey]
        ? "#fb7299"
        : "#909399";
    }

    button.addEventListener("click", () => {
      globalPluginConfig[configKey] = !globalPluginConfig[configKey];
      refreshButtonAppearance();
      saveGlobalConfigToStorage();
    });

    refreshButtonAppearance(); // 初始化按钮外观

    container.appendChild(label);
    container.appendChild(button);

    return container;
  }
  // 辅助函数:为设置创建输入文本
  function createSettingInput(labelText, configKey, title = null) {
    // 卡片扫描间隔设置
    const Container = document.createElement("div");
    Container.style.display = "flex";
    Container.style.alignItems = "center";
    Container.style.marginTop = "16px";
    Container.style.gap = "8px";
    Container.title = title;

    const Label = document.createElement("span");
    Label.textContent = labelText;
    Label.style.flex = "1";

    const Input = document.createElement("input");
    Input.type = "number";
    Input.min = "0";
    Input.value = globalPluginConfig[configKey];
    Input.style.width = "100px";
    Input.style.padding = "6px";
    Input.style.border = "1px solid #ddd";
    Input.style.borderRadius = "4px";

    const Button = document.createElement("button");
    Button.textContent = "保存";
    Button.style.padding = "6px 12px";
    Button.style.backgroundColor = "#fb7299";
    Button.style.color = "#fff";
    Button.style.border = "none";
    Button.style.borderRadius = "4px";
    Button.style.cursor = "pointer";

    Button.addEventListener("click", () => {
      const val = parseFloat(Input.value, 10);
      if (!isNaN(val) && val >= 0) {
        globalPluginConfig[configKey] = val;
        saveGlobalConfigToStorage();
      } else {
        alert("请输入有效的非负数字!");
      }
    });
    Container.appendChild(Label);
    Container.appendChild(Input);
    Container.appendChild(Button);

    return Container;
  }
  /**
   * 刷新面板中的配置设置显示。
   */
  function refreshConfigSettings() {
    if (!configListElement) {
      if (!isBlacklistPanelCreated()) {
        return;
      }
      configListElement = document.querySelector(
        "#bilibili-blacklist-config-list"
      );
      if (!configListElement) {
        console.warn("[Bilibili-Blacklist] configListElement 未定义");
        return;
      }
    }
    configListElement.innerHTML = "";

    // 临时开关按钮
    const tempToggleContainer = document.createElement("div");
    tempToggleContainer.style.display = "flex";
    tempToggleContainer.style.alignItems = "center";
    tempToggleContainer.style.marginBottom = "8px";
    tempToggleContainer.style.gap = "8px";
    tempToggleContainer.style.margin = "20px 0";

    const tempToggleLabel = document.createElement("span");
    tempToggleLabel.textContent = "临时开关";
    tempToggleLabel.style.flex = "1";

    tempUnblockButton = document.createElement("button");
    tempUnblockButton.textContent = isShowAllVideos ? "恢复屏蔽" : "取消屏蔽";
    tempUnblockButton.style.background = isShowAllVideos
      ? "#dddddd"
      : "#fb7299";
    tempUnblockButton.style.padding = "6px 12px";
    tempUnblockButton.style.border = "none";
    tempUnblockButton.style.cursor = "pointer";
    tempUnblockButton.style.color = "#fff";
    tempUnblockButton.addEventListener("click", toggleShowAllBlockedVideos);

    tempToggleContainer.appendChild(tempToggleLabel);
    tempToggleContainer.appendChild(tempUnblockButton);
    configListElement.appendChild(tempToggleContainer);

    const title = document.createElement("h4");
    title.textContent = "全局配置开关(部分功能刷新后生效)";
    title.style.fontWeight = "bold";
    title.style.marginBottom = "12px";
    configListElement.appendChild(title);

    // 添加配置切换按钮
    configListElement.appendChild(
      createSettingToggleButton(
        "屏蔽标题/Up主名",
        "flagInfo",
        "屏蔽标题/Up主名"
      )
    );
    configListElement.appendChild(
      createSettingToggleButton(
        "屏蔽分类标签",
        "flagTName",
        "通过请求API获取分类标签"
      )
    );
    configListElement.appendChild(
      createSettingToggleButton(
        "屏蔽竖屏视频",
        "flagVertical",
        "通过请求API获取视频分辨率"
      )
    );
    configListElement.appendChild(
      createSettingToggleButton("屏蔽主页推荐", "flagAD", "直播/广告/分区推送")
    );
    configListElement.appendChild(
      createSettingToggleButton(
        "屏蔽主页视频软广",
        "flagCM",
        "cm.bilibili.com软广"
      )
    );

    //分割线
    const hr = document.createElement("hr");
    hr.style.margin = "12px 0";
    hr.style.border = "none";
    hr.style.borderTop = "2px solid #ddd";
    configListElement.appendChild(hr);

    configListElement.appendChild(
      createSettingToggleButton("遮挡被屏蔽视频", "flagKirby", "更加温和的方式")
    );
    configListElement.appendChild(
      createSettingToggleButton(
        "加载时立即隐藏卡片",
        "flagHideOnLoad",
        "新卡片加载出来时是否立即隐藏,待处理完成后再决定显示或继续屏蔽。关闭此功能可能会导致卡片先显示后隐藏的闪烁。"
      )
    );

    configListElement.appendChild(
      createSettingInput(
        "卡片扫描间隔 (ms):",
        "blockScanInterval",
        "扫描新卡片的间隔时间,单位 ms。值越小,新卡片隐藏越快,但可能会增加CPU负担。建议值 200ms。"
      )
    );

    configListElement.appendChild(
      createSettingInput(
        "视频信息API请求间隔 (ms):",
        "processQueueInterval",
        "每个视频获取分类标签/视频分辨率时的API请求间隔时间,单位 ms。间隔时间越长,越不容易触发B站API限速。建议值 200ms。"
      )
    );
    configListElement.appendChild(
      createSettingInput(
        "竖屏视频比例阈值:",
        "verticalScaleThreshold",
        "获取的视频API信息后,判断视频是否为竖屏(长 除于 宽)的阈值。建议值 0.7。"
      )
    );
  }

  /**
   * 刷新黑名单管理面板中的所有标签页。
   */
  function refreshAllPanelTabs() {
    refreshExactMatchList();
    refreshRegexMatchList();
    refreshTagNameList();
    refreshConfigSettings();
  }

  /**
   * 检查黑名单管理面板是否已创建并存在于DOM中。
   * 如果找到,则设置全局 `managerPanel` 引用。
   * @returns {boolean} 如果面板存在则返回true,否则返回false。
   */
  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-manager-panel"; // 确保ID唯一

    // 设置面板样式
    managerPanel.style.position = "fixed";
    managerPanel.style.top = "50%";
    managerPanel.style.left = "50%";
    managerPanel.style.transform = "translate(-50%, -50%)";
    managerPanel.style.width = "500px";
    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);
    });

    // 创建面板头部
    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";

    blockCountTitleElement = document.createElement("h3");
    blockCountTitleElement.style.margin = "0";
    blockCountTitleElement.style.fontWeight = "500";
    blockCountTitleElement.title = "总数 =(UP/标题 + 广告 + CM + 分类/竖屏)";

    const closeBtn = document.createElement("button");
    closeBtn.textContent = "×";
    closeBtn.style.background = "none";
    closeBtn.style.border = "none";
    closeBtn.style.cursor = "pointer";
    closeBtn.style.padding = "0 8px";
    closeBtn.addEventListener("click", () => {
      managerPanel.style.display = "none";
    });

    header.appendChild(blockCountTitleElement);
    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 && !regexMatchBlacklist.includes(regex)) {
        try {
          new RegExp(regex); // 验证正则表达式
          regexMatchBlacklist.push(regex);
          saveBlacklistsToStorage();
          regexInput.value = "";
          refreshRegexMatchList();
        } catch (e) {
          alert("无效的正则表达式: " + e.message);
        }
      }
    });
    addRegexContainer.appendChild(regexInput);
    addRegexContainer.appendChild(addRegexBtn);
    regexContent.appendChild(addRegexContainer);

    // 创建列表元素
    exactMatchListElement = document.createElement("ul");
    exactMatchListElement.id = "bilibili-blacklist-exact-list";
    exactMatchListElement.style.listStyle = "none";
    exactMatchListElement.style.padding = "0";
    exactMatchListElement.style.margin = "0";

    regexMatchListElement = document.createElement("ul");
    regexMatchListElement.id = "bilibili-blacklist-regex-list";
    regexMatchListElement.style.listStyle = "none";
    regexMatchListElement.style.padding = "0";
    regexMatchListElement.style.margin = "0";

    tagNameListElement = document.createElement("ul");
    tagNameListElement.id = "bilibili-blacklist-tname-list";
    tagNameListElement.style.listStyle = "none";
    tagNameListElement.style.padding = "0";
    tagNameListElement.style.margin = "0";

    configListElement = document.createElement("ul");
    configListElement.id = "bilibili-blacklist-config-list";
    configListElement.style.listStyle = "none";
    configListElement.style.padding = "0";
    configListElement.style.margin = "0";

    refreshAllPanelTabs(); // 初始化所有标签页内容
    exactContent.appendChild(exactMatchListElement);
    regexContent.appendChild(regexMatchListElement);
    tnameContent.appendChild(tagNameListElement);
    configContent.appendChild(configListElement);

    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;
  }

  /**
   * 为插件添加全局CSS样式。
   */
  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-manager-panel {
            font-size: 15px;
        }
        /* 按钮悬停效果 */
        #bilibili-blacklist-manager-panel button {
            transition: background-color 0.2s;
        }
        #bilibili-blacklist-manager-panel button:hover {
            opacity: 0.9;
        }
        /* 管理按钮悬停效果 */
        #bilibili-blacklist-manager-button:hover svg {
            transform: scale(1.1);
        }
        #bilibili-blacklist-manager-button svg {
            transition: transform 0.2s;
        }
        /* 输入框聚焦效果 */
        #"bilibili-blacklist-manager-panel input:focus {
            outline: none;
            border-color: #fb7299 !important;
        }
        /*灰度效果*/
        .bilibili-blacklist-grayscale {
           filter: grayscale(95%);
        }
    `);

  /**
   * 返回卡比图标的SVG代码。
   * @returns {string} SVG字符串。
   */
  function getKirbySVG() {
    return `
        <svg width="35" height="35" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"  >
            <ellipse cx="70" cy="160" rx="30" ry="15" fill="#cc3333" />
            <ellipse cx="130" cy="160" rx="30" ry="15" fill="#cc3333" />
            <ellipse cx="50" cy="120" rx="20" ry="20" fill="#ffb6c1" />
            <ellipse cx="150" cy="120" rx="20" ry="20" fill="#ffb6c1" />
            <circle cx="100" cy="110" r="60" fill="#ffb6c1" />
            <ellipse cx="80" cy="90" rx="10" ry="22" fill="blue" />
            <ellipse cx="80" cy="88" rx="10" ry="15" fill="black" />
            <ellipse cx="80" cy="82" rx="8" ry="12" fill="#ffffff" />
            <ellipse cx="80" cy="90" rx="10" ry="22" fill="#00000000" stroke="#000000" strokeWidth="4" />
            <ellipse cx="120" cy="90" rx="10" ry="22" fill="blue" />
            <ellipse cx="120" cy="88" rx="10" ry="15" fill="black" />
            <ellipse cx="120" cy="82" rx="8" ry="12" fill="#ffffff" />
            <ellipse cx="120" cy="90" rx="10" ry="22" fill="#00000000" stroke="#000000" strokeWidth="4" />
            <ellipse cx="60" cy="110" rx="8" ry="5" fill="#ff4466" />
            <ellipse cx="140" cy="110" rx="8" ry="5" fill="#ff4466" />
            <path d="M 90 118 Q 100 125, 110 118" stroke="black" strokeWidth="3" fill="transparent" />
        </svg>
    `;
  }

  /**
   * 为视频卡片添加卡比主题的覆盖层。
   * @param {HTMLElement} cardElement - 视频卡片元素。
   */
  function addKirbyOverlayToCard(cardElement) {
    const kirbyWrapper = document.createElement("div");
    // 如果已经有Kirby覆盖层,则不重复添加
    if (cardElement.querySelector("#bilibili-blacklist-kirby") != null) return;
    kirbyWrapper.innerHTML = getKirbySVG();
    kirbyWrapper.id = "bilibili-blacklist-kirby";

    const justifyContent = isCurrentPageVideo() ? "flex-start" : "center";
    const alignItems = isCurrentPageVideo() ? "flex-start" : "center";
    Object.assign(kirbyWrapper.style, {
      position: "absolute",
      top: "0",
      left: "0",
      width: "100%",
      height: "100%",
      pointerEvents: "none",
      display: "flex",
      justifyContent: `${justifyContent}`,
      alignItems: `${alignItems}`,
      zIndex: "10",
      backgroundColor: "rgba(255, 255, 255, 0.7)",
      backdropFilter: "blur(5px)",
      WebkitBackdropFilter: "blur(5px)", // 兼容性
      borderRadius: "inherit",
      border: "1px solid rgba(255, 255, 255, 0.5)",
    });

    const svg = kirbyWrapper.querySelector("svg");
    if (svg) {
      const cardRect = cardElement.getBoundingClientRect();
      const size = Math.min(cardRect.width, cardRect.height) * 1.0;
      svg.setAttribute("width", `${size}px`);
      svg.setAttribute("height", `${size}px`);
      svg.setAttribute("bottom", `${cardRect.height - size}px`);
      svg.style.opacity = "0.15";
      svg.style.filter = "none";
      if (isCurrentPageVideo()) {
        svg.style.marginTop = "-10px"; // 视频播放页的微调
      } else {
        svg.style.marginTop = "-40px"; // 其他页面的微调
      }
    }

    // 确保卡片有position属性以便子元素绝对定位
    const cardStyle = getComputedStyle(cardElement);
    if (cardStyle.position === "static" || !cardStyle.position) {
      cardElement.style.position = "relative";
    }

    cardElement.appendChild(kirbyWrapper);
  }

  /**
   * 从视频卡片中移除卡比覆盖层。
   * @param {HTMLElement} cardElement - 视频卡片元素。
   */
  function removeKirbyOverlay(cardElement) {
    const kirbyWrapper = cardElement.querySelector("#bilibili-blacklist-kirby");
    if (kirbyWrapper) {
      kirbyWrapper.remove();
    }
  }

  //#endregion

  //#region 变动观察器

  // MutationObserver 检测动态加载的新内容
  const contentObserver = new MutationObserver((mutations) => {
    let shouldCheck = false;
    // 对视频播放页进行优化,只在实际添加了可见元素时触发扫描
    if (isCurrentPageVideo()) {
      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;
        }
      });
    }

    if (shouldCheck) {
      // 使用setTimeout延迟扫描,避免短时间内多次触发
      setTimeout(() => {
        scanAndBlockVideoCards();
        if (isCurrentPageMain()) {
          blockMainPageAds(); // 主页广告屏蔽
        }
        if (isCurrentPageVideo()) {
          blockVideoPageAds(); // 视频页广告屏蔽
        }
        if (!document.getElementById("bilibili-blacklist-manager-button")) {
          addBlacklistManagerButton(); // 确保管理按钮存在
        }
      }, globalPluginConfig.blockScanInterval);
    }
  });

  /**
   * 在指定容器上初始化MutationObserver。
   * @param {string} containerIdOrSelector - 要观察的容器的ID或CSS选择器。
   * @returns {boolean} 如果观察器成功初始化则返回true,否则返回false。
   */
  function initializeObserver(containerIdOrSelector) {
    const rootNode =
      document.getElementById(containerIdOrSelector) ||
      document.querySelector(containerIdOrSelector) ||
      document.documentElement; // 默认观察整个文档

    if (rootNode) {
      contentObserver.observe(rootNode, {
        childList: true,
        subtree: true,
      });
      return true;
    } else {
      // 如果未找到根节点,则进行重试
      setTimeout(() => initializeObserver(containerIdOrSelector), 500);
      console.warn("[bilibili-blacklist] 未找到根节点,正在重试...");
      observerRetryCount++;

      if (observerRetryCount > 10) {
        console.error("[bilibili-blacklist] 重试次数过多,停止重试。");
        return false;
      }
    }
  }

  //#endregion

  //#region 页面检测和初始化

  /**
   * 根据当前页面初始化脚本。
   */
  function initializeScript() {
    // 重置状态变量
    isBlockingOperationInProgress = false;
    lastBlockScanExecutionTime = 0;
    blockedVideoCards = new Set();
    videoCardProcessQueue = new Set();
    processedVideoCards = new WeakSet();

    // 根据当前页面URL判断并初始化
    if (isCurrentPageMain()) {
      initializeMainPage();
      blockMainPageAds();
    } else if (isCurrentPageSearch()) {
      initializeSearchPage();
      blockMainPageAds(); // 搜索页也进行主页广告屏蔽
    } else if (isCurrentPageVideo()) {
      initializeVideoPage();
    } else if (isCurrentPageCategory()) {
      initializeCategoryPage();
    } else if (isCurrentUserSpace()) {
      initializeUserSpace();
    } else {
      return; // 不支持的页面不进行初始化
    }
    createBlacklistPanel(); // 创建管理面板
    console.log("[bilibili-blacklist] 脚本已加载🥔");
  }

  // 监听DOMContentLoaded并检查readyState以进行早期初始化
  document.addEventListener("DOMContentLoaded", initializeScript);
  if (
    document.readyState === "complete" ||
    document.readyState === "interactive"
  ) {
    initializeScript();
  }

  /**
   * 检查当前页面是否为Bilibili主页。
   * @returns {boolean} 如果是主页则返回true,否则返回false。
   */
  function isCurrentPageMain() {
    return location.pathname === "/";
  }

  /**
   * 初始化主页特有的功能。
   */
  function initializeMainPage() {
    initializeObserver("feedchannel-main"); // 观察主页内容区域
    console.log("[bilibili-blacklist] 主页已加载🍓");
  }

  /**
   * 检查当前页面是否为Bilibili搜索结果页。
   * @returns {boolean} 如果是搜索页则返回true,否则返回false。
   */
  function isCurrentPageSearch() {
    return location.hostname === "search.bilibili.com";
  }

  /**
   * 初始化搜索页特有的功能。
   */
  function initializeSearchPage() {
    initializeObserver("i_cecream"); // 观察搜索结果内容区域
    console.log("[bilibili-blacklist] 搜索页已加载🍉");
  }

  /**
   * 检查当前页面是否为Bilibili视频播放页。
   * @returns {boolean} 如果是视频播放页则返回true,否则返回false。
   */
  function isCurrentPageVideo() {
    return location.pathname.startsWith("/video/");
  }

  /**
   * 初始化视频播放页特有的功能。
   */
  function initializeVideoPage() {
    initializeObserver("right-container"); // 观察视频播放页右侧推荐区域
    console.log("[bilibili-blacklist] 播放页已加载🍇");
  }

  /**
   * 检查当前页面是否为Bilibili分类页。
   * @returns {boolean} 如果是分类页则返回true,否则返回false。
   */
  function isCurrentPageCategory() {
    return location.pathname.startsWith("/c/");
  }

  /**
   * 初始化分类页特有的功能。
   */
  function initializeCategoryPage() {
    initializeObserver("app"); // 观察整个app容器
    console.log("[bilibili-blacklist] 分类页已加载🍊");
  }

  /**
   * 检查当前页面是否为Bilibili用户空间页。
   * @returns {boolean} 如果是用户空间页则返回true,否则返回false。
   */
  function isCurrentUserSpace() {
    return location.hostname === "space.bilibili.com";
  }

  /**
   * 初始化用户空间页特有的功能。
   */
  function initializeUserSpace() {
    console.log("[bilibili-blacklist] 用户空间已加载🍎");
    const upNameSelector = "#h-name, .nickname"; // UP主名称的选择器
    // 创建一个MutationObserver来等待UP主名称元素加载
    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);
    }
  }

  /**
   * 在用户空间页面上的UP主名称元素添加屏蔽/取消屏蔽按钮。
   * @param {HTMLElement} upNameElement - 包含UP主名称的元素。
   */
  function addBlockButtonToUserSpace(upNameElement) {
    const upName = upNameElement.textContent.trim();
    // 避免重复添加按钮
    if (upNameElement.querySelector(".bilibili-blacklist-up-block-btn")) {
      return;
    }

    // 调整UP主名称元素的样式,以便容纳按钮
    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(); // 设置按钮初始状态

    upNameElement.appendChild(button);
  }

  //#endregion

  //#region 广告屏蔽

  /**
   * 屏蔽主页上的广告。
   */
  function blockMainPageAds() {
    if (!globalPluginConfig.flagAD) return; // 如果广告屏蔽未启用,则直接返回
    const adSelectors = [
      ".floor-single-card", // 分区推荐
      ".bili-live-card", // 直播推广
      ".btn-ad", // 广告按钮
    ];
    adSelectors.forEach((selector) => {
      document.querySelectorAll(selector).forEach((adCard) => {
        hideVideoCard(adCard, "ad"); // 隐藏广告卡片
      });
    });
  }

  /**
   * 屏蔽视频播放页上的广告。
   */
  function blockVideoPageAds() {
    if (!globalPluginConfig.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", // 广告地板
      ".btn-ad", // 广告按钮
    ];

    adSelectors.forEach((selector) => {
      document.querySelectorAll(selector).forEach((adCard) => {
        hideVideoCard(adCard, "ad"); // 隐藏广告卡片
      });
    });
  }

  //#endregion
})();