您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Delete your comments in group posts
// ==UserScript== // @name DoubanCommentDelete // @namespace http://tampermonkey.net/ // @version 1.0 // @description Delete your comments in group posts // @author HouBo // @match *://www.douban.com/group/topic/* // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @license MIT // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // ==/UserScript== ;(async () => { 'use strict'; // —— 工具函数 —— function getCk() { const match = document.cookie.match(/(?:^|; )ck=([^;]+)/); return match ? match[1] : ''; } function logToUI(text, type = 'info') { const colorMap = { info: '#1890ff', success: '#52c41a', warn: '#faad14', error: '#f5222d' }; const log = document.createElement('div'); log.textContent = text; Object.assign(log.style, { fontSize: '14px', padding: '4px 0', color: colorMap[type] || '#000' }); logContainer.appendChild(log); logContainer.scrollTop = logContainer.scrollHeight; } function createButton(text, style, handler) { const btn = document.createElement('a'); btn.href = 'javascript:void(0)'; btn.textContent = text; Object.assign(btn.style, { padding: '6px 10px', margin: '0 4px', borderRadius: '4px', fontSize: '14px', textDecoration: 'none', cursor: 'pointer', ...style }); btn.addEventListener('click', handler); return btn; } // —— 获取或输入用户 ID —— async function fetchOrPromptUserId() { let stored = await GM_getValue('userId', null); if (!stored) { const input = prompt('请输入你的用户 ID(可在个人主页 URL 中找到):'); if (input && input.trim()) { await GM_setValue('userId', input.trim()); return input.trim(); } return null; } return stored; } const userId = await fetchOrPromptUserId(); if (!userId) { alert('未输入用户 ID,脚本已停止。'); return; } GM_registerMenuCommand('修改用户 ID', async () => { const newId = prompt('重新输入你的用户 ID:', userId || ''); if (newId && newId.trim()) { await GM_setValue('userId', newId.trim()); location.reload(); } }); // 显示当前 ID const idBox = document.createElement('div'); idBox.textContent = `当前用户 ID: ${userId}`; Object.assign(idBox.style, { position: 'fixed', bottom: '10px', left: '10px', padding: '8px 12px', background: 'rgba(0,0,0,0.7)', color: '#fff', borderRadius: '6px', fontSize: '14px', zIndex: 9999, fontFamily: 'monospace' }); document.body.appendChild(idBox); // —— 初始化变量 —— const tid = location.href.match(/topic\/(\d+)\//)?.[1]; if (!tid) { console.error('无法提取话题 ID'); return; } const ck = getCk(); if (!ck) { alert('未获取到 ck(登录凭证),请重新登录豆瓣后重试。'); return; } const topicAdminOpts = $('.topic-admin-opts')[0]; if (!topicAdminOpts) { console.error('找不到操作区域'); return; } // —— 创建控制面板 —— const controlPanel = document.createElement('div'); controlPanel.innerHTML = ` <div style="margin: 12px 0; padding: 12px; border: 1px solid #e8e8e8; border-radius: 8px; background: #fafafa; font-family: Arial, sans-serif;"> <h3 style="margin: 0 0 10px; color: #333;">自动删除我的评论</h3> <div style="display: flex; align-items: center; gap: 10px; flex-wrap: wrap;"> <label style="font-weight:bold; color:#333;">起始页:</label> <input type="number" id="page-start-input" value="1" min="1" style="width: 60px; padding: 4px; border: 1px solid #ccc; border-radius: 4px;"> <div id="progress-bar" style="flex: 1; height: 6px; background: #eee; border-radius: 3px; overflow: hidden; display: none;"> <div id="progress-fill" style="height: 100%; width: 0%; background: #1890ff; transition: width 0.3s;"></div> </div> </div> <div id="button-container" style="margin-top: 10px;"></div> <div id="log-container" style="max-height: 200px; overflow-y: auto; margin-top: 10px; font-size: 14px; color: #555; line-height: 1.5;"></div> </div> `; topicAdminOpts.appendChild(controlPanel); const progressBar = document.getElementById('progress-bar'); const progressFill = document.getElementById('progress-fill'); const buttonContainer = document.getElementById('button-container'); const logContainer = document.getElementById('log-container'); let isRunning = false; let isPaused = false; let totalDeleted = 0; let currentPage = 1; let totalPages = null; // —— 控制按钮 —— const startBtn = createButton('▶️ 开始删除', { background: '#1890ff', color: 'white' }, startDeletion); const pauseBtn = createButton('⏸️ 暂停', { background: '#faad14', color: 'white', display: 'none' }, () => { isPaused = true; pauseBtn.style.display = 'none'; resumeBtn.style.display = 'inline-block'; logToUI('⏸️ 已暂停,点击“继续”恢复。', 'warn'); }); const resumeBtn = createButton('▶️ 继续', { background: '#52c41a', color: 'white', display: 'none' }, () => { isPaused = false; resumeBtn.style.display = 'none'; pauseBtn.style.display = 'inline-block'; }); const stopBtn = createButton('⏹️ 停止', { background: '#f5222d', color: 'white' }, () => { isRunning = false; isPaused = false; logToUI(`⏹️ 已停止。共删除 ${totalDeleted} 条评论。`, 'warn'); updateButtons(false); }); buttonContainer.appendChild(startBtn); buttonContainer.appendChild(pauseBtn); buttonContainer.appendChild(resumeBtn); buttonContainer.appendChild(stopBtn); function updateButtons(running) { startBtn.style.display = running ? 'none' : 'inline-block'; pauseBtn.style.display = running && !isPaused ? 'inline-block' : 'none'; resumeBtn.style.display = running && isPaused ? 'inline-block' : 'none'; stopBtn.style.display = running ? 'inline-block' : 'none'; } // —— 延迟函数 —— function randomDelay(min = 800, max = 1500) { return new Promise(resolve => setTimeout(resolve, Math.random() * (max - min) + min)); } // —— 删除单条评论(带重试)—— async function delComment(commentElement) { const cid = $(commentElement).data('cid'); const content = $(commentElement).find('.markdown').text().trim().substring(0, 25); for (let i = 0; i < 3; i++) { try { await $.post(`/j/group/topic/${tid}/remove_comment`, { ck, cid }); totalDeleted++; logToUI(`成功删除:"${content}"`, 'success'); return true; } catch (err) { if (i === 2) { logToUI(`删除失败(重试3次):"${content}"`, 'error'); return false; } await randomDelay(1000, 2000); } } } // —— 删除当前页所有自己的评论 —— async function delPageComment() { const comments = $('.topic-reply li'); const myComments = Array.from(comments).filter(el => $(el).data('author-id') == userId); if (myComments.length === 0) { logToUI(`🔍 第 ${currentPage} 页:未找到属于你的评论。`); return; } logToUI(`🔍 第 ${currentPage} 页:发现 ${myComments.length} 条属于你的评论,开始删除...`); for (const comment of myComments) { if (!isRunning || isPaused) break; await delComment(comment); await randomDelay(); } } // —— 跳转下一页 —— async function gotoNextPage() { const nextLink = $('a:contains("后页")').attr('href'); if (!nextLink) return false; try { const data = await $.ajax({ url: nextLink, method: 'GET' }); const $newDom = $('<div>').html(data); $('#comments').html($newDom.find('#comments').html()); $('#popular-comments, h3:contains("最赞回应")').remove(); $('.paginator').html($newDom.find('.paginator').html()); currentPage++; updateProgress(); return true; } catch (e) { logToUI('❌ 加载下一页失败', 'error'); return false; } } // —— 更新进度条 —— function updateProgress() { const percent = totalPages ? Math.round((currentPage / totalPages) * 100) : 0; progressFill.style.width = `${Math.min(percent, 100)}%`; } // —— 主删除流程 —— async function startDeletion() { const startPageInput = document.getElementById('page-start-input').value; const startPage = Math.max(1, parseInt(startPageInput, 10) || 1); isRunning = true; isPaused = false; totalDeleted = 0; currentPage = startPage; updateButtons(true); logContainer.innerHTML = ''; progressBar.style.display = 'block'; progressFill.style.width = '0%'; logToUI(`🚀 开始从第 ${startPage} 页删除评论...`, 'info'); try { await goToPage(startPage); updateProgress(); while (isRunning) { if (isPaused) { await new Promise(resolve => { const check = () => isPaused && isRunning ? setTimeout(check, 500) : resolve(); check(); }); } await delPageComment(); const hasNext = await gotoNextPage(); if (!hasNext) { logToUI('已到最后一页,删除完成!', 'success'); break; } } } catch (e) { logToUI(`❌ 发生错误: ${e.message}`, 'error'); } finally { if (isRunning) { logToUI(`✅ 全部完成!共删除 ${totalDeleted} 条评论。`, 'success'); } isRunning = false; updateButtons(false); } } // —— 跳转到指定页 —— function goToPage(pageNum) { if (pageNum === 1) { $('#popular-comments, h3:contains("最赞回应")').remove(); return Promise.resolve(); } const url = `/group/topic/${tid}/?start=${(pageNum - 1) * 100}`; return $.ajax({ url, method: 'GET', success: data => { const $newDom = $('<div>').html(data); $('.topic-reply').html($newDom.find('.topic-reply').html()); $('.paginator').html($newDom.find('.paginator').html()); currentPage = pageNum; updateProgress(); }, error: () => logToUI(`❌ 无法加载第 ${pageNum} 页`, 'error') }); } // —— 移除广告 —— $('#gdt-ad-container, #dale_group_topic_inner_middle').remove(); logToUI('✅ 脚本已就绪,请设置起始页并点击“开始删除”。'); })();