Greasy Fork

Greasy Fork is available in English.

Bilibili-BlackList

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

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

// ==UserScript==
// @name         Bilibili-BlackList
// @namespace    https://github.com/HeavenTTT/bilibili-blacklist
// @version      1.1.2
// @author       HeavenTTT
// @description  Bilibili UP屏蔽插件 - 屏蔽UP主视频卡片,支持精确匹配和正则匹配,支持视频页面、分类页面、搜索页面等。
// @match        *://*.bilibili.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @icon         https://www.bilibili.com/favicon.ico
// @require      https://update.greasyfork.icu/scripts/533940/Bilibili-BlackList.user.js
// @license      MIT
// ==/UserScript==

(function () {
  "use strict";
  /*
   * Bilibili-BlackList -- Bilibili UP屏蔽插件
   * 脚本大部分代码由AI生成,作者一点都不懂JavaScript,出现bug请联系Gemini / ChatGPT / DeepSeek
   * this script is mainly generated by AI, the author doesn't know JavaScript at all, if there are bugs, please contact Gemini / ChatGPT / DeepSeek
   * 感谢你的使用
   * Thank you for using this script
   *
   * 本段注释为VS code 自动生成 this is a comment generated by VS code
   */
  // 从存储中获取黑名单
  // 默认精确匹配黑名单(区分大小写)
  let exactBlacklist = GM_getValue("exactBlacklist", [
    "绝区零",
    "崩坏星穹铁道",
    "崩坏3",
    "原神",
    "米哈游miHoYo",
  ]);
  // 默认正则匹配黑名单
  let regexBlacklist = GM_getValue("regexBlacklist", [
    "王者荣耀",
    "和平精英",
    "PUBG",
    "绝地求生",
    "吃鸡",
  ]);
  // 标签屏蔽黑名单
  let tNameBlacklist = GM_getValue("tNameBlacklist", ["手机游戏"]);
  // let globalConfig = {
  //   flagInfo: true,
  //   flagAD: true,
  //    flagTName : true,
  //    flagCM: true,
  //   processQueueInterval: 500, // 单位 ms
  // };
  let globalConfig = GM_getValue("globalConfig", {
    flagInfo: true,
    flagAD: true,
    flagTName: true,
    flagCM: true,
    processQueueInterval: 500, // 单位 ms
  });
  // 保存黑名单到存储
  function saveBlacklists() {
    GM_setValue("exactBlacklist", exactBlacklist);
    GM_setValue("regexBlacklist", regexBlacklist);
    GM_setValue("tNameBlacklist", tNameBlacklist);
  }
  function saveGlobalConfig() {
    GM_setValue("globalConfig", globalConfig);
  }

  //#region 核心功能 - 屏蔽视频卡片
  let isShowAll = false; // 是否显示全部视频卡片
  let isBlocking = false; // 是否正在执行屏蔽操作
  let lastBlockTime = 0; // 上次执行屏蔽的时间戳
  let blockedCards = new Set(); // 存储已屏蔽的视频卡片元素
  let processedCards = new WeakSet(); // 记录已处理过的卡片(避免重复处理)
  //给卡片添加屏蔽按钮
  function cardAddBlockcContainer(upName, card) {
    if (!card.querySelector("bilibili-blacklist-block-container")) {
      const container = document.createElement("div");
      container.classList.add("bilibili-blacklist-block-container");
      if (!card.querySelector(".bilibili-blacklist-block-btn")) {
        // 创建屏蔽按钮
        if (isVideoPage()) {
          // 如果是视频页面
          if (isInit) {
            const blockButton = createBlockButton(upName, card);
            card.querySelector(".card-box").style.position = "relative";
            card.querySelector(".card-box").appendChild(container);
            blockButton.style.scale = "80%"; // 缩小按钮
            container.appendChild(blockButton);
          }
        } else if (isCategoryPage()) {
          // 如果是分类页面
          const blockButton = createBlockButton(upName, card); // 创建屏蔽按钮
          card.querySelector(".bili-video-card").appendChild(container); // 将按钮添加到卡片中信息中
          container.appendChild(blockButton); // 将按钮添加到容器中
        } else {
          const blockButton = createBlockButton(upName, card); // 创建屏蔽按钮
          card.appendChild(container); // 将按钮添加到卡片中
          container.appendChild(blockButton); // 将按钮添加到容器中
        }
      }
    }
  }
  //隐藏卡片
  function hideCard(card) {
    if (isSearchPage()) {
      // 如果是搜索页面 -> 隐藏父元素
      card = card.parentElement; // 获取父元素
    }
    if (isMainPage()) {
      // 如果是主页
      if (card.parentElement.classList.contains("feed-card")) {
        // 如果父元素是feed-card
        card = card.parentElement; // 获取父元素
      }
    }
    if (!blockedCards.has(card)) {
      blockedCards.add(card); // 将卡片添加到已屏蔽列表
    }
    if (!isShowAll) {
      card.style.display = "none"; // 隐藏卡片
    }
  }
  /// 查找所有视频卡片
  function querySelectorAllVideoCard(selector) {
    return document.querySelectorAll(selector);
  }
  /// 屏蔽视频卡片
  function BlockCard() {
    const now = Date.now();
    if (isBlocking || now - lastBlockTime < 1000) {
      return;
    }
    isBlocking = true;
    lastBlockTime = now;
    try {
      let cards = null;
      if (isMainPage()) {
        cards = querySelectorAllVideoCard(".bili-video-card");
      } else if (isVideoPage()) {
        cards = querySelectorAllVideoCard(".video-page-card-small");
      } else if (isCategoryPage()) {
        cards = querySelectorAllVideoCard(".feed-card");
      } else if (isSearchPage()) {
        cards = querySelectorAllVideoCard(".bili-video-card");
      } else return; // 如果不是视频页面,则不执行屏蔽操作
      //console.log(`检测到 ${cards.length} 个视频卡片`);
      cards.forEach((card) => {
        addButtontTNameQueue(card);
        if (processedCards.has(card)) {
          return; // 如果卡片已经处理过,则跳过
        }
        // 获取视频信息
        const { upName, title } = GetVideoInfo(card);
        if (upName && title) {
          processedCards.add(card); // 将卡片标记为已处理
          cardAddBlockcContainer(upName, card); // 添加屏蔽按钮

          // 检查是否在黑名单中
          if (isBlacklisted(upName, title) && globalConfig.flagInfo) {
            // 如果在黑名单中,则隐藏卡片
            hideCard(card);
          }
          if (isCardBlacklistTName(card) && globalConfig.flagTName) {
            hideCard(card);
          }
        } else {
          //console.warn("未找到UP主名称或视频标题,跳过屏蔽:", card);
        }
      });
      updateBlockCountDisplay();
      FixedMainPage(); // 修正主页的错位问题
    } finally {
      isBlocking = false; // 重置屏蔽状态
    }
  }
  //修正主页的错位问题
  function FixedMainPage() {
    if (!isMainPage()) return; // 仅在主页执行
    const container = document.querySelector(
      ".recommended-container_floor-aside .container" // 推荐视频容器
    );
    // 检查父元素是否存在
    if (container) {
      const children = container.children; // 这是一个 HTMLCollection
      let visableindex = 0; // 可见子元素的索引
      for (let i = 0; i < children.length; i++) {
        const child = children[i];
        if (child.style.display !== "none") {
          if (visableindex <= 6) {
            child.style.marginTop = "0px";
          } else if (visableindex < 12) {
            child.style.marginTop = "24px";
          } else {
            break; // 如果可见子元素超过10个,则停止处理
          }
          visableindex++; // 统计可见子元素的数量
        }
      }
    }
  }
  // 更新屏蔽计数显示
  function updateBlockCountDisplay() {
    if (blockCountDiv) {
      blockCountDiv.textContent = `${blockedCards.size}`; // 更新右侧导航栏的屏蔽计数
    }
    // 更新面板标题(如果面板已打开)
    const panel = document.getElementById("bilibili-blacklist-panel");
    if (panel && blockTitle) {
      blockTitle.textContent = `已屏蔽视频 (${blockedCards.size})`; // 更新面板标题
    }
  }
  // 暂时取消屏蔽/恢复屏蔽功能
  function toggleShowAll() {
    isShowAll = !isShowAll;
    blockedCards.forEach((card) => {
      card.style.display = isShowAll ? "block" : "none";
    });
    btnTempUnblock.textContent = isShowAll ? "恢复屏蔽" : "取消屏蔽";
    btnTempUnblock.style.background = isShowAll ? "#dddddd" : "#fb7299";
    // 不需要更新blockCount,因为总数没有变化
  }
  const selectorUpName = [
    ".bili-video-card__info--author", // 主页
    ".bili-video-card__author", // 分类页面--> span title
    ".name", // 播放页面
  ];
  const selectorTitle = [
    ".bili-video-card__info--tit", // 主页
    ".bili-video-card__title", // 分类页面--> span title
    ".title", // 播放页面
  ];
  //获取视频信息 -UP主名称 -视频标题
  function GetVideoInfo(card) {
    let upName = "";
    let title = "";
    if (card.style.display === "none") return { upName, title }; // 如果卡片已被隐藏,则直接返回空信息
    const upNameElement = card.querySelectorAll(selectorUpName.join(", ")); // 使用逗号分隔的选择器
    if (upNameElement.length > 0) {
      upName = upNameElement[0].textContent.trim(); // 获取第一个匹配到的元素的内容,并去除首尾空格
      //处理分类页面的UP主名称
      if (isCategoryPage()) {
        upName = upName.split(" · ")[0].trim();
        //console.log(`分类页面UP主名称: ${upName}`);
      }
    }
    const titleElement = card.querySelectorAll(selectorTitle.join(", ")); // 使用逗号分隔的选择器
    if (titleElement.length > 0) {
      title = titleElement[0].textContent.trim(); // 获取第一个匹配到的元素的内容,并去除首尾空格
    }
    return { upName, title };
  }

  function isBlacklisted(upName, title) {
    // 精确匹配黑名单(无视大小写)
    // 将 upName 转换为小写,然后与黑名单中的每个项的小写进行比较
    const lowerCaseUpName = upName.toLowerCase();
    if (exactBlacklist.some((item) => item.toLowerCase() === lowerCaseUpName)) {
      return true;
    }

    // 正则匹配黑名单(无视大小写)
    // 为正则表达式添加 'i' 标志,表示忽略大小写
    if (regexBlacklist.some((regex) => new RegExp(regex, "i").test(upName))) {
      return true;
    }
    if (regexBlacklist.some((regex) => new RegExp(regex, "i").test(title))) {
      return true;
    }
    return false; // 不在黑名单中
  }
  /// 添加UP主到精确黑名单并刷新页面
  function addToExactBlacklist(upName, cardElement = null) {
    try {
      if (!upName) return;
      if (!exactBlacklist.includes(upName)) {
        exactBlacklist.push(upName);
        saveBlacklists();
        updateExactList();
        if (cardElement) {
          hideCard(cardElement); // 隐藏当前卡片
        }
        //BlockCard();
      }
    } catch (e) {
      console.error("添加黑名单出错:", e);
    }
  }
  function removeFromExactBlacklist(upName) {
    try {
      if (exactBlacklist.includes(upName)) {
        const index = exactBlacklist.indexOf(upName);
        exactBlacklist.splice(index, 1);
        saveBlacklists();
        updateExactList();
      }
    } catch (e) {
      console.error("移除黑名单出错:", e);
    } finally {
      BlockCard();
    }
  }
  function addToTNameBlachlist(tname, cardElement = null) {
    try {
      if (!tname) return;
      if (!tNameBlacklist.includes(tname)) {
        tNameBlacklist.push(tname);
        saveBlacklists();
        updateTNameList();
        if (cardElement) {
          hideCard(cardElement); // 隐藏当前卡片
        }
      }
    } catch (e) {
      console.error("添加黑名单出错:", e);
    }
  }

  //#endregion

  //#region Bv号以及视频信息
  let cardSequenceGetJson = new Set(); // 存储卡片队列
  let isProcessingCardQueue = false; // 是否正在处理队列
  let isPageActive = true; // 页面是否可见
  function getCardBv(card) {
    const bvElement = card.querySelector("a");
    if (!bvElement) {
      return null;
    }
    try {
      const link = bvElement.getAttribute("href");
      if (!link) {
        return null;
      } else {
        //判断链接是不是cm.bilili
        if (link.match(/cm.bilibili.com/) && globalConfig.flagCM) {
          hideCard(card);
          //console.log("软广链接,已屏蔽");
          return null;
        }
        const bv = link.match(/BV\w+/);
        return bv[0];
      }
    } catch (e) {
      return null;
    }
  }

  async function getBilibiliVideoAPI(bvid) {
    if (bvid.length >= 24) {
      return null;
    }
    const url = `https://api.bilibili.com/x/web-interface/view?bvid=${bvid}`;
    try {
      const response = await fetch(url);
      const json = await response.json();
      if (json.code === 0) {
        //console.log("data1:", json.data.tname);
        //console.log("data2:", json.data.tname_v2);
        return json.data;
      } else {
        //console.error("获取视频信息失败:", bvid);
        return null;
      }
    } catch (error) {
      console.error("请求失败:", error);
    }
  }
  function isCardBlacklistTName(card) {
    const tnameGroup = card.querySelector(".bilibili-blacklist-tname-group");
    if (tnameGroup) {
      const tnameElements = tnameGroup.querySelectorAll(
        ".bilibili-blacklist-tname"
      );
      for (const tnameElement of tnameElements) {
        const tname = tnameElement.textContent.trim();
        if (tNameBlacklist.includes(tname)) {
          return true;
        }
      }
    }
    return false;
  }
  // 队列处理函数
  let cardSequenceGetJsonDone = new Set(); // 存储卡片队列
  async function processCardTNameQueue() {
    if (isProcessingCardQueue) return;
    isProcessingCardQueue = true;

    while (cardSequenceGetJson.size > 0) {
      // 页面不活动时暂停处理
      if (!isPageActive) {
        //console.log("页面不活动,暂停队列处理...");
        await sleep(1000); // 每秒检查一次
        continue; // 不处理当前卡片,重新判断
      }

      const iterator = cardSequenceGetJson.values();
      const card = iterator.next().value;
      cardSequenceGetJson.delete(card);
      if (!card) continue;
      if (cardSequenceGetJsonDone.has(card)) {
        //("卡片已处理过" + card.textContent);
        continue;
      }
      const bv = getCardBv(card);
      if (!bv) continue;
      const container = card.querySelector(
        ".bilibili-blacklist-block-container"
      );
      if (!container) {
        //console.log("未找到容器" + card.textContent);
        continue;
      }
      cardSequenceGetJsonDone.add(card);
      const data = await getBilibiliVideoAPI(bv);
      if (!data) {
        // console.log("未找到数据" + card.textContent);
        cardSequenceGetJsonDone.remove(card);
        continue;
      }
      // 如果 card 已经处理过 tname group,跳过

      // 最终确认
      if (!card.querySelector(".bilibili-blacklist-tname-group")) {
        // 创建 tname group
        const tnameGroup = document.createElement("div");
        tnameGroup.className = "bilibili-blacklist-tname-group";

        let hasTname = false;
        // 添加一级 tname
        if (data.tname) {
          //console.log(`处理 BV: ${bv} - 分类: ${data.tname}`);
          const btn = createTNameBlockButton(data.tname, card);
          tnameGroup.appendChild(btn);
          hasTname = true;
        }

        // 添加二级 tname_v2
        if (data.tname_v2) {
          //console.log(`处理 BV: ${bv} - 分类2: ${data.tname_v2}`);
          const tnameElement = createTNameBlockButton(data.tname_v2, card);
          tnameGroup.appendChild(tnameElement);
          hasTname = true;
        }
        // 只有有 tname 才 append group,避免插入空容器
        if (hasTname) {
          container.appendChild(tnameGroup);
        }
      }

      await sleep(globalConfig.processQueueInterval || 100);
    }
    isProcessingCardQueue = false;
  }
  function addButtontTNameQueue(card) {
    if (!globalConfig.flagTName) return;
    const bv = getCardBv(card);
    if (!bv) return;
    // 检查是否已在处理中或已有标签组
    if (card.querySelector(".bilibili-blacklist-tname-group")) {
      return;
    }
    // 检查是否已在队列中
    if (cardSequenceGetJson.has(card)) {
      return;
    }
    cardSequenceGetJson.add(card);
    if (!isProcessingCardQueue) {
      processCardTNameQueue();
    }
  }
  //
  function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
  // 页面可见性监听
  document.addEventListener("visibilitychange", () => {
    isPageActive = !document.hidden;
    //console.log(`页面可见状态改变: ${isPageActive ? "活动" : "隐藏"}`);
  });

  // 窗口焦点监听
  window.addEventListener("focus", () => {
    isPageActive = true;
    //console.log("窗口获得焦点");
  });

  window.addEventListener("blur", () => {
    isPageActive = false;
    //console.log("窗口失去焦点");
  });

  //#endregion
  //#region 页面修改
  //创建屏蔽按钮(悬停在视频卡片上时显示)
  function createBlockButton(upName, cardElement) {
    const btn = document.createElement("div");
    btn.className = "bilibili-blacklist-block-btn";
    btn.innerHTML = "屏蔽";
    btn.title = `屏蔽: ${upName}`;

    // 屏蔽按钮样式
    // 点击时添加到黑名单
    btn.addEventListener("click", (e) => {
      e.stopPropagation(); // 防止事件冒泡
      addToExactBlacklist(upName, cardElement); // 使用公共函数
    });

    return btn;
  }
  function createTNameBlockButton(tName, cardElement) {
    const btn = document.createElement("span");
    btn.className = "bilibili-blacklist-tname";
    btn.innerHTML = `${tName}`;
    btn.title = `屏蔽: ${tName}`;

    // 屏蔽按钮样式
    // 点击时添加到黑名单
    btn.addEventListener("click", (e) => {
      e.stopPropagation(); // 防止事件冒泡
      addToTNameBlachlist(tName, cardElement);
      updateTNameList();
    });

    return btn;
  }
  // 在右侧导航栏添加黑名单管理按钮
  let blockCountDiv = null;
  function addBlacklistManagerButton() {
    if (isVideoPage()) {
      return;
    }
    const rightEntry = document.querySelector(".right-entry");
    if (
      !rightEntry ||
      rightEntry.querySelector("#bilibili-blacklist-manager")
    ) {
      return;
    }

    const li = document.createElement("li");
    li.id = "bilibili-blacklist-manager";
    li.style.cursor = "pointer";
    li.className = "v-popover-wrap";

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

    // 可爱的卡比图标SVG
    const icon = document.createElement("div");
    icon.className = "right-entry__outside";
    icon.innerHTML = getKirbySVG();
    //icon.style.color = '#fb7299'; // B站粉色
    icon.style.marginBottom = "-5px";
    blockCountDiv = document.createElement("span");
    //const text = document.createElement('div');
    blockCountDiv.textContent = `0`;
    btn.appendChild(icon);
    btn.appendChild(blockCountDiv);
    li.appendChild(btn);

    // 在导航中插入按钮
    if (rightEntry.children.length > 1) {
      rightEntry.insertBefore(li, rightEntry.children[1]);
    } else {
      rightEntry.appendChild(li);
    }

    // 如果面板不存在则创建
    let panel = document.getElementById("bilibili-blacklist-panel");
    if (!panel) {
      panel = createBlacklistPanel();
    }

    // 点击按钮时显示面板
    li.addEventListener("click", () => {
      if (panel.style.display === "none") {
        panel.style.display = "flex";
        //updateBlockCountDisplay(); // 更新屏蔽计数显示
      } else {
        panel.style.display = "none";
      }
    });
  }
  // 创建黑名单管理面板
  let btnTempUnblock = null;
  let exactList; //精确匹配列表
  let regexList; //正则匹配列表
  let tNameList; //tname匹配列表
  let configList; //配置列表
  let blockTitle;

  // 工具函数:创建按钮
  function createButton(text, bgColor, onClick) {
    const button = document.createElement("button");
    button.textContent = text;
    button.style.padding = "4px 8px";
    button.style.background = bgColor;
    button.style.color = "#fff";
    button.style.border = "none";
    button.style.borderRadius = "4px";
    button.style.cursor = "pointer";
    button.addEventListener("click", onClick);
    return button;
  }

  // 工具函数:创建列表项
  function createListItem(contentText, onRemoveClick, isRegex = false) {
    const item = document.createElement("li");
    item.style.display = "flex";
    item.style.justifyContent = "space-between";
    item.style.alignItems = "center";
    item.style.padding = "8px 0";
    item.style.borderBottom = "1px solid #f1f2f3";

    const content = document.createElement("span");
    content.textContent = contentText;
    content.style.flex = "1";
    if (isRegex) {
      content.style.fontFamily = "monospace";
    }

    const removeBtn = createButton("移除", "#f56c6c", onRemoveClick);

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

  // 更新精确匹配列表
  function updateExactList() {
    if (!exactList) return;
    exactList.innerHTML = "";

    exactBlacklist.forEach((upName) => {
      const item = createListItem(upName, () => {
        removeFromExactBlacklist(upName);
        BlockCard(); //更新屏蔽卡片
      });
      exactList.appendChild(item);
    });

    Array.from(exactList.children)
      .reverse()
      .forEach((item) => exactList.appendChild(item));

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

  // 更新正则匹配列表
  function updateRegexList() {
    if (!regexList) return;
    regexList.innerHTML = "";

    regexBlacklist.forEach((regex, index) => {
      const item = createListItem(
        regex,
        () => {
          regexBlacklist.splice(index, 1);
          saveBlacklists();
          updateRegexList();
          BlockCard();
        },
        true
      );
      regexList.appendChild(item);
    });

    Array.from(regexList.children)
      .reverse()
      .forEach((item) => regexList.appendChild(item));

    if (regexBlacklist.length === 0) {
      const empty = document.createElement("div");
      empty.textContent = "暂无正则匹配屏蔽规则";
      empty.style.textAlign = "center";
      empty.style.padding = "16px";
      empty.style.color = "#999";
      regexList.appendChild(empty);
    }
  }
  function updateTNameList() {
    if (!tNameList) return;
    tNameList.innerHTML = "";

    tNameBlacklist.forEach((tName, index) => {
      //console.log("移除标签", tName);
      const item = createListItem(tName, () => {
        tNameBlacklist.splice(index, 1);
        saveBlacklists();
        updateTNameList();
        BlockCard();
      });
      tNameList.appendChild(item);
    });

    Array.from(tNameList.children)
      .reverse()
      .forEach((item) => tNameList.appendChild(item));

    if (tNameBlacklist.length === 0) {
      const empty = document.createElement("div");
      empty.textContent = "暂无标签屏蔽规则";
      empty.style.textAlign = "center";
      empty.style.padding = "16px";
      empty.style.color = "#999";
      tNameList.appendChild(empty);
    }
  }
  function updateConfig() {
    if (!configList) return;
    // 清空 configContent
    configList.innerHTML = "";
    //开关
    const container = document.createElement("div");
    container.style.display = "flex";
    container.style.alignItems = "center";
    container.style.marginBottom = "8px";
    container.style.gap = "8px";
    container.style.margin = "20px 0";
    const label = document.createElement("span");
    label.textContent = "临时开关";
    label.style.flex = "1";
    btnTempUnblock = document.createElement("button");
    btnTempUnblock.textContent = isShowAll ? "恢复屏蔽" : "取消屏蔽";
    btnTempUnblock.style.background = isShowAll ? "#dddddd" : "#fb7299";
    btnTempUnblock.style.padding = "6px 12px";
    btnTempUnblock.style.border = "none";
    btnTempUnblock.style.cursor = "pointer";
    btnTempUnblock.style.color = "#fff";
    btnTempUnblock.addEventListener("click", toggleShowAll);
    container.appendChild(label);
    container.appendChild(btnTempUnblock);

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

    // 工具函数:创建一个开关按钮
    function createToggleButton(labelText, configKey, title = null) {
      const container = document.createElement("div");
      container.style.display = "flex";
      container.style.alignItems = "center";
      container.style.marginBottom = "8px";
      container.style.gap = "8px";
      container.title = title;
      const label = document.createElement("span");
      label.textContent = labelText;
      label.style.flex = "1";

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

      function updateButtonStyle() {
        button.textContent = globalConfig[configKey] ? "开启" : "关闭";
        button.style.backgroundColor = globalConfig[configKey]
          ? "#fb7299"
          : "#909399";
      }

      button.addEventListener("click", () => {
        globalConfig[configKey] = !globalConfig[configKey];
        updateButtonStyle();
        saveGlobalConfig(); // 你可以实现此函数,将globalConfig存储到localStorage或其他
      });

      updateButtonStyle();

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

      return container;
    }

    // 创建3个开关
    configList.appendChild(
      createToggleButton("屏蔽标题/Up主名", "flagInfo", "屏蔽标题/Up主名")
    );
    configList.appendChild(
      createToggleButton("屏蔽分类标签", "flagTName", "屏蔽分类标签")
    );
    configList.appendChild(createToggleButton("屏蔽主页推荐", "flagAD"));
    configList.appendChild(createToggleButton("屏蔽主页视频软广", "flagCM"));

    // 处理队列请求间隔
    const intervalContainer = document.createElement("div");
    intervalContainer.style.display = "flex";
    intervalContainer.style.alignItems = "center";
    intervalContainer.style.marginTop = "16px";
    intervalContainer.style.gap = "8px";
    intervalContainer.title =
      "请求API间隔时间,间隔时间越长,屏蔽越快,但可能会被B站检测到,默认100ms";

    const intervalLabel = document.createElement("span");
    intervalLabel.textContent = "视频分类-处理队列请求间隔 (ms):";

    intervalLabel.style.flex = "1";

    const intervalInput = document.createElement("input");
    intervalInput.type = "number";
    intervalInput.min = "0";
    intervalInput.value = globalConfig.processQueueInterval;
    intervalInput.style.width = "100px";
    intervalInput.style.padding = "6px";
    intervalInput.style.border = "1px solid #ddd";
    intervalInput.style.borderRadius = "4px";

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

    saveIntervalBtn.addEventListener("click", () => {
      const val = parseInt(intervalInput.value, 10);
      if (!isNaN(val) && val >= 0) {
        globalConfig.processQueueInterval = val;
        saveGlobalConfig(); // 保存配置
        //alert("处理队列请求间隔已保存!");
      } else {
        alert("请输入有效的非负数字!");
      }
    });

    intervalContainer.appendChild(intervalLabel);
    intervalContainer.appendChild(intervalInput);
    intervalContainer.appendChild(saveIntervalBtn);
    configList.appendChild(intervalContainer);
    return configList;
  }

  // 创建黑名单面板
  function createBlacklistPanel() {
    const panel = document.createElement("div");
    panel.id = "bilibili-blacklist-panel";

    panel.style.position = "fixed";
    panel.style.top = "50%";
    panel.style.left = "50%";
    panel.style.transform = "translate(-50%, -50%)";
    panel.style.width = "500px";
    panel.style.maxHeight = "80vh";
    panel.style.backgroundColor = "#fff";
    panel.style.borderRadius = "8px";
    panel.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.15)";
    panel.style.zIndex = "99999";
    panel.style.overflow = "hidden";
    panel.style.display = "none";
    panel.style.flexDirection = "column";

    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: "精确匹配", content: exactContent },
      { name: "正则匹配(Up/标题)", content: regexContent },
      { name: "屏蔽标签", content: tnameContent },
      { name: "配置开关", content: configContent },
    ];

    tabs.forEach((tabData) => {
      const tab = document.createElement("div");
      tab.textContent = tabData.name;
      tab.style.padding = "12px 16px";
      tab.style.cursor = "pointer";
      tab.style.fontWeight = "500";
      tab.style.borderBottom =
        tabData.content.style.display === "block"
          ? "2px solid #fb7299"
          : "none";

      tab.addEventListener("click", () => {
        tabs.forEach(({ tab: t, content: c }) => {
          t.style.borderBottom = "none";
          c.style.display = "none";
        });
        tab.style.borderBottom = "2px solid #fb7299";
        tabData.content.style.display = "block";
      });

      tabData.tab = tab;
      tabContainer.appendChild(tab);
    });

    // Header 区域
    const header = document.createElement("div");
    header.style.padding = "16px";
    header.style.borderBottom = "1px solid #f1f2f3";
    header.style.display = "flex";
    header.style.justifyContent = "space-between";
    header.style.alignItems = "center";

    blockTitle = document.createElement("h3");
    blockTitle.style.margin = "0";
    blockTitle.style.fontSize = "16px";
    blockTitle.style.fontWeight = "500";

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

    header.appendChild(blockTitle);
    header.appendChild(closeBtn);

    const contentContainer = document.createElement("div");
    contentContainer.style.display = "flex";
    contentContainer.style.flexDirection = "column";
    contentContainer.style.flex = "1";
    contentContainer.style.overflow = "hidden";

    // 精确匹配输入框
    const addExactContainer = document.createElement("div");
    addExactContainer.style.display = "flex";
    addExactContainer.style.marginBottom = "16px";
    addExactContainer.style.gap = "8px";

    const exactInput = document.createElement("input");
    exactInput.type = "text";
    exactInput.placeholder = "输入要屏蔽的UP主名称";
    exactInput.style.flex = "1";
    exactInput.style.padding = "8px";
    exactInput.style.border = "1px solid #ddd";
    exactInput.style.borderRadius = "4px";

    const addExactBtn = document.createElement("button");
    addExactBtn.textContent = "添加";
    addExactBtn.style.padding = "8px 16px";
    addExactBtn.style.background = "#fb7299";
    addExactBtn.style.color = "#fff";
    addExactBtn.style.border = "none";
    addExactBtn.style.borderRadius = "4px";
    addExactBtn.style.cursor = "pointer";
    addExactBtn.addEventListener("click", () => {
      const upName = exactInput.value.trim();
      if (upName) {
        addToExactBlacklist(upName);
        exactInput.value = "";
      }
    });

    addExactContainer.appendChild(exactInput);
    addExactContainer.appendChild(addExactBtn);
    exactContent.appendChild(addExactContainer);

    // 正则匹配输入框
    const addRegexContainer = document.createElement("div");
    addRegexContainer.style.display = "flex";
    addRegexContainer.style.marginBottom = "16px";
    addRegexContainer.style.gap = "8px";

    const regexInput = document.createElement("input");
    regexInput.type = "text";
    regexInput.placeholder = "输入正则表达式 (如: 小小.*Official)";
    regexInput.style.flex = "1";
    regexInput.style.padding = "8px";
    regexInput.style.border = "1px solid #ddd";
    regexInput.style.borderRadius = "4px";

    const addRegexBtn = document.createElement("button");
    addRegexBtn.textContent = "添加";
    addRegexBtn.style.padding = "8px 16px";
    addRegexBtn.style.background = "#fb7299";
    addRegexBtn.style.color = "#fff";
    addRegexBtn.style.border = "none";
    addRegexBtn.style.borderRadius = "4px";
    addRegexBtn.style.cursor = "pointer";
    addRegexBtn.addEventListener("click", () => {
      const regex = regexInput.value.trim();
      if (regex && !regexBlacklist.includes(regex)) {
        try {
          new RegExp(regex);
          regexBlacklist.push(regex);
          saveBlacklists();
          regexInput.value = "";
          updateRegexList();
          BlockCard();
        } catch (e) {
          alert("无效的正则表达式: " + e.message);
        }
      }
    });

    addRegexContainer.appendChild(regexInput);
    addRegexContainer.appendChild(addRegexBtn);
    regexContent.appendChild(addRegexContainer);

    // 精确匹配列表
    exactList = document.createElement("ul");
    exactList.style.listStyle = "none";
    exactList.style.padding = "0";
    exactList.style.margin = "0";

    // 正则匹配列表
    regexList = document.createElement("ul");
    regexList.style.listStyle = "none";
    regexList.style.padding = "0";
    regexList.style.margin = "0";

    // tname列表
    tNameList = document.createElement("ul");
    tNameList.style.listStyle = "none";
    tNameList.style.padding = "0";
    tNameList.style.margin = "0";

    configList = document.createElement("ul");
    configList.style.listStyle = "none";
    configList.style.padding = "0";
    configList.style.margin = "0";

    // 更新列表
    updateExactList();
    updateRegexList();
    updateTNameList();
    updateConfig();

    exactContent.appendChild(exactList);
    regexContent.appendChild(regexList);
    tnameContent.appendChild(tNameList);
    configContent.appendChild(configList);
    contentContainer.appendChild(exactContent);
    contentContainer.appendChild(regexContent);
    contentContainer.appendChild(tnameContent);
    contentContainer.appendChild(configContent);

    panel.appendChild(tabContainer);
    panel.appendChild(header);
    panel.appendChild(contentContainer);

    document.body.appendChild(panel);
    return panel;
  }

  // 添加全局样式
  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;
          /*渐变背景
          background: linear-gradient(to top, #fb729955,#ffffff00);*/
          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: auto;
      }

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

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


        /* 修复视频卡片布局 */
        .bili-video-card__cover {
            contain: layout !important;
        }
        /* 面板样式 */
        #bilibili-blacklist-panel {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
        }
        /* 按钮悬停效果 */
        #bilibili-blacklist-panel button {
            transition: background-color 0.2s;
        }
        #bilibili-blacklist-panel button:hover {
            opacity: 0.9;
        }
        /* 管理按钮悬停效果 */
        #bilibili-blacklist-manager:hover svg {
            transform: scale(1.1);
        }
        #bilibili-blacklist-manager svg {
            transition: transform 0.2s;
        }
        /* 输入框聚焦效果 */
        #bilibili-blacklist-panel input:focus {
            outline: none;
            border-color: #fb7299 !important;
        }
        /*灰度效果*/
        .bilibili-blacklist-grayscale {
           filter: grayscale(95%); 
        }


    `);
  //可爱的卡比图标
  function getKirbySVG() {
    return `
        <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>
    `;
  }
  //#endregion
  //##########################

  //#region 观察者
  // MutationObserver 检测动态加载的新内容(仅当节点可见时才触发)
  const observer = new MutationObserver((mutations) => {
    let shouldCheck = false;
    if (isVideoPage()) {
      mutations.forEach((mutation) => {
        if (mutation.addedNodes.length > 0) {
          // 检查新增节点是否可见(有宽度或高度)
          shouldCheck = Array.from(mutation.addedNodes).some((node) => {
            // 仅检查元素节点(跳过文本节点、注释等)
            if (node.nodeType !== Node.ELEMENT_NODE) return false;

            // 检查元素或其子元素是否可见
            const hasVisibleContent =
              node.offsetWidth > 0 ||
              node.offsetHeight > 0 ||
              node.querySelector("[offsetWidth], [offsetHeight]");

            return hasVisibleContent;
          });
        }
      });
    } else {
      mutations.forEach((mutation) => {
        if (mutation.addedNodes.length > 0) {
          shouldCheck = true;
        }
      });
    }

    // 如果有可见的新内容,延迟 1 秒后执行屏蔽(确保 DOM 完全渲染)
    if (shouldCheck) {
      processedCards = new WeakSet(); // 重置已处理卡片集合
      setTimeout(() => {
        BlockCard();
        addBlacklistManagerButton(); // 确保每次都添加黑名单管理按钮
        if (isMainPage()) {
          BlockMainAD(); // 屏蔽页面广告
        }
        if (isVideoPage()) {
          BlockVideoPageAd(); // 屏蔽视频页面广告
        }
      }, 1000);
    }
  });

  // 初始化观察者(监视 DOM 变化)
  let observerError = 0;
  function initObserver(container) {
    const rootNode =
      document.getElementById(container) || // B站的主容器 ID
      document.documentElement; // 回退到整个文档

    if (rootNode) {
      observer.observe(rootNode, {
        childList: true, // 监视添加/移除的节点
        subtree: true, // 监视所有后代
      });
      return true;
    } else {
      // 如果没找到根节点则重试
      setTimeout(() => initObserver(container), 500);
      console.warn("未找到根节点,正在重试...");
      observerError++;

      if (observerError > 10) {
        console.error("重试次数过多,停止重试。");
        return false;
      }
    }
  }
  //#endregion
  //#region 初始化函数

  let isInit = false; // 是否已经初始化
  function init() {
    // 重置状态
    isBlocking = false;
    lastBlockTime = 0;
    blockedCards = new Set(); // 使用 Set 存储已屏蔽的卡片
    processedCards = new WeakSet();
    cardSequenceGetJson = new Set();
    if (isMainPage()) {
      initMainPage(); // 初始化主页
      BlockMainAD(); // 屏蔽主页广告
    } else if (isSearchPage()) {
      initSearchPage(); // 初始化搜索页
    } else if (isVideoPage()) {
      initVideoPage(); // 初始化播放页
      //BlockVideoPageAd(); // 屏蔽视频页面广告
    } else if (isCategoryPage()) {
      initCategoryPage(); // 初始化分类页
    } else if (isUserSpace()) {
      initUserSpace(); // 初始化用户空间
      //return; // 用户空间不需要屏蔽
    } else {
      return; // 如果不是已知页面则不执行
    }
    BlockCard(); // 初始化时立即执行屏蔽
    addBlacklistManagerButton(); // 添加黑名单管理按钮

    isInit = true; // 标记为已初始化
    console.log("BiliBili黑名单脚本已加载🥔");
  }
  // 监听页面加载完成事件
  document.addEventListener("DOMContentLoaded", init);
  if (
    document.readyState === "interactive" ||
    document.readyState === "complete"
  ) {
    init();
  }
  // 检查当前页面是否为B站主页
  function isMainPage() {
    return location.pathname === "/";
  }

  function initMainPage() {
    initObserver("i_cecream"); // 传入B站主页的主容器ID
    console.log("主页已加载🍓");
  }
  /// -----搜索页----
  function isSearchPage() {
    return location.hostname === "search.bilibili.com";
  }
  function initSearchPage() {
    initObserver("i_cecream");
    console.log("搜索页已加载🍉");
  }
  /// --- 播放页 ---
  function isVideoPage() {
    return location.pathname.startsWith("/video/");
  }
  function initVideoPage() {
    initObserver("app");
    console.log("播放页已加载🍇");
  }
  // ---- 分类页 ----
  function isCategoryPage() {
    // 页面链接 https://www.bilibili.com/c/xxxxxx
    // 通过检查路径名是否以 "/c/" 开头来判断是否为分类页
    return location.pathname.startsWith("/c/");
  }
  function initCategoryPage() {
    initObserver("app");
    console.log("分类页已加载🍊");
  }
  ///---用户空间---
  function isUserSpace() {
    return location.hostname === "space.bilibili.com";
  }
  function initUserSpace() {
    console.log("用户空间已加载🍎");

    // Use a more robust way to find the UP name element, and observe for its presence
    const upNameSelector = "#h-name, .nickname";
    const observerForUpName = new MutationObserver((mutations, observer) => {
      const upNameElement = document.querySelector(upNameSelector);
      if (upNameElement) {
        observer.disconnect(); // Stop observing once found
        addBlockButtonToUserSpace(upNameElement);
      }
    });

    // Start observing the body for changes
    observerForUpName.observe(document.body, {
      childList: true,
      subtree: true,
    });

    // Also try to find it immediately in case it's already there
    const initialUpNameElement = document.querySelector(upNameSelector);
    if (initialUpNameElement) {
      observerForUpName.disconnect(); // Disconnect if found immediately
      addBlockButtonToUserSpace(initialUpNameElement);
    }
  }

  function addBlockButtonToUserSpace(upNameElement) {
    const upName = upNameElement.textContent.trim();
    //console.log(`当前用户昵称: ${upName}`);
    if (upNameElement.querySelector(".bilibili-blacklist-up-block-btn")) {
      return;
    }
    // 设置 inline-flex,让文字和按钮一行显示
    upNameElement.style.display = "inline-flex";
    upNameElement.style.alignItems = "center";

    const button = document.createElement("button");
    button.className = "bilibili-blacklist-up-block-btn";
    button.textContent = "屏蔽";
    button.style.color = "#fff";
    button.style.width = "100px";
    button.style.height = "30px";
    button.style.marginLeft = "10px";
    button.style.borderRadius = "5px";
    button.style.border = "1px solid #fb7299";

    const updateButtonState = () => {
      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);
      }
      updateButtonState(); // Update button state immediately
      // No need to re-run initUserSpace(); it causes unnecessary re-initializations
    });

    updateButtonState(); // Set initial button state

    upNameElement.appendChild(button);
  }
  //#endregion

  //#region 额外功能-屏蔽广告
  // 屏蔽广告
  function BlockMainAD() {
    if (!globalConfig.flagAD) return;
    const adSelectors = [
      ".floor-single-card", // 分区推荐
      ".bili-live-card", // 直播推广
    ];
    adSelectors.forEach((selector) => {
      document.querySelectorAll(selector).forEach((adCard) => {
        adCard.remove();
      });
    });
  }

  // 屏蔽视频页面广告(使用数组优化)
  function BlockVideoPageAd() {
    if (!globalConfig.flagAD) return;
    const adSelectors = [
      ".video-card-ad-small", // 右上角推广
      ".slide-ad-exp", // 大推广
      ".video-page-game-card-small", // 游戏推广
      ".activity-m-v1", // 活动推广
      ".video-page-special-card-small", // 特殊卡片推广
      ".ad-floor-exp", // 广告地板
    ];

    adSelectors.forEach((selector) => {
      document.querySelectorAll(selector).forEach((adCard) => {
        adCard.remove();
      });
    });
  }
  //#endregion
})();