您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
智能处理B站抽奖动态:未开奖保留,已开奖判断UP主其他抽奖后决定是否取关
// ==UserScript== // @name B站抽奖动态智能处理 // @namespace https://github.com/bilibili-lottery-handler // @version 1.0.2 // @description 智能处理B站抽奖动态:未开奖保留,已开奖判断UP主其他抽奖后决定是否取关 // @author senjoke // @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== (function() { 'use strict'; /** * 初始化配置 */ function initConfig() { if (GM_getValue('smart-unfollow') == undefined) { GM_setValue('smart-unfollow', true); } if (GM_getValue('delete-finished-lottery') == undefined) { GM_setValue('delete-finished-lottery', true); } if (GM_getValue('unfollow-list') == undefined) { GM_setValue('unfollow-list', []); } // 新增配置:删除重试次数 if (GM_getValue('delete-retry-count') == undefined) { GM_setValue('delete-retry-count', 3); } // 新增配置:操作延迟时间(毫秒) if (GM_getValue('operation-delay') == undefined) { GM_setValue('operation-delay', 1500); } } /** * 全局状态管理 */ let isProcessing = false; let currentProcessId = null; /** * 弹窗样式 */ const styles = ` .lottery-handler-popup { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 10000; display: flex; justify-content: center; align-items: center; } .lottery-handler-content { background-color: #fff; border-radius: 10px; width: 500px; max-height: 80vh; padding: 20px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); position: relative; } .lottery-handler-content.large { width: 700px; max-height: 90vh; } .lottery-handler-header { font-size: 20px; font-weight: bold; margin-bottom: 20px; text-align: center; color: #333; display: flex; justify-content: space-between; align-items: center; } .lottery-handler-body { max-height: 60vh; overflow-y: auto; margin-bottom: 20px; } .config-item { display: flex; justify-content: space-between; align-items: center; padding: 10px 0; border-bottom: 1px solid #eee; } .config-item:last-child { border-bottom: none; } .config-label { font-size: 14px; color: #333; } .config-input { margin-left: 10px; } .lottery-handler-footer { display: flex; justify-content: flex-end; gap: 10px; } .btn { padding: 8px 16px; border: none; border-radius: 5px; cursor: pointer; font-size: 14px; transition: background-color 0.3s; } .btn:disabled { opacity: 0.6; cursor: not-allowed; } .btn-primary { background-color: #00a1d6; color: white; } .btn-primary:hover:not(:disabled) { background-color: #0082b3; } .btn-secondary { background-color: #ccc; color: #333; } .btn-secondary:hover:not(:disabled) { background-color: #bbb; } .btn-success { background-color: #52c41a; color: white; } .btn-success:hover:not(:disabled) { background-color: #389e0d; } .progress-bar { width: 100%; height: 20px; background-color: #f0f0f0; border-radius: 10px; overflow: hidden; margin: 10px 0; } .progress-fill { height: 100%; background-color: #00a1d6; transition: width 0.3s ease; } .status-text { font-size: 12px; color: #666; text-align: center; margin-top: 5px; } .minimize-btn { background: #f0f0f0; border: none; border-radius: 3px; width: 30px; height: 30px; cursor: pointer; font-size: 16px; display: flex; align-items: center; justify-content: center; } .minimize-btn:hover { background: #e0e0e0; } .mini-progress { position: fixed; top: 20px; right: 20px; width: 300px; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.2); padding: 15px; z-index: 10001; display: none; } .mini-progress.show { display: block; } .mini-header { font-size: 14px; font-weight: bold; margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center; } .expand-btn { background: #00a1d6; color: white; border: none; border-radius: 3px; width: 24px; height: 24px; cursor: pointer; font-size: 12px; } .result-section { margin: 15px 0; padding: 15px; background: #f8f9fa; border-radius: 8px; border-left: 4px solid #00a1d6; } .result-title { font-weight: bold; margin-bottom: 10px; color: #333; } .result-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin-bottom: 15px; } .stat-item { text-align: center; padding: 10px; background: white; border-radius: 6px; border: 1px solid #e0e0e0; } .stat-number { font-size: 18px; font-weight: bold; color: #00a1d6; } .stat-label { font-size: 12px; color: #666; margin-top: 5px; } .user-list { max-height: 200px; overflow-y: auto; background: white; border-radius: 6px; padding: 10px; } .user-item { display: flex; align-items: center; padding: 8px 0; border-bottom: 1px solid #f0f0f0; } .user-item:last-child { border-bottom: none; } .user-name { font-weight: 500; color: #333; } .user-uid { font-size: 12px; color: #999; margin-left: 8px; } .no-data { text-align: center; color: #999; font-style: italic; padding: 20px; } `; GM_addStyle(styles); /** * 获取Cookie值 * @param {string} key Cookie键名 * @returns {string} Cookie值 */ function getCookie(key) { const cookieArr = document.cookie.split(';'); for (let i = 0; i < cookieArr.length; i++) { const cookie = cookieArr[i].trim(); if (cookie.indexOf(key + '=') === 0) { return cookie.substring(key.length + 1); } } return null; } /** * 发送通知 * @param {string} message 通知内容 * @param {string} type 通知类型 */ function sendNotification(message, type = 'info') { console.log(`[B站抽奖动态智能处理] ${message}`); GM_notification({ text: message, title: 'B站抽奖动态智能处理', image: 'https://www.bilibili.com/favicon.ico', timeout: 3000, }); } /** * 获取用户动态列表 * @param {string} uid 用户ID * @param {string} offset 偏移量 * @returns {Promise} 动态数据 */ async function getUserDynamics(uid, offset = '') { const apiUrl = `https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/space?offset=${offset}&host_mid=${uid}`; try { const response = await axios.get(apiUrl, { withCredentials: true }); return response.data; } catch (error) { console.error('获取动态失败:', error); throw error; } } /** * 获取抽奖状态 * @param {string} lotteryId 抽奖ID * @returns {Promise} 抽奖状态信息 */ async function getLotteryStatus(lotteryId) { const apiUrl = `https://api.vc.bilibili.com/lottery_svr/v1/lottery_svr/lottery_notice?business_type=4&business_id=${lotteryId}`; try { const response = await axios.get(apiUrl); return response.data; } catch (error) { console.error('获取抽奖状态失败:', error); throw error; } } /** * 验证动态是否被成功删除 * @param {string} dynamicId 动态ID * @param {string} uid 用户ID * @returns {Promise<boolean>} 是否删除成功 */ async function verifyDynamicDeleted(dynamicId, uid) { try { // 等待一段时间让服务器处理 await new Promise(resolve => setTimeout(resolve, 2000)); // 重新获取用户动态,检查目标动态是否还存在 const dynamicsData = await getUserDynamics(uid); if (dynamicsData.code !== 0) { console.warn('验证删除时获取动态失败:', dynamicsData.message); return false; // 无法验证,假定删除失败 } const items = dynamicsData.data.items || []; const stillExists = items.some(item => item.id_str === dynamicId); return !stillExists; // 如果不存在则删除成功 } catch (error) { console.error('验证删除失败:', error); return false; // 验证失败,假定删除失败 } } /** * 删除动态(带重试和验证机制) * @param {string} dynamicId 动态ID * @param {string} uid 用户ID * @param {number} maxRetries 最大重试次数 * @returns {Promise<Object>} 删除结果 */ async function deleteDynamicWithRetry(dynamicId, uid, maxRetries = 3) { const csrf = getCookie('bili_jct'); const apiUrl = `https://api.bilibili.com/x/dynamic/feed/operate/remove?csrf=${csrf}`; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { console.log(`尝试删除动态 ${dynamicId},第 ${attempt} 次`); const response = await axios.post(apiUrl, { dyn_id_str: dynamicId }, { withCredentials: true }); if (response.data.code === 0) { // API返回成功,进行验证 console.log(`删除API调用成功,开始验证动态 ${dynamicId} 是否真的被删除`); const isDeleted = await verifyDynamicDeleted(dynamicId, uid); if (isDeleted) { console.log(`动态 ${dynamicId} 删除成功并验证通过`); return { code: 0, message: '删除成功', verified: true, attempts: attempt }; } else { console.warn(`动态 ${dynamicId} API返回成功但验证失败,可能触发了验证码或其他限制`); if (attempt < maxRetries) { // 增加更长的延迟避免触发验证码 const delayTime = GM_getValue('operation-delay') * attempt; console.log(`等待 ${delayTime}ms 后重试...`); await new Promise(resolve => setTimeout(resolve, delayTime)); continue; } else { return { code: -1, message: '删除验证失败,可能触发了验证码或其他安全限制', verified: false, attempts: attempt }; } } } else { console.warn(`删除动态 ${dynamicId} 失败:`, response.data.message); if (attempt < maxRetries) { const delayTime = GM_getValue('operation-delay') * attempt; console.log(`等待 ${delayTime}ms 后重试...`); await new Promise(resolve => setTimeout(resolve, delayTime)); continue; } else { return { code: response.data.code, message: response.data.message || '删除失败', verified: false, attempts: attempt }; } } } catch (error) { console.error(`删除动态 ${dynamicId} 第 ${attempt} 次尝试失败:`, error); if (attempt < maxRetries) { const delayTime = GM_getValue('operation-delay') * attempt; console.log(`等待 ${delayTime}ms 后重试...`); await new Promise(resolve => setTimeout(resolve, delayTime)); continue; } else { return { code: -1, message: `删除失败: ${error.message}`, verified: false, attempts: attempt }; } } } return { code: -1, message: '删除失败,已达到最大重试次数', verified: false, attempts: maxRetries }; } /** * 删除动态(保留原函数以兼容) * @param {string} dynamicId 动态ID * @returns {Promise} 删除结果 */ async function deleteDynamic(dynamicId) { const csrf = getCookie('bili_jct'); const apiUrl = `https://api.bilibili.com/x/dynamic/feed/operate/remove?csrf=${csrf}`; try { const response = await axios.post(apiUrl, { dyn_id_str: dynamicId }, { withCredentials: true }); return response.data; } catch (error) { console.error('删除动态失败:', error); throw error; } } /** * 取关用户 * @param {string} uid 用户ID * @returns {Promise} 取关结果 */ async function unfollowUser(uid) { const csrf = getCookie('bili_jct'); const apiUrl = 'https://api.bilibili.com/x/relation/modify'; try { const response = await axios.post(apiUrl, { fid: uid, act: 2, // 2表示取关 re_src: 11, spmid: '333.999.0.0', csrf: csrf }, { withCredentials: true }); return response.data; } catch (error) { console.error('取关失败:', error); throw error; } } /** * 检查用户是否有其他未开奖的抽奖动态 * @param {string} uid 用户ID * @param {string} excludeId 排除的动态ID * @returns {Promise<Object>} 返回检查结果 {hasOtherLottery: boolean, otherLotteryCount: number} */ async function hasOtherUnfinishedLottery(uid, excludeId) { try { let offset = ''; let hasMore = true; let otherLotteryCount = 0; while (hasMore) { const dynamicsData = await getUserDynamics(uid, offset); if (dynamicsData.code !== 0) { console.error('获取用户动态失败:', dynamicsData.message); return {hasOtherLottery: false, otherLotteryCount: 0}; } const items = dynamicsData.data.items || []; for (const item of items) { // 跳过要排除的动态 if (item.id_str === excludeId) continue; // 检查是否为转发动态且包含抽奖 if (item.orig && item.orig.id_str) { try { const lotteryData = await getLotteryStatus(item.orig.id_str); // 如果是抽奖且未开奖 if (lotteryData.code === 0 && lotteryData.data.status === 0) { otherLotteryCount++; } } catch (error) { // 不是抽奖动态,继续检查下一个 continue; } } } // 检查是否还有更多动态 offset = dynamicsData.data.offset; hasMore = dynamicsData.data.has_more && offset; // 为了避免请求过于频繁,添加延迟 await new Promise(resolve => setTimeout(resolve, 500)); } return {hasOtherLottery: otherLotteryCount > 0, otherLotteryCount}; } catch (error) { console.error('检查其他抽奖动态失败:', error); return {hasOtherLottery: false, otherLotteryCount: 0}; } } /** * 处理单个抽奖动态 * @param {Object} dynamic 动态对象 * @param {Function} progressCallback 进度回调 * @param {string} currentUid 当前用户ID(用于删除验证) * @returns {Promise<Object>} 处理结果 */ async function processLotteryDynamic(dynamic, progressCallback, currentUid) { const result = { dynamicId: dynamic.id_str, deleted: false, unfollowed: false, reason: '', authorName: '', authorUid: '', lotteryStatus: null, isLottery: false, otherLotteryCount: 0, deleteAttempts: 0, deleteVerified: false }; try { // 检查是否为转发的抽奖动态 if (!dynamic.orig || !dynamic.orig.id_str) { result.reason = '不是转发动态'; return result; } const origId = dynamic.orig.id_str; const authorInfo = dynamic.orig.modules?.module_author; if (authorInfo) { result.authorName = authorInfo.name; result.authorUid = authorInfo.mid; } progressCallback(`正在检查抽奖状态: ${result.authorName || '未知用户'}`); // 获取抽奖状态 const lotteryData = await getLotteryStatus(origId); if (lotteryData.code !== 0) { result.reason = '不是抽奖动态'; return result; } result.isLottery = true; result.lotteryStatus = lotteryData.data.status; // 0: 未开奖, 2: 已开奖 if (result.lotteryStatus === 0) { result.reason = '抽奖未开奖,保留'; return result; } if (result.lotteryStatus === 2) { progressCallback(`抽奖已开奖,准备删除动态并验证...`); // 删除已开奖的动态(使用带重试和验证的版本) if (GM_getValue('delete-finished-lottery')) { const maxRetries = GM_getValue('delete-retry-count'); const deleteResult = await deleteDynamicWithRetry(dynamic.id_str, currentUid, maxRetries); result.deleteAttempts = deleteResult.attempts; result.deleteVerified = deleteResult.verified; if (deleteResult.code === 0 && deleteResult.verified) { result.deleted = true; result.reason = `已开奖,动态已删除并验证成功 (尝试${deleteResult.attempts}次)`; } else { result.reason = `删除失败: ${deleteResult.message} (尝试${deleteResult.attempts}次)`; // 如果删除失败,不继续处理取关逻辑 return result; } } // 检查是否需要取关(只检查已关注且参与过抽奖的UP主) if (GM_getValue('smart-unfollow') && authorInfo && authorInfo.following) { progressCallback(`检查UP主是否有其他抽奖...`); const otherLotteryCheck = await hasOtherUnfinishedLottery(result.authorUid, origId); result.otherLotteryCount = otherLotteryCheck.otherLotteryCount; if (!otherLotteryCheck.hasOtherLottery) { // 没有其他未开奖抽奖,可以取关 progressCallback(`准备取关UP主: ${result.authorName}`); const unfollowResult = await unfollowUser(result.authorUid); if (unfollowResult.code === 0) { result.unfollowed = true; result.reason += ',已取关UP主'; } else { result.reason += ',取关失败'; } } else { result.reason += `,UP主还有${result.otherLotteryCount}个抽奖,保持关注`; } } } } catch (error) { console.error('处理动态失败:', error); result.reason = `处理失败: ${error.message}`; } return result; } /** * 主处理函数 */ async function processAllLotteryDynamics() { // 检查是否正在处理 if (isProcessing) { sendNotification('已有处理任务在进行中,请等待完成后再试'); return; } const uid = getCookie('DedeUserID'); if (!uid) { sendNotification('未检测到登录状态'); return; } // 设置处理状态 isProcessing = true; currentProcessId = Date.now().toString(); // 显示进度弹窗 showProgressDialog(); try { updateProgress(0, '正在获取动态列表...'); let allDynamics = []; let offset = ''; let hasMore = true; // 获取所有动态 while (hasMore) { const dynamicsData = await getUserDynamics(uid, offset); if (dynamicsData.code !== 0) { throw new Error(`获取动态失败: ${dynamicsData.message}`); } const items = dynamicsData.data.items || []; allDynamics = allDynamics.concat(items); offset = dynamicsData.data.offset; hasMore = dynamicsData.data.has_more && offset; updateProgress(10, `已获取 ${allDynamics.length} 条动态...`); // 添加延迟避免请求过频 await new Promise(resolve => setTimeout(resolve, 500)); } // 筛选出转发的抽奖动态 const lotteryDynamics = []; const totalDynamics = allDynamics.length; updateProgress(20, '正在识别抽奖动态...'); for (let i = 0; i < allDynamics.length; i++) { const dynamic = allDynamics[i]; if (dynamic.orig && dynamic.orig.id_str) { try { const lotteryData = await getLotteryStatus(dynamic.orig.id_str); if (lotteryData.code === 0) { lotteryDynamics.push(dynamic); } } catch (error) { // 不是抽奖动态,跳过 } } // 更新识别进度 if (i % 10 === 0) { const identifyProgress = 20 + (i / allDynamics.length) * 10; updateProgress(identifyProgress, `正在识别抽奖动态... ${i}/${allDynamics.length}`); } } updateProgress(30, `找到 ${lotteryDynamics.length} 条抽奖动态`); if (lotteryDynamics.length === 0) { const finalResult = { totalDynamics, lotteryDynamics: 0, deletedCount: 0, keptCount: 0, deleteFailedCount: 0, unfollowedUsers: [], keptFollowUsers: [], results: [] }; updateProgress(100, '没有找到抽奖动态'); showDetailedResults(finalResult); return; } // 处理每个抽奖动态 const results = []; for (let i = 0; i < lotteryDynamics.length; i++) { const dynamic = lotteryDynamics[i]; const progress = 30 + (i / lotteryDynamics.length) * 60; const result = await processLotteryDynamic(dynamic, (status) => { updateProgress(progress, status); }, uid); results.push(result); // 增加更长的延迟避免触发验证码 const delayTime = GM_getValue('operation-delay'); await new Promise(resolve => setTimeout(resolve, delayTime)); } // 统计结果 const finalResult = analyzeResults(results, totalDynamics, lotteryDynamics.length); // 显示详细处理结果 updateProgress(100, '处理完成'); showDetailedResults(finalResult); } catch (error) { console.error('处理失败:', error); updateProgress(100, `处理失败: ${error.message}`); setTimeout(() => { hideProgressDialog(); isProcessing = false; }, 3000); } } /** * 分析处理结果 * @param {Array} results 处理结果数组 * @param {number} totalDynamics 总动态数 * @param {number} lotteryDynamics 抽奖动态数 * @returns {Object} 统计结果 */ function analyzeResults(results, totalDynamics, lotteryDynamics) { const deletedCount = results.filter(r => r.deleted).length; const keptCount = results.filter(r => r.isLottery && r.lotteryStatus === 0).length; const deleteFailedCount = results.filter(r => r.lotteryStatus === 2 && !r.deleted).length; // 统计取关的用户 const unfollowedUsers = results .filter(r => r.unfollowed) .map(r => ({ name: r.authorName, uid: r.authorUid })); // 统计删除了动态但保持关注的用户 const keptFollowUsers = results .filter(r => r.deleted && !r.unfollowed && r.authorUid && r.otherLotteryCount > 0) .map(r => ({ name: r.authorName, uid: r.authorUid, otherLotteryCount: r.otherLotteryCount })); // 去重 const uniqueUnfollowed = unfollowedUsers.filter((user, index, self) => index === self.findIndex(u => u.uid === user.uid) ); const uniqueKeptFollow = keptFollowUsers.filter((user, index, self) => index === self.findIndex(u => u.uid === user.uid) ); return { totalDynamics, lotteryDynamics, deletedCount, keptCount, deleteFailedCount, unfollowedUsers: uniqueUnfollowed, keptFollowUsers: uniqueKeptFollow, results }; } /** * 显示进度对话框 */ function showProgressDialog() { // 先隐藏小窗口 hideMiniProgress(); const dialog = document.createElement('div'); dialog.className = 'lottery-handler-popup'; dialog.id = 'lottery-progress-dialog'; dialog.innerHTML = ` <div class="lottery-handler-content"> <div class="lottery-handler-header"> <span>处理抽奖动态</span> <button class="minimize-btn" id="minimize-btn" title="最小化">−</button> </div> <div class="lottery-handler-body"> <div class="progress-bar"> <div class="progress-fill" id="progress-fill" style="width: 0%"></div> </div> <div class="status-text" id="status-text">准备开始...</div> </div> <div class="lottery-handler-footer"> <button class="btn btn-secondary" id="cancel-btn">取消</button> </div> </div> `; // 绑定事件监听器 const minimizeBtn = dialog.querySelector('#minimize-btn'); const cancelBtn = dialog.querySelector('#cancel-btn'); if (minimizeBtn) { minimizeBtn.addEventListener('click', minimizeProgressDialog); } if (cancelBtn) { cancelBtn.addEventListener('click', cancelProcess); } document.body.appendChild(dialog); } /** * 最小化进度对话框 */ function minimizeProgressDialog() { hideProgressDialog(); showMiniProgress(); } /** * 显示小窗口进度 */ function showMiniProgress() { let miniProgress = document.getElementById('mini-progress'); if (!miniProgress) { miniProgress = document.createElement('div'); miniProgress.className = 'mini-progress'; miniProgress.id = 'mini-progress'; miniProgress.innerHTML = ` <div class="mini-header"> <span>处理中...</span> <button class="expand-btn" id="expand-btn" title="展开">+</button> </div> <div class="progress-bar"> <div class="progress-fill" id="mini-progress-fill" style="width: 0%"></div> </div> <div class="status-text" id="mini-status-text">准备开始...</div> `; // 绑定展开按钮事件 const expandBtn = miniProgress.querySelector('#expand-btn'); if (expandBtn) { expandBtn.addEventListener('click', expandProgressDialog); } document.body.appendChild(miniProgress); } miniProgress.classList.add('show'); } /** * 隐藏小窗口进度 */ function hideMiniProgress() { const miniProgress = document.getElementById('mini-progress'); if (miniProgress) { miniProgress.classList.remove('show'); } } /** * 展开进度对话框 */ function expandProgressDialog() { hideMiniProgress(); showProgressDialog(); } /** * 取消处理 */ function cancelProcess() { if (isProcessing) { const confirmed = confirm('确定要取消当前处理任务吗?'); if (confirmed) { isProcessing = false; hideProgressDialog(); hideMiniProgress(); sendNotification('处理任务已取消'); } } else { hideProgressDialog(); } } /** * 更新进度 * @param {number} percent 进度百分比 * @param {string} status 状态文本 */ function updateProgress(percent, status) { // 更新主窗口进度 const progressFill = document.getElementById('progress-fill'); const statusText = document.getElementById('status-text'); if (progressFill) { progressFill.style.width = `${percent}%`; } if (statusText) { statusText.textContent = status; } // 同时更新小窗口进度 const miniProgressFill = document.getElementById('mini-progress-fill'); const miniStatusText = document.getElementById('mini-status-text'); if (miniProgressFill) { miniProgressFill.style.width = `${percent}%`; } if (miniStatusText) { miniStatusText.textContent = status; } } /** * 隐藏进度对话框 */ function hideProgressDialog() { const dialog = document.getElementById('lottery-progress-dialog'); if (dialog) { document.body.removeChild(dialog); } } /** * 显示详细处理结果 * @param {Object} finalResult 最终结果统计 */ function showDetailedResults(finalResult) { // 重置处理状态 isProcessing = false; // 隐藏进度窗口和小窗口 hideProgressDialog(); hideMiniProgress(); const { totalDynamics, lotteryDynamics, deletedCount, keptCount, deleteFailedCount, unfollowedUsers, keptFollowUsers, results } = finalResult; const dialog = document.createElement('div'); dialog.className = 'lottery-handler-popup'; dialog.id = 'lottery-result-dialog'; // 统计删除失败的动态 const failedDynamics = results.filter(r => r.lotteryStatus === 2 && !r.deleted); dialog.innerHTML = ` <div class="lottery-handler-content large"> <div class="lottery-handler-header"> <span>处理完成 - 详细结果</span> </div> <div class="lottery-handler-body"> <div class="result-section"> <div class="result-title">📊 统计概览</div> <div class="result-stats"> <div class="stat-item"> <div class="stat-number">${totalDynamics}</div> <div class="stat-label">总动态数</div> </div> <div class="stat-item"> <div class="stat-number">${lotteryDynamics}</div> <div class="stat-label">抽奖动态</div> </div> <div class="stat-item"> <div class="stat-number">${deletedCount}</div> <div class="stat-label">成功删除</div> </div> <div class="stat-item"> <div class="stat-number">${keptCount}</div> <div class="stat-label">保留未开奖</div> </div> ${deleteFailedCount > 0 ? ` <div class="stat-item" style="border-color: #ff4d4f;"> <div class="stat-number" style="color: #ff4d4f;">${deleteFailedCount}</div> <div class="stat-label">删除失败</div> </div> ` : ''} </div> </div> ${deleteFailedCount > 0 ? ` <div class="result-section"> <div class="result-title">⚠️ 删除失败的动态 (${deleteFailedCount})</div> <div style="font-size: 12px; color: #666; margin-bottom: 10px;"> 以下动态删除失败,可能触发了验证码或其他安全限制 </div> <div class="user-list"> ${failedDynamics.map(result => ` <div class="user-item"> <span class="user-name">${result.authorName || '未知用户'}</span> <span class="user-uid">动态ID: ${result.dynamicId}</span> <span style="color: #ff4d4f; font-size: 12px; margin-left: auto;"> ${result.reason} </span> </div> `).join('')} </div> </div> ` : ''} <div class="result-section"> <div class="result-title">🚫 已取关UP主 (${unfollowedUsers.length})</div> <div class="user-list"> ${unfollowedUsers.length > 0 ? unfollowedUsers.map(user => ` <div class="user-item"> <span class="user-name">${user.name}</span> <span class="user-uid">UID: ${user.uid}</span> </div> `).join('') : '<div class="no-data">没有取关任何UP主</div>' } </div> </div> <div class="result-section"> <div class="result-title">💝 保持关注UP主 (${keptFollowUsers.length})</div> <div style="font-size: 12px; color: #666; margin-bottom: 10px;"> 以下UP主的抽奖动态已删除,但因还有其他未开奖抽奖而保持关注 </div> <div class="user-list"> ${keptFollowUsers.length > 0 ? keptFollowUsers.map(user => ` <div class="user-item"> <span class="user-name">${user.name}</span> <span class="user-uid">UID: ${user.uid}</span> <span style="color: #00a1d6; font-size: 12px; margin-left: auto;"> 还有${user.otherLotteryCount}个抽奖 </span> </div> `).join('') : '<div class="no-data">没有此类UP主</div>' } </div> </div> </div> <div class="lottery-handler-footer"> <button class="btn btn-secondary" id="export-btn">导出详情</button> <button class="btn btn-success" id="finish-btn">完成</button> </div> </div> `; document.body.appendChild(dialog); // 绑定事件监听器 const exportBtn = dialog.querySelector('#export-btn'); const finishBtn = dialog.querySelector('#finish-btn'); if (exportBtn) { exportBtn.addEventListener('click', () => { exportResults(btoa(JSON.stringify(results))); }); } if (finishBtn) { finishBtn.addEventListener('click', hideResultDialog); } // 发送通知 const notificationMsg = deleteFailedCount > 0 ? `处理完成!成功删除 ${deletedCount} 条,失败 ${deleteFailedCount} 条,取关 ${unfollowedUsers.length} 个UP主` : `处理完成!删除 ${deletedCount} 条动态,取关 ${unfollowedUsers.length} 个UP主`; sendNotification(notificationMsg); // 详细结果输出到控制台 console.log('📊 处理完成,详细结果:', finalResult); } /** * 隐藏结果对话框 */ function hideResultDialog() { const dialog = document.getElementById('lottery-result-dialog'); if (dialog) { document.body.removeChild(dialog); } } /** * 导出处理结果 * @param {string} encodedResults Base64编码的结果数据 */ function exportResults(encodedResults) { try { const results = JSON.parse(atob(encodedResults)); const exportData = { timestamp: new Date().toLocaleString(), summary: { totalProcessed: results.length, deleted: results.filter(r => r.deleted).length, kept: results.filter(r => r.isLottery && r.lotteryStatus === 0).length, unfollowed: results.filter(r => r.unfollowed).length }, details: results.map(r => ({ 动态ID: r.dynamicId, UP主: r.authorName, UP主UID: r.authorUid, 抽奖状态: r.lotteryStatus === 0 ? '未开奖' : (r.lotteryStatus === 2 ? '已开奖' : '未知'), 是否删除: r.deleted ? '是' : '否', 是否取关: r.unfollowed ? '是' : '否', 其他抽奖数量: r.otherLotteryCount || 0, 处理结果: r.reason })) }; const blob = new Blob([JSON.stringify(exportData, null, 2)], {type: 'application/json'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `B站抽奖处理结果_${new Date().toISOString().slice(0, 10)}.json`; a.click(); URL.revokeObjectURL(url); sendNotification('结果已导出到文件'); } catch (error) { console.error('导出失败:', error); sendNotification('导出失败,请查看控制台'); } } /** * 显示设置对话框 */ function showSettingsDialog() { // 检查是否正在处理,如果是则提示用户 if (isProcessing) { alert('正在处理抽奖动态,请等待完成后再修改设置。'); return; } // 检查是否已有设置对话框打开 if (document.getElementById('lottery-settings-dialog')) { return; } const dialog = document.createElement('div'); dialog.className = 'lottery-handler-popup'; dialog.id = 'lottery-settings-dialog'; dialog.innerHTML = ` <div class="lottery-handler-content"> <div class="lottery-handler-header"> <span>设置</span> </div> <div class="lottery-handler-body"> <div class="config-item"> <label class="config-label">智能取关功能</label> <input type="checkbox" class="config-input" id="smart-unfollow" ${GM_getValue('smart-unfollow') ? 'checked' : ''} ${isProcessing ? 'disabled' : ''}> </div> <div class="config-item"> <label class="config-label">删除已开奖动态</label> <input type="checkbox" class="config-input" id="delete-finished-lottery" ${GM_getValue('delete-finished-lottery') ? 'checked' : ''} ${isProcessing ? 'disabled' : ''}> </div> <div class="config-item"> <label class="config-label">删除重试次数</label> <input type="number" class="config-input" id="delete-retry-count" value="${GM_getValue('delete-retry-count')}" min="1" max="5" ${isProcessing ? 'disabled' : ''}> </div> <div class="config-item"> <label class="config-label">操作延迟(毫秒)</label> <input type="number" class="config-input" id="operation-delay" value="${GM_getValue('operation-delay')}" min="1000" max="10000" step="500" ${isProcessing ? 'disabled' : ''}> </div> <div style="margin-top: 15px; padding: 10px; background-color: #f5f5f5; border-radius: 5px; font-size: 12px; color: #666;"> <p><strong>⚠️ 安全提醒:</strong></p> <p>• 智能取关:仅取关参与过抽奖且无其他未开奖抽奖的UP主</p> <p>• 删除验证机制:删除后会验证是否真的删除成功</p> <p>• 删除重试:如果删除失败会自动重试指定次数</p> <p>• 操作延迟:每次操作间隔时间,避免触发验证码</p> <p>• 建议延迟设置为1500毫秒以上</p> </div> </div> <div class="lottery-handler-footer"> <button class="btn btn-secondary" id="cancel-settings-btn" ${isProcessing ? 'disabled' : ''}>取消</button> <button class="btn btn-primary" id="save-settings-btn" ${isProcessing ? 'disabled' : ''}>保存</button> </div> </div> `; document.body.appendChild(dialog); // 绑定事件监听器 const cancelBtn = dialog.querySelector('#cancel-settings-btn'); const saveBtn = dialog.querySelector('#save-settings-btn'); if (cancelBtn) { cancelBtn.addEventListener('click', hideSettingsDialog); } if (saveBtn) { saveBtn.addEventListener('click', saveSettings); } } /** * 隐藏设置对话框 */ function hideSettingsDialog() { const dialog = document.getElementById('lottery-settings-dialog'); if (dialog) { document.body.removeChild(dialog); } } /** * 保存设置 */ function saveSettings() { const smartUnfollow = document.getElementById('smart-unfollow').checked; const deleteFinishedLottery = document.getElementById('delete-finished-lottery').checked; const deleteRetryCount = parseInt(document.getElementById('delete-retry-count').value); const operationDelay = parseInt(document.getElementById('operation-delay').value); // 验证设置值 if (deleteRetryCount < 1 || deleteRetryCount > 5) { alert('删除重试次数必须在1-5之间'); return; } if (operationDelay < 1000 || operationDelay > 10000) { alert('操作延迟必须在1000-10000毫秒之间'); return; } GM_setValue('smart-unfollow', smartUnfollow); GM_setValue('delete-finished-lottery', deleteFinishedLottery); GM_setValue('delete-retry-count', deleteRetryCount); GM_setValue('operation-delay', operationDelay); sendNotification('设置已保存'); hideSettingsDialog(); } /** * 初始化 */ function init() { initConfig(); // 将所有需要在HTML onclick中使用的函数添加到全局作用域 window.hideProgressDialog = hideProgressDialog; window.minimizeProgressDialog = minimizeProgressDialog; window.expandProgressDialog = expandProgressDialog; window.cancelProcess = cancelProcess; window.hideResultDialog = hideResultDialog; window.exportResults = exportResults; window.hideSettingsDialog = hideSettingsDialog; window.saveSettings = saveSettings; // 注册菜单命令 GM_registerMenuCommand('开始智能处理', processAllLotteryDynamics); GM_registerMenuCommand('设置', showSettingsDialog); sendNotification('B站抽奖动态智能处理脚本已加载'); } // 页面加载完成后初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();