// ==UserScript== // @name B站动态批量删除小工具 - 交互版 // @namespace https://scriptcat.org/zh-CN/users/210832 // @version 2.15 // @description B站动态批量删除工具,适配新版 Polymer 接口,支持全部删除/抽奖筛选/转发清理/关键词匹配删除 // @author GLM5.2 // @tags B站,工具,清理动态 // @match https://space.bilibili.com/* // @match http://space.bilibili.com/* // @icon https://static.hdslb.com/images/favicon.ico // @grant none // @license GPL-3.0-or-later // @supportURL https://scriptcat.org/zh-CN/users/210832 // @compatible chrome // @compatible edge // @compatible firefox // @run-at document-idle // ==/UserScript== (function () { 'use strict'; // ================= 配置区域 ================= const CONFIG = { // 请求间隔 (毫秒),太快会被B站拦截 INTERVAL: 800, // 如果失败,重试次数 RETRY_LIMIT: 3, // 删除完成后是否自动刷新页面 AUTO_RELOAD: false }; // [修复] 新版动态类型用字符串常量表示 const DYNAMIC_TYPES = { FORWARD: 'DYNAMIC_TYPE_FORWARD', DRAW: 'DYNAMIC_TYPE_DRAW', WORD: 'DYNAMIC_TYPE_WORD', AV: 'DYNAMIC_TYPE_AV', SHORT: 'DYNAMIC_TYPE_SHORT', ARTICLE: 'DYNAMIC_TYPE_ARTICLE' }; // 获取UID和CSRF Token const uid = window.location.pathname.split("/")[1]; function getCSRFToken() { const m = document.cookie.match(/bili_jct=([^;]+)/); return m ? m[1] : ''; } const csrfToken = getCSRFToken(); // [修复] 公共请求头,防止被B站风控拦截返回HTML const COMMON_HEADERS = { 'Accept': 'application/json, text/plain, */*', 'Referer': 'https://www.bilibili.com/', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36' }; // ================= 工具类:解析动态内容 ================= class ContentParser { // [修复] 适配新版 modules 结构 static parse(item) { let text = '(无文字内容)'; let originText = ''; const dyn = item.modules && item.modules.module_dynamic; if (dyn) { // 提取正文 if (dyn.desc && dyn.desc.text) { text = dyn.desc.text; } // 如果没有正文,尝试从 major (视频/文章/图片) 提取标题 if ((!text || text === '(无文字内容)') && dyn.major) { const m = dyn.major; if (m.opus && m.opus.summary && m.opus.summary.text) text = m.opus.summary.text; else if (m.archive && m.archive.title) text = m.archive.title; else if (m.article && m.article.title) text = m.article.title; } } // 提取转发动态的原动态信息 let origin = item.orig || (dyn && dyn.major && dyn.major.opus && dyn.major.opus.orig); if (origin) { try { let oText = ''; if (origin.modules && origin.modules.module_dynamic) { const od = origin.modules.module_dynamic; oText = (od.desc && od.desc.text) || (od.major && od.major.opus && od.major.opus.summary && od.major.opus.summary.text) || (od.major && od.major.archive && od.major.archive.title) || ''; } else if (origin.item) { oText = origin.item.content || origin.item.description || ''; } const author = (origin.modules && origin.modules.module_author && origin.modules.module_author.name) || (origin.user && origin.user.name) || '未知用户'; originText = `[原动态 @${author}]: ${oText || '(无内容)'}`; } catch (e) { originText = '[原动态已失效或无法解析]'; } } return { text, origin: originText, isLottery: this.checkIsLottery(item) }; } // [修复] 抽奖判断:兼容多种字段位置 + 关键词兜底 static checkIsLottery(item) { const orig = item.orig; if (orig) { if (orig.origin_extension && orig.origin_extension.lott) return true; if (orig.origin_extend_json) { try { if (JSON.parse(orig.origin_extend_json).lott) return true; } catch (e) {} } if (orig.modules && orig.modules.module_dynamic && orig.modules.module_dynamic.additional) { const add = orig.modules.module_dynamic.additional; if (add.lottery || (add.common && JSON.stringify(add.common).includes('lottery'))) return true; } } const dyn = item.modules && item.modules.module_dynamic; if (dyn && dyn.additional && dyn.additional.lottery) return true; try { if (JSON.stringify(item).toLowerCase().includes('"lottery"')) return true; } catch (e) {} return false; } } // ================= API类(适配新版端点) ================= class DynamicAPI { constructor() { // [修复] 列表和删除接口全部替换为 api.bilibili.com 的新端点 this.listUrl = 'https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/space'; this.delUrl = 'https://api.bilibili.com/x/dynamic/feed/operate/remove'; } async getSpaceHistory(offset = '') { // [修复] 参数改为 host_mid + offset(首次为空字符串) const url = `${this.listUrl}?host_mid=${uid}&offset=${encodeURIComponent(offset)}&timezone_offset=-480&platform=web`; const res = await fetch(url, { method: 'GET', credentials: 'include', headers: COMMON_HEADERS }); // [修复] 先拿文本,避免直接 .json() 抛错无法定位 const txt = await res.text(); if (!txt || txt.trim().startsWith('<')) { throw new Error(`返回非JSON(HTTP ${res.status}):${txt.slice(0, 60)}`); } return JSON.parse(txt); } async deleteDynamic(dynamicId) { const url = `${this.delUrl}?csrf=${csrfToken}`; // [修复] 新版删除用 JSON body,字段名 dyn_id_str const res = await fetch(url, { method: 'POST', credentials: 'include', headers: { ...COMMON_HEADERS, 'Content-Type': 'application/json' }, body: JSON.stringify({ dyn_id_str: String(dynamicId) }) }); const txt = await res.text(); if (!txt || txt.trim().startsWith('<')) { throw new Error(`删除返回非JSON(HTTP ${res.status})`); } return JSON.parse(txt); } sleep(ms) { return new Promise(r => setTimeout(r, ms)); } } const api = new DynamicAPI(); let isProcessing = false; let deleteCount = 0; // ================= UI管理器 ================= class UIManager { constructor() { this.panel = null; } createPanel() { const panel = document.createElement('div'); panel.className = 'bili-cleaner-panel'; panel.innerHTML = `

🛡️ 动态清理助手 v2.14

就绪
等待操作...
`; this.addStyles(); return panel; } addStyles() { const style = document.createElement('style'); style.textContent = ` .bili-cleaner-panel { background:#fff; border:1px solid #ddd; border-radius:8px; padding:15px; margin-bottom:20px; box-shadow:0 2px 10px rgba(0,0,0,.05); font-family:sans-serif; } .cleaner-header { display:flex; justify-content:space-between; align-items:center; margin-bottom:15px; border-bottom:1px solid #eee; padding-bottom:10px; } .cleaner-header h3 { margin:0; color:#00a1d6; font-size:16px; } .status-indicator { font-size:12px; color:#666; background:#f4f4f4; padding:2px 8px; border-radius:4px; } .status-indicator.running { color:#fff; background:#f6a500; } /* [新增] flex-wrap 允许按钮换行,避免挤压 */ .action-buttons { display:flex; flex-wrap: wrap; gap:10px; margin-bottom:15px; } .cleaner-btn { flex: 1 1 45%; padding:8px 0; border:1px solid #e7e7e7; background:#f9f9f9; border-radius:6px; cursor:pointer; font-size:13px; color:#333; transition:all .2s; } .cleaner-btn:hover { border-color:#00a1d6; color:#00a1d6; background:#eef8fc; } .cleaner-btn:disabled { opacity:.6; cursor:not-allowed; } .result-box { background:#f4f5f7; border-radius:6px; padding:10px; font-size:12px; color:#555; max-height:120px; overflow-y:auto; } .cleaner-modal-overlay { position:fixed; inset:0; background:rgba(0,0,0,.6); z-index:10000; display:flex; justify-content:center; align-items:center; } .cleaner-modal { background:#fff; width:400px; max-width:90%; border-radius:12px; padding:20px; box-shadow:0 10px 30px rgba(0,0,0,.2); } .modal-title { font-size:16px; font-weight:bold; margin-bottom:15px; color:#333; } .modal-content { background:#f8f9fa; padding:12px; border-radius:6px; border:1px solid #eee; margin-bottom:20px; font-size:14px; line-height:1.5; max-height:300px; overflow-y:auto; } .modal-origin { margin-top:10px; padding-top:10px; border-top:1px dashed #ccc; color:#888; font-size:13px; } .modal-actions { display:flex; gap:10px; } .modal-btn { flex:1; padding:10px; border:none; border-radius:6px; font-weight:bold; cursor:pointer; } .btn-confirm-del { background:#ff4d4d; color:#fff; } .btn-skip{background:#e7e7e7;color:#333;} .btn-stop{background:#333;color:#fff;} `; document.head.appendChild(style); } updateStatus(text, running = false) { const el = document.getElementById('statusIndicator'); if (el) { el.textContent = text; el.className = `status-indicator ${running ? 'running' : ''}`; } } log(msg, isError = false) { const box = document.querySelector('.result-content'); if (box) { const t = new Date().toTimeString().split(' ')[0]; const p = document.createElement('div'); p.style.color = isError ? '#ff4d4d' : '#333'; p.textContent = `[${t}] ${msg}`; box.insertBefore(p, box.firstChild); } } showConfirmModal(parsedData) { return new Promise(resolve => { const overlay = document.createElement('div'); overlay.className = 'cleaner-modal-overlay'; overlay.innerHTML = `
`; document.body.appendChild(overlay); const rm = () => document.body.removeChild(overlay); document.getElementById('c_del').onclick = () => { rm(); resolve('delete'); }; document.getElementById('c_skip').onclick = () => { rm(); resolve('skip'); }; document.getElementById('c_stop').onclick = () => { rm(); resolve('stop'); }; }); } } // ================= 核心逻辑 ================= const uiManager = new UIManager(); // [新增] 接收 keyword 参数 async function processDynamics(mode, keyword = '') { if (isProcessing) return; isProcessing = true; deleteCount = 0; uiManager.updateStatus('运行中...', true); // [新增] 日志输出对应模式 let modeText = '全部删除'; if (mode === 'lottery') modeText = '筛选抽奖'; else if (mode === 'repost') modeText = '删除转发'; else if (mode === 'keyword') modeText = `关键词删除[${keyword}]`; uiManager.log(`开始任务: ${modeText}`); document.querySelectorAll('.cleaner-btn').forEach(b => b.disabled = true); // [新增] 预处理关键词列表,不区分大小写 const keywords = keyword.split(/[,,\s]+/).map(s => s.trim().toLowerCase()).filter(Boolean); let offset = ''; let hasMore = true; let consecutiveErrors = 0; try { while (hasMore && isProcessing) { let data; try { const res = await api.getSpaceHistory(offset); if (res.code !== 0) throw new Error(res.message || ('code=' + res.code)); data = res.data; consecutiveErrors = 0; } catch (e) { consecutiveErrors++; uiManager.log(`获取列表失败 (${consecutiveErrors}/${CONFIG.RETRY_LIMIT}): ${e.message}`, true); if (consecutiveErrors >= CONFIG.RETRY_LIMIT) break; await api.sleep(2000); continue; } // [修复] 新版字段 const items = (data && data.items) || []; if (items.length === 0) break; hasMore = !!data.has_more; for (const item of items) { if (!isProcessing) break; const dynamicId = item.id_str; // [修复] const type = item.type; // [修复] 字符串 const parsedContent = ContentParser.parse(item); let shouldDelete = false; if (mode === 'all') { shouldDelete = true; } else if (mode === 'repost') { // [修复] 转发类型判断改为字符串,或存在 orig 字段 if (type === DYNAMIC_TYPES.FORWARD || item.orig) shouldDelete = true; } else if (mode === 'lottery') { if (parsedContent.isLottery) { const action = await uiManager.showConfirmModal(parsedContent); if (action === 'stop') { isProcessing = false; uiManager.log('用户终止操作'); break; } if (action === 'delete') shouldDelete = true; else uiManager.log(`已跳过: ${parsedContent.text.substring(0, 20)}...`); } } // [新增] 关键词匹配模式 else if (mode === 'keyword') { // 将动态正文与转发原文合并,并转为小写以不区分大小写匹配 const textToCheck = (parsedContent.text + " " + parsedContent.origin).toLowerCase(); // 只要包含任意一个关键词,即触发删除 const hit = keywords.some(kw => textToCheck.includes(kw)); if (hit) { shouldDelete = true; } } if (shouldDelete) { try { const delRes = await api.deleteDynamic(dynamicId); if (delRes.code === 0) { deleteCount++; uiManager.log(`已删除[${deleteCount}]: ${parsedContent.text.substring(0, 15)}...`); } else { uiManager.log(`删除失败: ${delRes.message || JSON.stringify(delRes)}`, true); } await api.sleep(CONFIG.INTERVAL); } catch (e) { uiManager.log(`API请求错误: ${e.message}`, true); } } } // [修复] 翻页游标用 data.offset,没有则终止 offset = data.offset || ''; if (!offset) hasMore = false; await api.sleep(1000); } } catch (e) { console.error(e); uiManager.log(`发生严重错误: ${e.message}`, true); } finally { isProcessing = false; uiManager.updateStatus('完成'); uiManager.log(`任务结束,共删除 ${deleteCount} 条动态`); document.querySelectorAll('.cleaner-btn').forEach(b => b.disabled = false); if (CONFIG.AUTO_RELOAD) setTimeout(() => location.reload(), 3000); } } // ================= 初始化 ================= async function init() { // 延时确保页面加载 await new Promise(r => setTimeout(r, 1500)); const panel = uiManager.createPanel(); // 尝试插入位置:B站新旧版界面不同 const containers = [ "#app > main > div.space-dynamic > div.space-dynamic__right", // 新版 "#page-dynamic .col-2", // 旧版 ".bili-dyn-home--right" // 另一种新版 ]; let inserted = false; for (const sel of containers) { const c = document.querySelector(sel); if (c) { if (c.firstChild) c.insertBefore(panel, c.firstChild); else c.appendChild(panel); inserted = true; break; } } if (!inserted) { // 如果都找不到,就悬浮显示 panel.style.position = 'fixed'; panel.style.bottom = '20px'; panel.style.right = '20px'; panel.style.width = '320px'; panel.style.zIndex = '9999'; document.body.appendChild(panel); } // 绑定事件 document.getElementById('btnDeleteAll').onclick = () => { if (confirm('警告:确定要删除【所有】动态吗?不可恢复!')) processDynamics('all'); }; document.getElementById('btnDeleteLottery').onclick = () => processDynamics('lottery'); document.getElementById('btnDeleteRepost').onclick = () => { if (confirm('确定要删除所有【转发】动态吗?')) processDynamics('repost'); }; // [新增] 绑定关键词删除事件 document.getElementById('btnDeleteKeyword').onclick = () => { const input = prompt('请输入要匹配的关键词:\n(多个词可用逗号或空格分隔,不区分大小写)\n动态正文或转发原文包含任一关键词即删除:'); if (input !== null && input.trim() !== '') { processDynamics('keyword', input.trim()); } }; console.log('B站动态清理助手 v2.14 已加载'); } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); else init(); })();