您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
这是一个帮助B站用户高效管理个人动态的脚本,支持多种类型动态的批量删除操作。
当前为
// ==UserScript== // @name B站动态批量删除助手 // @version 0.22 // @description 这是一个帮助B站用户高效管理个人动态的脚本,支持多种类型动态的批量删除操作。 // @author 梦把我 // @match https://space.bilibili.com/* // @match http://space.bilibili.com/* // @require http://greasyfork.icu/scripts/38220-mscststs-tools/code/MSCSTSTS-TOOLS.js?version=713767 // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js // @icon https://static.hdslb.com/images/favicon.ico // @license MIT // @grant none // @namespace http://greasyfork.icu/users/1383389 // ==/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 = [".onlyDeleteAll", ".onlyDeleteRepost", ".deleteVideo", ".deleteImage", ".deleteText", ".deleteCustomType"]; let logNode; // 添加确认状态管理 const confirmStates = { deleteStates: {}, resetTimer: null }; async function init() { const shijiao = await mscststs.wait(".h-version-state", true, 100); if (!shijiao || shijiao.innerText != "我自己") { console.log('当前不是自己的个人动态'); return; } await Promise.all([ mscststs.wait("#page-dynamic"), mscststs.wait("#page-dynamic .col-2") ]); const node = createControlPanel(); document.querySelector("#page-dynamic .col-2").append(node); logNode = document.querySelector(".msc_panel .log"); setEventListeners(); // 设置教程链接 document.querySelector('.tutorial-btn').href = '占位符URL'; // 添加样式 const style = document.createElement('style'); style.textContent = ` .msc_panel { max-width: 800px; margin: 20px auto; 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); addConfirmationStyles(); } function createControlPanel() { const node = document.createElement("div"); node.className = "msc_panel"; node.innerHTML = ` <div class="inner"> <div class="panel-section type-table"> <h3>动态类型对照表</h3> <table border="1"> <tr><th>类型值</th><th>含义</th></tr> <tr><td>1</td><td>转发</td></tr> <tr><td>2</td><td>图片动态</td></tr> <tr><td>4</td><td>文字动态</td></tr> <tr><td>8</td><td>视频动态</td></tr> <tr><td>16</td><td>小视频</td></tr> <tr><td>64</td><td>专栏</td></tr> <tr><td>256</td><td>音频</td></tr> </table> </div> <div class="panel-section custom-delete"> <h3>自定义删除</h3> <div class="input-group"> <input type="number" class="type-input" placeholder="输入要删除的动态类型值"> <button class="deleteCustomType primary-btn">删除指定类型动态</button> </div> </div> <div class="panel-section quick-actions"> <h3>快捷操作</h3> <div class="button-group"> <button class="deleteVideo">删除视频动态</button> <button class="deleteImage">删除图片动态</button> <button class="deleteText">删除文字动态</button> </div> </div> <div class="panel-section lottery-actions"> <h3>抽奖相关</h3> <div class="button-group"> <button class="onlyDeleteAll">删除抽奖动态</button> </div> </div> <div class="panel-section repost-actions"> <h3>转发相关</h3> <div class="button-group"> <button class="onlyDeleteRepost">删除转发动态</button> </div> </div> <div class="panel-section other-actions"> <h3>其他</h3> <div class="button-group"> <a href="#" class="tutorial-btn" target="_blank">使用视频教程</a> </div> </div> <div class="panel-section"> <div class="log"></div> </div> </div>`; return node; } function setEventListeners() { // 为每个按钮添加确认机制 document.querySelector(".onlyDeleteAll").addEventListener("click", () => handleConfirmation("onlyDeleteAll", () => handleDelete(true))); 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 (typeValue) { handleConfirmation("deleteCustomType", () => handleDeleteByType(typeValue)); } else { alert("请输入有效的动态类型值"); } }); } 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 => document.querySelector(btn).disabled = false); 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) { 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; } } } } enableAll(); } // 添加确认处理函数 function handleConfirmation(buttonId, callback) { const button = document.querySelector(`.${buttonId}`); 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 { // 第二次点击,执行删除 resetButtonState(buttonId); callback(); } } // 重置按钮状态 function resetButtonState(buttonId) { const button = document.querySelector(`.${buttonId}`); if (!button) return; // 重置确认状态 confirmStates.deleteStates[buttonId] = false; // 恢复按钮样式 button.textContent = getOriginalButtonText(buttonId); button.style.backgroundColor = ""; button.style.animation = ""; } // 获取按钮原始文字 function getOriginalButtonText(buttonId) { const textMap = { 'onlyDeleteAll': '删除抽奖动态', 'onlyDeleteRepost': '删除转发动态', 'deleteVideo': '删除视频动态', 'deleteImage': '删除图片动态', 'deleteText': '删除文字动态', 'deleteCustomType': '删除指定类型动态' }; return textMap[buttonId] || '删除'; } // 添加闪烁动画样式 function addConfirmationStyles() { const style = document.createElement('style'); style.textContent = ` @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); } init(); })();