// ==UserScript== // @name B站动态批量删除助手 // @version 0.24 // @description 这是一个帮助B站用户高效管理个人动态的脚本,支持多种类型动态的批量删除操作。 // @author 梦把我 // @match https://space.bilibili.com/* // @match http://space.bilibili.com/* // @require https://greasyfork.org/scripts/38220-mscststs-tools/code/MSCSTSTS-TOOLS.js?version=713767 // @require https://cdn.jsdelivr.net/npm/axios@1.7.3/dist/axios.min.js // @icon https://static.hdslb.com/images/favicon.ico // @namespace https://greasyfork.org/users/1383389 // @license MIT // @grant none // @downloadURL none // ==/UserScript== (function () { 'use strict'; const uid = window.location.pathname.split("/")[1]; function getUserCSRF() { return document.cookie.split("; ").find(row => row.startsWith("bili_jct="))?.split("=")[1]; } const csrfToken = getUserCSRF(); class Api { constructor() { } async spaceHistory(offset = 0) { // 获取个人动态 return this.retryOn429(() => this._api( `https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/space_history?visitor_uid=${uid}&host_uid=${uid}&offset_dynamic_id=${offset}`, {}, "get" )); } async removeDynamic(id) { // 删除动态 return this._api( "https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/rm_dynamic", { dynamic_id: id, csrf_token: csrfToken } ); } async _api(url, data, method = "post") { // 通用请求 return axios({ url, method, data: this.transformRequest(data), withCredentials: true, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }).then(res => res.data); } transformRequest(data) { // 转换请求参数 return Object.entries(data).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join('&'); } async fetchJsonp(url) { // jsonp请求 return fetchJsonp(url).then(res => res.json()); } async retryOn429(func, retries = 5, delay = 100) { // 出现429错误时冷却100ms重试,出现412错误时提示并退出 while (retries > 0) { try { return await func(); } catch (err) { if (err.response && err.response.status === 429) { await this.sleep(delay); retries--; } else if (err.response && err.response.status === 412) { alert('由于请求过于频繁,IP暂时被ban,请更换IP或稍后再试。'); throw new Error('IP blocked, please retry later.'); } else { throw err; } } } throw new Error('Too many retries, request failed.'); } sleep(ms) { // 睡眠 return new Promise(resolve => setTimeout(resolve, ms)); } } const api = new Api(); const buttons = [".onlyDeleteRepost", ".deleteVideo", ".deleteImage", ".deleteText", ".deleteCustomType"]; let logNode; // 添加确认状态管理 const confirmStates = { deleteStates: {}, resetTimer: null }; async function init() { // 检查是否在自己的空间页面 try { // 等待页面加载完成 await new Promise(resolve => setTimeout(resolve, 1000)); // 尝试新版界面的验证 const newVersionText = document.evaluate( '//*[@id="app"]/div[2]/div[3]/div[2]/div[1]/div/div/text()', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null ).singleNodeValue; // 尝试旧版界面的验证 const oldVersionText = await mscststs.wait(".h-version-state", true, 100); // 检查是否为自己的空间 if ((!newVersionText || !newVersionText.textContent.includes("我自己")) && (!oldVersionText || oldVersionText.innerText !== "我自己")) { console.log('当前不是自己的个人动态'); return; } // 创建控制面板节点 const node = createControlPanel(); // 尝试插入到新版或旧版界面 try { // 先尝试获取新版界面的位置 const newVersionContainer = document.querySelector("#app > main > div.space-dynamic > div.space-dynamic__right"); if (newVersionContainer) { // 获取第一个子元素作为参考 const firstChild = newVersionContainer.querySelector("div:nth-child(1)"); if (firstChild) { // 在第一个子元素之前插入我们的面板 newVersionContainer.insertBefore(node, firstChild); } else { // 如果没有子元素,直接追加 newVersionContainer.appendChild(node); } } else { // 如果找不到新版界面,尝试旧版界面 const oldVersionContainer = document.querySelector("#page-dynamic .col-2"); if (oldVersionContainer) { oldVersionContainer.appendChild(node); } else { throw new Error('无法找到合适的插入位置'); } } // 获取日志节点引用 logNode = document.querySelector(".msc_panel .log"); // 设置事件监听 setEventListeners(); // 设置教程链接 document.querySelector('.tutorial-btn').href = '占位符URL'; // 添加样式 addConfirmationStyles(); } catch (error) { console.error('插入控制面板失败:', error); alert('插入控制面板失败,请检查是否支持当前版本的B站界面'); } } catch (error) { console.error('验证用户身份失败:', error); alert('无法验证是否为个人空间,请确保在自己的动态页面使用此脚本'); } } function createControlPanel() { const node = document.createElement("div"); node.className = "msc_panel"; node.innerHTML = `

动态类型对照表

类型值含义
1转发
2图片动态
4文字动态
8视频动态
16小视频
64专栏

自定义删除

快捷操作

转发相关

其他

`; return node; } function setEventListeners() { // 为每个按钮添加确认机制 document.querySelector(".onlyDeleteRepost").addEventListener("click", () => handleConfirmation("onlyDeleteRepost", () => handleDelete(false))); document.querySelector(".deleteVideo").addEventListener("click", () => handleConfirmation("deleteVideo", () => handleDeleteByType(8))); document.querySelector(".deleteImage").addEventListener("click", () => handleConfirmation("deleteImage", () => handleDeleteByType(2))); document.querySelector(".deleteText").addEventListener("click", () => handleConfirmation("deleteText", () => handleDeleteByType(4))); document.querySelector(".deleteCustomType").addEventListener("click", () => { const typeValue = parseInt(document.querySelector(".type-input").value); if (isValidDynamicType(typeValue)) { handleConfirmation("deleteCustomType", () => handleDeleteByType(typeValue)); } else { alert("请输入有效的动态类型值!\n有效值包括:1(转发)、2(图片)、4(文字)、8(视频)、16(小视频)、64(专栏)"); resetButtonState("deleteCustomType"); enableAll(); } }); } async function handleDelete(deleteLottery) { // 删除参数 unfollow disableAll(); let deleteCount = 0; // 删除计数 let hasMore = true; // 是否还有更多动态 let offset = 0; // 动态偏移量 while (hasMore) { const { data } = await api.spaceHistory(offset); hasMore = data.has_more; for (const card of data.cards) { offset = card.desc.dynamic_id_str; if (card.desc.orig_dy_id != 0) { // 如果是转发动态 try { const content = JSON.parse(card.card); const content2 = JSON.parse(content.origin_extend_json); if (!deleteLottery || content2.lott) { // 如果"仅删除抽奖"为假,或判断为抽奖动态 const rm = await api.removeDynamic(card.desc.dynamic_id_str); if (rm.code === 0) deleteCount++; else throw new Error("删除出错"); } await api.sleep(50); log(`已删除 ${deleteCount} 条动态`); } catch (e) { console.error(e); break; } } } } enableAll(); } function disableAll() { console.log('start'); buttons.forEach(btn => { const button = document.querySelector(btn); button.disabled = true; resetButtonState(btn.substring(1)); // 移除开头的点号 }); confirmStates.deleteStates = {}; // 清除所有确认状态 } function enableAll() { console.log('done'); buttons.forEach(btn => { const button = document.querySelector(btn); if (button) { button.disabled = false; resetButtonState(btn.substring(1)); } }); confirmStates.deleteStates = {}; log('操作已完成!', true); } function log(message, autoRefresh = false) { logNode.innerText = message; if (autoRefresh) { let countdown = 3; const timer = setInterval(() => { logNode.innerText = `${message} (${countdown}秒后自动刷新)`; countdown--; if (countdown < 0) { clearInterval(timer); window.location.reload(); } }, 1000); } } async function handleDeleteByType(targetType) { if (!isValidDynamicType(targetType)) { log('无效的动态类型值'); enableAll(); return; } try { disableAll(); let deleteCount = 0; let hasMore = true; let offset = 0; while (hasMore) { const { data } = await api.spaceHistory(offset); hasMore = data.has_more; for (const card of data.cards) { offset = card.desc.dynamic_id_str; if (card.desc.type === targetType) { try { const rm = await api.removeDynamic(card.desc.dynamic_id_str); if (rm.code === 0) deleteCount++; else throw new Error("删除出错"); await api.sleep(50); log(`已删除 ${deleteCount} 条类型为 ${targetType} 的动态`); } catch (e) { console.error(e); break; } } } } } catch (error) { console.error('删除操作执行出错:', error); log('操作执行出错,请重试'); } finally { enableAll(); } } // 添加确认处理函数 function handleConfirmation(buttonId, callback) { const button = document.querySelector(`.${buttonId}`); if (!button) return; const originalText = button.textContent; // 如果是首次点击 if (!confirmStates.deleteStates[buttonId]) { // 设置确认状态 confirmStates.deleteStates[buttonId] = true; // 修改按钮文字 button.textContent = "确认删除?"; button.style.backgroundColor = "#ff6b6b"; // 添加闪烁动画 button.style.animation = "buttonBlink 1s infinite"; // 5秒后重置状态 setTimeout(() => { resetButtonState(buttonId); }, 5000); // 显示提示 log("请再次点击确认删除操作"); } else { try { // 第二次点击,执行删除 resetButtonState(buttonId); callback(); } catch (error) { console.error('执行删除操作时出错:', error); resetButtonState(buttonId); enableAll(); log('操作执行出错,请重试'); } } } // 重置按钮状态 function resetButtonState(buttonId) { const button = document.querySelector(`.${buttonId}`); if (!button) return; // 重置确认状态 confirmStates.deleteStates[buttonId] = false; // 重置按钮状态 button.disabled = false; button.textContent = getOriginalButtonText(buttonId); button.style.backgroundColor = ""; button.style.animation = ""; // 清除可能存在的定时器 if (confirmStates.resetTimer) { clearTimeout(confirmStates.resetTimer); confirmStates.resetTimer = null; } } // 获取按钮原始文字 function getOriginalButtonText(buttonId) { const textMap = { 'onlyDeleteRepost': '删除转发动态', 'deleteVideo': '删除视频动态', 'deleteImage': '删除图片动态', 'deleteText': '删除文字动态', 'deleteCustomType': '删除指定类型动态' }; return textMap[buttonId] || '删除'; } // 添加闪烁动画样式 function addConfirmationStyles() { const style = document.createElement('style'); style.textContent = ` .msc_panel { max-width: 100%; /* 修改最大宽度以适应侧边栏 */ margin: 0 0 20px 0; /* 修改边距以适应新布局 */ padding: 20px; background: #fff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } .panel-section { margin-bottom: 24px; padding-bottom: 20px; border-bottom: 1px solid #eee; } .panel-section:last-child { border-bottom: none; margin-bottom: 0; } .panel-section h3 { font-size: 16px; color: #18191c; margin-bottom: 16px; font-weight: 500; } .type-table table { width: 100%; border-collapse: collapse; margin: 10px 0; font-size: 14px; } .type-table th, .type-table td { padding: 8px; text-align: center; border: 1px solid #eee; } .type-table th { background: #f6f7f8; } .input-group { display: flex; gap: 10px; margin-bottom: 10px; } .type-input { flex: 1; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; } .button-group { display: flex; flex-wrap: wrap; gap: 10px; } .msc_panel button { padding: 8px 16px; border-radius: 4px; border: 1px solid #ddd; background: #fff; cursor: pointer; font-size: 14px; transition: all 0.2s; } .msc_panel button:hover { background: #f6f7f8; } .msc_panel button.primary-btn { background: #00aeec; color: #fff; border-color: #00aeec; } .msc_panel button.primary-btn:hover { background: #0096cc; } .msc_panel button.warning-btn { background: #fb7299; color: #fff; border-color: #fb7299; } .msc_panel button.warning-btn:hover { background: #e45c80; } .msc_panel button:disabled { background: #eee; color: #999; cursor: not-allowed; border-color: #ddd; } .tutorial-btn { display: inline-block; padding: 8px 16px; background: #6c757d; color: #fff; text-decoration: none; border-radius: 4px; transition: all 0.2s; } .tutorial-btn:hover { background: #5a6268; } .log { margin-top: 16px; padding: 12px; background: #f6f7f8; border-radius: 4px; font-size: 14px; color: #666; } @keyframes buttonBlink { 0% { opacity: 1; } 50% { opacity: 0.7; } 100% { opacity: 1; } } .msc_panel button.confirming { background-color: #ff6b6b !important; color: white !important; } .msc_panel button:disabled { animation: none !important; opacity: 0.5 !important; } `; document.head.appendChild(style); } // 添加动态类型验证函数 function isValidDynamicType(type) { const validTypes = [1, 2, 4, 8, 16, 64, 256, 512]; return validTypes.includes(type); } init(); })();