// ==UserScript== // @name Bili.Dynamic.AutoDel // @namespace https://github.com/ // @version 2024.03.03 // @description 删除B站转发的已开奖动态和源动态已被删除的动态。 // @author monSteRhhe // @match http*://*.bilibili.com/* // @icon https://www.bilibili.com/favicon.ico // @grant GM_info // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_notification // @grant GM_registerMenuCommand // @run-at document-end // @require https://unpkg.com/axios/dist/axios.min.js // ==/UserScript== /* globals axios, waitForKeyElements */ (function() { 'use strict'; /** * 初始化数据的值 */ if (GM_getValue('set-unfollow') == undefined) { GM_setValue('set-unfollow', false); } if (GM_getValue('unfollow-list') == undefined || GM_getValue('unfollow-list').length != 0) { GM_setValue('unfollow-list', []); } /** * 弹窗样式 */ let style = ` .setting-content { color: #000; z-index: 10; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 400px; height: 310px; background-color: #efecfa; border-radius: 10px; padding: 10px; } .setting-content .setting-header { font-size: 25px; line-height: 25px; padding: 5px 20px; margin-bottom: 10px; } .setting-content .setting-body { width: 340px; height: 190px; margin: 0 auto; padding: 10px 10px 0 10px; background-color: #fff; border-radius: 10px; padding: 10px; font-size: 15px; overflow-y: auto; } .setting-content .setting-footer { text-align: right; padding: 17px 20px 17px 0; } .setting-content .setting-footer button { cursor: pointer; border-radius: 25px; background-color: #ffffff; border: none; height: 30px; min-width: 50px; padding: 5px 10px; font-size: 85%; } `; GM_addStyle(style); /** * 打开设置弹窗 */ function openSettingWindow() { // 创建弹窗 let main_window = document.createElement('div'); main_window.className = 'setting-popup'; main_window.innerHTML = `
设置
`; document.body.appendChild(main_window); // 绑定点击事件 document.querySelector('.setting-close').addEventListener('click', closeSettingWindow); // 设置选中状态 let checkbox_list = document.querySelectorAll('.setting-body input'); for (let node of checkbox_list) { node.checked = GM_getValue(node.id); node.addEventListener('change', () => { GM_setValue(node.id, node.checked); }) } } /** * 关闭弹窗 */ function closeSettingWindow() { document.body.removeChild(document.querySelector('.setting-popup')); } /** * 获取 X 天前的日期 * @param {string} num 往前的天数 * @returns 返回之前的日期,格式YY-MM-DD */ function getBeforeDate(num) { let d = new Date(); d.setDate(d.getDate() - num); let year = d.getFullYear(), month = d.getMonth() + 1, // getMonth() 返回的值为月份数-1 day = d.getDate(), before_date = year + '-' + (month < 10 ? ('0' + month) : month) + '-' + (day < 10 ? ('0' + day) : day); return before_date; } /** * 时间戳转日期 * @param {number} ts 时间戳 (秒) * @returns 返回源动态日期,格式YY-MM-DD */ function timestampToDate(ts) { let date = new Date(ts * 1000), year = date.getFullYear(), month = date.getMonth() + 1, day = date.getDate(), dyn_date = year + '-' + (month < 10 ? ('0' + month) : month) + '-' + (day < 10 ? ('0' + day) : day); return dyn_date; } /** * 获取动态信息 * @param {string} duid 用户的 DedeUserID * @param {string} offset 前往下一页动态的参数 * @param {string} mode 选择的模式 * @param {string} input 输入的内容 */ function getDynamics(duid, offset, mode, input) { let dynamics_api = 'https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/space?offset=' + offset + '&host_mid=' + duid, // 动态 API lottery_api = 'https://api.vc.bilibili.com/lottery_svr/v1/lottery_svr/lottery_notice?business_type=4&business_id='; // 互动抽奖 API axios({ url: dynamics_api, withCredentials: true // 跨域使用凭证 }) .then(function(response) { if (offset == '') { if (mode == 'auto') { sendNotification('开始自动判断删除互动抽奖动态。'); } if (mode == 'user') { sendNotification('开始删除转发用户 ' + input + ' 的动态。'); } if (mode == 'days_ago') { sendNotification('开始删除 ' + getBeforeDate(input) + ' 之前的动态。'); } } if (response.data.code == 0) { let items_list = response.data.data.items; // 动态信息的数组 items_list.forEach(function(data) { if (data.orig != null) { let orig_id_str = data.orig.id_str; // 源动态 ID if (mode == 'auto') { //* "源动态已被作者删除" -> 源动态 ID 为 null if (data.orig.id_str == null) { deleteDynamic(data) } axios({ url: lottery_api + orig_id_str }) .then(function(response) { // code = 0,有“互动抽奖”信息 if (response.data.code == '0') { //* status = 0 -> 未开奖,2 -> 已开奖 if (response.data.data.status == '2') { deleteDynamic(data); if (GM_getValue('set-unfollow') && data.orig.modules.module_author.following) { saveUnfollowUser(data) } } } }) } if (mode == 'user') { //* 判断用户名 / UID if (input.indexOf(data.orig.modules.module_author.name) != -1 || input.indexOf(data.orig.modules.module_author.mid) != -1) { deleteDynamic(data); if (GM_getValue('set-unfollow') && data.orig.modules.module_author.following) { saveUnfollowUser(data) } } } if (mode == 'days_ago') { let dyn_timestamp = data.orig.modules.module_author.pub_ts, // 源动态发布时间戳 (秒) status = '0'; axios({ url: lottery_api + orig_id_str }) .then(function(response) { if (response.data.code == '0') { //* status = 0 -> 未开奖,2 -> 已开奖 if (response.data.data.status == '2') { status = '2'; } } else { status = '-9999'; // code = -9999,无互动抽奖 } //* 比较动态与设定日期 + 排除互动抽奖未开奖的动态 if (timestampToDate(dyn_timestamp) <= getBeforeDate(input) && status != '0') { deleteDynamic(data); if (GM_getValue('set-unfollow') && data.orig.modules.module_author.following) { saveUnfollowUser(data) } } }) } } }) offset = response.data.data.offset; if (offset != '') { getDynamics(duid, offset, mode, input); } else { sendNotification('你已经到达了世界的尽头。'); // 取关 if (GM_getValue('set-unfollow')) { unfollowUser(); } } } }) } /** * 删除动态 * @param {object} item 每条动态的信息 */ function deleteDynamic(item) { //* csrf 参数 -> 从 cookie 获取 bili_jct let delete_api = 'https://api.bilibili.com/x/dynamic/feed/operate/remove?csrf=' + getCookie(' bili_jct'), re_id_str = item.id_str; // 转发动态的 ID console.log('[' + GM_info.script.name + ']', 'https://www.bilibili.com/opus/' + re_id_str); // 控制台输出动态网址 axios({ method: 'post', url: delete_api, withCredentials: true, data: { dyn_id_str: re_id_str } }) .then(function(response) { if (response.data.code == '0') { sendNotification(re_id_str + ' 删除成功。'); } }) } /** * 删除动态 * @param {object} data 每条动态的信息 */ function saveUnfollowUser(data) { let unfollow_arr = GM_getValue('unfollow-list'), uid = data.orig.modules.module_author.mid; if (unfollow_arr.indexOf(uid) == -1) { unfollow_arr.push(uid); GM_setValue('unfollow-list', unfollow_arr); } } /** * 取关用户 */ function unfollowUser() { let unfollow_api = 'https://api.bilibili.com/x/relation/modify', unfollow_list = GM_getValue('unfollow-list'); for (let uid of unfollow_list) { // console.log(uid + ' 取关成功。'); axios({ method: 'post', url: unfollow_api, withCredentials: true, data: { fid: uid, act: 2, re_src: 11, spmid: '333.999.0.0', csrf: getCookie(' bili_jct') } }) .then(function(response) { if (response.data.code == '0') { sendNotification(uid + ' 取关成功。'); } }) } } /** * 显示通知 * @param {string} msg 发送的通知消息 */ function sendNotification(msg) { GM_notification({ text: msg, title: GM_info.script.name, image: GM_info.script.icon, timeout: 1500, }); } /** * 获取 cookie * @param {string} key 所需的 cookie 的键 * @returns 返回 cookie 的值 */ function getCookie(key) { let cookieArr = document.cookie.split(';'); for (var i = 0; i < cookieArr.length; i++) { if (cookieArr[i].split('=')[0] == key) { let value = cookieArr[i].split('=')[1]; return value; } } } /** * 启动 * @param {string} mode 选择的模式 */ function start(mode) { let duid = getCookie(' DedeUserID'), input = ''; if(duid == undefined) { sendNotification('未检测到登录状态。'); // 未登录时 DedeUserID 未定义 } else { if (mode == 'user') { input = prompt('请输入想要删除的用户名或 UID (多个则用英文逗号「,」进行分割) :'); if (input == '' || input == undefined) { sendNotification('没有输入内容!') return false } } if (mode == 'days_ago') { input = prompt('请输入想要删除多少天前的动态 (整数即可) :'); if (!isNaN(input)) { sendNotification('输入错误!') return false; } } getDynamics(duid, '', mode, input); } } /** * 删除源动态已开奖 / 已删除对应的转发动态 */ GM_registerMenuCommand('自动判断', () => { start('auto'); }) /** * 删除源动态用户名 / UID对应的转发动态 */ GM_registerMenuCommand('指定用户', () => { start('user'); }) /** * 删除X天前发布的源动态对应的转发动态 */ GM_registerMenuCommand('删除X天前的转发动态', () => { start('days_ago'); }) /** * 打开设置弹窗 */ GM_registerMenuCommand('打开设置', () => { openSettingWindow(); }) })();