Greasy Fork

Greasy Fork is available in English.

Bilibili-BlackList

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

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


  // 从存储中获取黑名单
  // 默认精确匹配黑名单(区分大小写)
  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)
  });

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

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

  // 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软广计数

  // 用于不同页面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", // 视频播放页
  ];

  /**
   * 为视频卡片添加屏蔽按钮容器。
   * @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);
    }
  }

  /**
   * 获取视频卡片的链接。
   * @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));
  }

  /**
   * 为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();
    }
  }

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

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

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

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

  /**
   * 根据当前页面初始化脚本。
   */
  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() {
    // **用户修改 2: 延迟 5 秒启动屏蔽功能**
    console.log("[bilibili-blacklist] 播放页已加载,将延迟 5 秒启动功能。🍇");

    // 延迟 5 秒执行核心功能
    setTimeout(() => {
      initializeObserver("right-container"); // 观察视频播放页右侧推荐区域

      // 首次手动扫描和广告屏蔽
      scanAndBlockVideoCards();
      blockVideoPageAds();

      console.log("[bilibili-blacklist] 视频播放页屏蔽功能已启动。");
    }, 5000); // 5000 毫秒 = 5 秒
  }


  /**
   * 检查当前页面是否为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);
  }

  /**
   * 屏蔽主页上的广告。
   */
  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", // 广告按钮
      ".video-page-operator-card-small", // 运营推广
    ];

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

  // 这里可以放置一些通用的工具函数
  // 目前脚本中没有独立的工具函数,所以这个模块暂时为空
  // 如果后续有需要可以添加更多工具函数

  /*
   * 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
   */

  // 启动脚本
  initializeScript();


})();