// ==UserScript==
// @name B站批量拉黑工具
// @namespace https://github.com/Chuc-Jie/BilibiliBlockUserJs
// @version 1.0.2
// @description 批量拉黑B站用户(支持用户名输入)- 脚本猫菜单触发版
// @author 友野YouyEr
// @match *://*.bilibili.com/*
// @grant GM_addStyle
// @grant GM_notification
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @connect bilibili.com
// @connect api.bilibili.com
// ==/UserScript==
(function() {
'use strict';
GM_addStyle(`
#batchBlockContainer {
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
width: 400px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
padding: 20px;
font-family: sans-serif;
border: 1px solid #fb7299;
display: none; /* 默认隐藏 */
}
#batchBlockContainer h3 {
margin: 0 0 15px;
color: #fb7299;
font-size: 16px;
text-align: center;
}
#usernameInput {
width: 100%;
height: 150px;
margin-bottom: 10px;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
resize: vertical;
font-family: monospace;
font-size: 12px;
}
#controlBtns {
display: flex;
gap: 10px;
margin-bottom: 15px;
}
.batch-btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: background-color 0.3s;
}
#startBtn {
background: #fb7299;
color: white;
flex: 1;
}
#startBtn:hover {
background: #ff8ab0;
}
#startBtn:disabled {
background: #ccc;
cursor: not-allowed;
}
#clearBtn {
background: #eee;
color: #333;
}
#clearBtn:hover {
background: #ddd;
}
#logArea {
height: 200px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 4px;
padding: 8px;
font-size: 12px;
line-height: 1.5;
background: #f9f9f9;
font-family: monospace;
}
.success { color: #4CAF50; font-weight: bold; }
.error { color: #f44336; font-weight: bold; }
.info { color: #2196F3; }
.warning { color: #ff9800; }
/* 可拖拽样式 */
.draggable {
cursor: move;
}
.drag-handle {
background: #fb7299;
color: white;
padding: 5px 10px;
border-radius: 8px 8px 0 0;
cursor: move;
text-align: center;
margin: -20px -20px 15px -20px;
}
`);
// 创建操作面板(默认隐藏)
function createPanel() {
if (document.getElementById('batchBlockContainer')) {
return document.getElementById('batchBlockContainer');
}
const container = document.createElement('div');
container.id = 'batchBlockContainer';
container.innerHTML = `
⇳ 拖拽移动
B站批量拉黑工具
状态: 就绪 |
进度: 0/0
`;
document.body.appendChild(container);
// 绑定事件
document.getElementById('startBtn').addEventListener('click', startBatchBlock);
document.getElementById('clearBtn').addEventListener('click', clearAll);
// 添加拖拽功能
makeDraggable(container, container.querySelector('.drag-handle'));
return container;
}
// 显示面板
function showPanel() {
const panel = createPanel();
panel.style.display = 'block';
log('面板已显示,可开始输入用户名', 'info');
}
// 拖拽功能
function makeDraggable(element, handle) {
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
handle.onmousedown = dragMouseDown;
function dragMouseDown(e) {
e.preventDefault();
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e.preventDefault();
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
element.style.top = (element.offsetTop - pos2) + "px";
element.style.left = (element.offsetLeft - pos1) + "px";
}
function closeDragElement() {
document.onmouseup = null;
document.onmousemove = null;
}
}
// 清空所有内容
function clearAll() {
document.getElementById('usernameInput').value = '';
document.getElementById('logArea').innerHTML = '';
updateStatus('已清空', '0/0');
}
// 更新状态
function updateStatus(status, progress) {
const statusEl = document.getElementById('statusText');
const progressEl = document.getElementById('progressText');
if (statusEl) statusEl.textContent = status;
if (progressEl) progressEl.textContent = progress;
}
// 解析用户输入的用户名列表
function parseUsernames(input) {
input = input.trim();
if (!input) return [];
// 尝试解析JSON数组格式
if (input.startsWith('[') && input.endsWith(']')) {
try {
const jsonStr = input.replace(/'/g, '"');
const list = JSON.parse(jsonStr);
if (Array.isArray(list) && list.every(item => typeof item === 'string')) {
return list.filter(username => username.trim());
}
} catch (e) {
log(`输入的列表格式有误(${e.message}),将尝试按行解析`, 'warning');
}
}
// 按行解析
return input.split('\n')
.map(line => line.trim())
.filter(line => line);
}
// 从用户名获取UID
async function getUidByUsername(username) {
try {
const url = `https://api.bilibili.com/x/web-interface/search/type?search_type=bili_user&keyword=${encodeURIComponent(username)}`;
const response = await fetch(url, {
credentials: 'include',
headers: {
'User-Agent': navigator.userAgent,
'Referer': 'https://www.bilibili.com/'
}
});
if (!response.ok) {
throw new Error(`网络请求失败: ${response.status}`);
}
const data = await response.json();
if (data.code !== 0) {
throw new Error(`搜索接口错误: ${data.message || data.code}`);
}
if (!data.data || !data.data.result) {
throw new Error('搜索结果为空');
}
// 精确匹配用户名
const user = data.data.result.find(item =>
item.uname && item.uname.trim() === username.trim()
);
if (user && user.mid) {
return user.mid;
} else {
throw new Error('未找到精确匹配的用户');
}
} catch (e) {
throw new Error(`获取UID失败: ${e.message}`);
}
}
// 拉黑指定UID的用户
async function blockUser(uid) {
try {
const biliJct = getCookie('bili_jct');
if (!biliJct) {
throw new Error('未找到CSRF令牌,请确保已登录B站');
}
const response = await fetch('https://api.bilibili.com/x/relation/modify', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': navigator.userAgent,
'Referer': 'https://www.bilibili.com/'
},
body: new URLSearchParams({
fid: uid,
act: 5, // 5表示拉黑
re_src: 11,
csrf: biliJct
})
});
const data = await response.json();
if (data.code !== 0) {
throw new Error(`拉黑失败: ${data.message || '未知错误'}`);
}
return true;
} catch (e) {
throw new Error(`拉黑操作失败: ${e.message}`);
}
}
// 获取Cookie
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
return null;
}
// 日志输出
function log(message, type = 'info') {
const logArea = document.getElementById('logArea');
if (!logArea) return;
const logItem = document.createElement('div');
logItem.className = type;
logItem.innerHTML = `[${new Date().toLocaleTimeString()}] ${message}`;
logArea.appendChild(logItem);
logArea.scrollTop = logArea.scrollHeight;
}
// 批量拉黑主流程
async function startBatchBlock() {
const input = document.getElementById('usernameInput').value;
const usernames = parseUsernames(input);
if (usernames.length === 0) {
log('请输入有效的用户名', 'error');
GM_notification({
text: '请输入有效的用户名',
title: 'B站批量拉黑工具',
timeout: 3000
});
return;
}
log(`开始处理 ${usernames.length} 个用户...`, 'info');
updateStatus('处理中...', `0/${usernames.length}`);
const startBtn = document.getElementById('startBtn');
startBtn.disabled = true;
startBtn.textContent = '处理中...';
let successCount = 0;
let failCount = 0;
for (const [index, username] of usernames.entries()) {
const current = index + 1;
const total = usernames.length;
updateStatus('处理中...', `${current}/${total}`);
log(`处理第 ${current}/${total} 个: ${username}`, 'info');
try {
const uid = await getUidByUsername(username);
log(`获取到UID: ${uid}`, 'info');
await blockUser(uid);
log(`✅ 成功拉黑用户: ${username} (UID: ${uid})`, 'success');
successCount++;
} catch (e) {
log(`❌ 处理失败: ${e.message}`, 'error');
failCount++;
}
// 延迟1秒,避免请求过于频繁
await new Promise(resolve => setTimeout(resolve, 1000));
}
const summary = `批量处理完成 | 成功: ${successCount} | 失败: ${failCount}`;
log(summary, successCount === usernames.length ? 'success' : 'warning');
updateStatus('完成', `${successCount}/${usernames.length}`);
startBtn.disabled = false;
startBtn.textContent = '开始批量拉黑';
GM_notification({
text: `批量拉黑完成:成功${successCount}个,失败${failCount}个`,
title: 'B站批量拉黑工具',
timeout: 5000
});
}
// 注册脚本猫菜单
function registerMenu() {
// 脚本猫菜单命令:点击显示面板
GM_registerMenuCommand('显示批量拉黑面板', showPanel, '📌');
// 可选:添加隐藏面板的菜单
GM_registerMenuCommand('隐藏批量拉黑面板', () => {
const panel = document.getElementById('batchBlockContainer');
if (panel) panel.style.display = 'none';
}, '🙈');
}
// 初始化:只注册菜单,不自动显示面板
function init() {
registerMenu();
log('脚本已就绪,可通过脚本猫菜单打开面板', 'info');
}
// 启动脚本
init();
})();