Greasy Fork

Greasy Fork is available in English.

B站批量拉黑开屏广告

批量拉黑

// ==UserScript==
// @name         B站批量拉黑开屏广告
// @version      3.0.4
// @description  批量拉黑
// @note         更新于 2025年11月3日
// @author       qcgzxw
// @match        https://*.bilibili.com/*
// @license      GNU GPLv3
// @run-at       document-end
// @grant        GM_registerMenuCommand
// @grant        GM_addStyle
// @grant        GM_openInTab
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// @namespace    http://greasyfork.icu/zh-CN/users/1192640-huaisha2049
// ==/UserScript==

(function () {
  'use strict';

  /*************** 配置区 ***************/
  // ✅ 内置黑名单UID
  const BUILTIN_BLACKLIST = [
    1356882480, 1082814196, 1919627194, 1957313739,
    1817661914, 1627242161, 1859459400, 1826766269,
    1926952280, 2103756604, 1987938455,
  ];

  // ✅ 收藏夹列表(链接 + 名称)
  const FAVORITE_LIST = [
    {
      name: "拉格朗日广告",
      url: "https://space.bilibili.com/95863234/favlist?fid=1520149734&ftype=create"
    },
    {
      name: "无限期途广告",
      url: "https://space.bilibili.com/95863234/favlist?fid=1852507834&ftype=create"
    },
    {
      name: "广告发布号",
      url: "https://space.bilibili.com/95863234/favlist?fid=2008751934&ftype=create"
    },
  ];

  // ✅ 拉黑请求间隔(毫秒)
  const BLOCK_INTERVAL = 250;

  /*************** 初始化 ***************/
  const match = document.cookie.match(/bili_jct=([^;]+)/);
  if (!match) {
    alert("⚠️ 未找到bili_jct,请先登录B站!");
    return;
  }
  const csrf_token = match[1];

  /*************** 工具函数 ***************/
  const sleep = ms => new Promise(r => setTimeout(r, ms));
  const fetchJson = async url => (await fetch(url, { credentials: 'include' })).json();
  const parseFavUrl = url => ({
    uid: url.match(/space\.bilibili\.com\/(\d+)/)?.[1],
    favid: url.match(/[?&]fid=(\d+)/)?.[1]
  });

  /*************** 获取收藏夹作者UID ***************/
  async function getFavAuthors(fav) {
    const { uid, favid } = parseFavUrl(fav.url);
    if (!uid || !favid) throw new Error(`无法解析收藏夹链接: ${fav.url}`);

    let page = 1;
    const uids = new Set();

    console.log(`📥 抓取收藏夹【${fav.name}】(${uid}/${favid})...`);

    while (true) {
      const apiUrl = `https://api.bilibili.com/x/v3/fav/resource/list?media_id=${favid}&pn=${page}&ps=20&platform=web`;
      const data = await fetchJson(apiUrl);

      if (data.code !== 0 || !data.data?.medias?.length) break;
      data.data.medias.forEach(m => m.upper?.mid && uids.add(m.upper.mid));

      console.log(`✅ 收藏夹 ${fav.name} 第 ${page} 页已处理,共 ${uids.size} 个唯一UP`);
      if (!data.data.has_more) break;
      page++;
    }

    console.log(`🎯 收藏夹【${fav.name}】共获取 ${uids.size} 个唯一作者UID`);
    return Array.from(uids);
  }

  /*************** 拉黑函数 ***************/
  async function blockUser(uid) {
    const body = new URLSearchParams({
      fid: uid,
      act: 5,
      re_src: 11,
      jsonp: 'jsonp',
      csrf: csrf_token
    });

    const res = await fetch('https://api.bilibili.com/x/relation/modify', {
      method: 'POST',
      credentials: 'include',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body
    });

    const data = await res.json();
    if (data.code === 0) {
      console.log(`🚫 已拉黑: https://space.bilibili.com/${uid}`);
    } else {
      console.warn(`⚠️ 拉黑失败(${uid}): ${data.message}`);
    }
  }

  /*************** 弹出选择框 ***************/
  async function selectFavorites() {
    return new Promise(resolve => {
      // 创建半透明遮罩层
      const overlay = document.createElement('div');
      Object.assign(overlay.style, {
        position: 'fixed',
        top: 0,
        left: 0,
        width: '100%',
        height: '100%',
        background: 'rgba(0,0,0,0.4)',
        zIndex: 100000,
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center'
      });

      // 创建对话框
      const box = document.createElement('div');
      Object.assign(box.style, {
        background: '#fff',
        padding: '20px',
        borderRadius: '10px',
        width: '360px',
        boxShadow: '0 4px 20px rgba(0,0,0,0.3)'
      });

      const title = document.createElement('h3');
      title.textContent = '选择要拉黑的收藏夹';
      Object.assign(title.style, { margin: '0 0 10px', textAlign: 'center' });
      box.appendChild(title);

      const list = document.createElement('div');
      FAVORITE_LIST.forEach((fav, idx) => {
        const label = document.createElement('label');
        label.style.display = 'block';
        label.style.marginBottom = '6px';
        const input = document.createElement('input');
        input.type = 'checkbox';
        input.value = idx;
        label.appendChild(input);
        label.append(` ${fav.name}`);
        list.appendChild(label);
      });

      // 是否包含内置黑名单
      const builtin = document.createElement('label');
      builtin.style.display = 'block';
      builtin.style.margin = '10px 0';
      const builtinInput = document.createElement('input');
      builtinInput.type = 'checkbox';
      builtinInput.checked = true;
      builtin.appendChild(builtinInput);
      builtin.append(' 同时拉黑内置黑名单');
      list.appendChild(builtin);

      box.appendChild(list);

      const btn = document.createElement('button');
      btn.textContent = '开始拉黑';
      Object.assign(btn.style, {
        width: '100%',
        marginTop: '10px',
        padding: '8px',
        background: '#d9001b',
        color: '#fff',
        border: 'none',
        borderRadius: '5px',
        cursor: 'pointer'
      });

      btn.onclick = () => {
        const selectedFavs = Array.from(list.querySelectorAll('input[type=checkbox]'))
          .filter((c, i) => i < FAVORITE_LIST.length && c.checked)
          .map(c => FAVORITE_LIST[c.value]);
        const includeBuiltin = builtinInput.checked;
        overlay.remove();
        resolve({ selectedFavs, includeBuiltin });
      };

      box.appendChild(btn);
      overlay.appendChild(box);
      document.body.appendChild(overlay);
    });
  }

  /*************** 主流程 ***************/
  async function startBatchBlock() {
    try {
      const { selectedFavs, includeBuiltin } = await selectFavorites();
      if (selectedFavs.length === 0 && !includeBuiltin) {
        alert('请至少选择一个收藏夹或内置黑名单');
        return;
      }

      const all_uids = new Set(includeBuiltin ? BUILTIN_BLACKLIST : []);

      for (const fav of selectedFavs) {
        const fav_uids = await getFavAuthors(fav);
        fav_uids.forEach(u => all_uids.add(u));
      }

      const uidArray = Array.from(all_uids);
      if (!confirm(`确定要拉黑 ${uidArray.length} 位UP主?`)) return;

      for (let i = 0; i < uidArray.length; i++) {
        await blockUser(uidArray[i]);
        await sleep(BLOCK_INTERVAL);
      }

      alert(`✅ 已完成批量拉黑,共 ${uidArray.length} 位UP主。`);
    } catch (err) {
      console.error('❌ 出错:', err);
      alert('批量拉黑出错,请查看控制台日志。');
    }
  }

  /*************** 页面按钮 ***************/
  function createStartButton() {
    const btn = document.createElement('button');
    btn.textContent = '批量拉黑收藏夹作者';
    Object.assign(btn.style, {
      position: 'fixed',
      top: '500px',
      right: '10px',
      padding: '10px 20px',
      backgroundColor: '#d9001b',
      color: 'white',
      border: 'none',
      borderRadius: '6px',
      cursor: 'pointer',
      zIndex: 99999,
      fontSize: '14px'
    });
    btn.addEventListener('click', startBatchBlock);
    document.body.appendChild(btn);
  }

  window.addEventListener('load', createStartButton);
})();