// ==UserScript== // @name DeepSeek 对话批量管理 // @namespace http://tampermonkey.net/ // @version 1.0.0 // @description good! // @author Assistant // @match *://chat.deepseek.com/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @license MIT // ==/UserScript== (function() { 'use strict'; // ==================== API 配置(不含硬编码凭据) ==================== const API_CONFIG = { url: 'https://chat.deepseek.com/api/v0/chat_session/delete', method: 'POST', credentials: 'include', body: (sessionId) => ({ chat_session_id: sessionId }), }; const CONFIG = { storageKey: 'ds_batch_v7', tokenStorageKey: 'ds_token_v7', toolbarPosition: 'top', }; function sleep(ms) { return new Promise(r => setTimeout(r, ms)); } // ==================== Token 管理 ==================== function getToken() { // 1. 尝试从 GM 存储读取 let token = GM_getValue(CONFIG.tokenStorageKey, null); if (token) return token; // 2. 尝试从 Cookie 解析 const cookies = document.cookie.split('; '); for (const cookie of cookies) { const [key, value] = cookie.split('='); if (key === 'token' || key === 'access_token' || key === 'session' || key === 'auth_token') { GM_setValue(CONFIG.tokenStorageKey, value); return value; } } // 3. 尝试从 localStorage 解析 const storageKeys = ['token', 'access_token', 'session', 'auth_token']; for (const key of storageKeys) { const value = localStorage.getItem(key); if (value) { GM_setValue(CONFIG.tokenStorageKey, value); return value; } } // 4. 都没有,弹窗让用户手动输入 const input = prompt( '未自动获取到 Token,请手动输入您的 Authorization Token(格式:Bearer xxxxx)\n\n' + '获取方法:\n' + '1. 打开 DeepSeek 页面,按 F12 打开开发者工具\n' + '2. 切换到 Network 标签,刷新页面\n' + '3. 点击任意请求,在 Request Headers 中找到 Authorization\n' + '4. 复制整个值(包括 Bearer 前缀)粘贴到下方' ); if (input && input.trim() !== '') { const trimmed = input.trim(); GM_setValue(CONFIG.tokenStorageKey, trimmed); return trimmed; } return null; } function clearToken() { GM_deleteValue(CONFIG.tokenStorageKey); } function getHeaders() { const token = getToken(); if (!token) { alert('无法获取 Token,请手动输入。'); return null; } return { 'Content-Type': 'application/json', 'Authorization': token, }; } // ==================== 样式(精简) ==================== GM_addStyle(` .ds-toolbar { display: flex; align-items: center; gap: 12px; padding: 8px 16px; background: #f8f9fa; border-radius: 8px; margin: 8px 0; border: 1px solid #e9ecef; font-size: 13px; font-family: system-ui, sans-serif; box-shadow: 0 1px 3px rgba(0,0,0,0.05); position: sticky; top: 0; z-index: 999; backdrop-filter: blur(8px); background: rgba(248,249,250,0.95); flex-wrap: wrap; } .ds-toolbar .btn { padding: 4px 14px; border: 1px solid #d0d0d0; border-radius: 4px; background: white; cursor: pointer; font-size: 12px; transition: all 0.2s; color: #333; display: inline-flex; align-items: center; gap: 4px; } .ds-toolbar .btn:hover { background: #f0f0f0; border-color: #b0b0b0; } .ds-toolbar .btn-primary { background: #4F46E5; border-color: #4F46E5; color: white; } .ds-toolbar .btn-primary:hover { background: #4338CA; } .ds-toolbar .stats { font-size: 12px; color: #666; margin-left: auto; } .ds-toolbar .stats strong { color: #333; } .ds-float-stats { position: fixed; bottom: 20px; right: 20px; background: rgba(0,0,0,0.8); color: white; padding: 8px 16px; border-radius: 20px; font-size: 13px; z-index: 9999; box-shadow: 0 4px 12px rgba(0,0,0,0.3); pointer-events: none; backdrop-filter: blur(4px); transition: opacity 0.3s; } .ds-float-stats.hidden { opacity: 0; transform: translateY(10px); } .ds-progress { position: fixed; top: 0; left: 0; width: 0%; height: 3px; background: #4F46E5; z-index: 99999; transition: width 0.3s; box-shadow: 0 0 10px rgba(79,70,229,0.5); } .ds-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); z-index: 10000; display: flex; align-items: center; justify-content: center; animation: fadeIn 0.2s; pointer-events: auto; } .ds-modal { background: #fff; border-radius: 12px; max-width: 750px; width: 92%; max-height: 90vh; display: flex; flex-direction: column; box-shadow: 0 20px 60px rgba(0,0,0,0.3); animation: slideUp 0.3s; pointer-events: auto; overflow: hidden; } .ds-modal-header { padding: 16px 24px; border-bottom: 1px solid #e9ecef; display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; } .ds-modal-header h3 { margin: 0; font-size: 18px; font-weight: 600; color: #1a1a1a; } .ds-modal-header .ds-modal-stats { font-size: 13px; color: #666; } .ds-modal-close { background: none; border: none; font-size: 24px; cursor: pointer; color: #999; padding: 0 4px; line-height: 1; pointer-events: auto; } .ds-modal-close:hover { color: #333; } .ds-modal-body { padding: 12px 24px 16px 24px; overflow-y: auto; flex: 1; pointer-events: auto; } .ds-modal-body .ds-top-actions { margin-bottom: 12px; display: flex; gap: 16px; flex-wrap: wrap; align-items: center; } .ds-modal-body .ds-top-actions a { color: #4F46E5; cursor: pointer; text-decoration: underline; font-size: 13px; } .ds-modal-body .ds-top-actions a:hover { color: #4338CA; } .ds-modal-body .ds-item { display: flex; align-items: center; padding: 3px 0; } .ds-modal-body .ds-item:hover { background: #f5f7fa; } .ds-modal-body .ds-item label { cursor: pointer; flex: 1; font-size: 14px; color: #1a1a1a; padding: 4px 0; display: flex; align-items: center; } .ds-modal-body .ds-item input[type="checkbox"] { margin-right: 10px; flex-shrink: 0; width: 16px; height: 16px; cursor: pointer; pointer-events: auto; } .ds-modal-footer { padding: 12px 24px; border-top: 1px solid #e9ecef; display: flex; justify-content: flex-end; gap: 10px; flex-shrink: 0; flex-wrap: wrap; } .ds-modal-footer .btn { padding: 6px 18px; border-radius: 6px; border: 1px solid #ccc; background: #fff; cursor: pointer; font-size: 13px; pointer-events: auto; } .ds-modal-footer .btn-danger { background: #dc3545; border-color: #dc3545; color: white; } .ds-modal-footer .btn-danger:hover { background: #c82333; } .ds-modal-footer .btn-success { background: #28a745; border-color: #28a745; color: white; } .ds-modal-footer .btn-success:hover { background: #218838; } .ds-modal-footer .btn-secondary { background: #6c757d; border-color: #6c757d; color: white; } .ds-modal-footer .btn-secondary:hover { background: #5a6268; } .ds-modal-footer .btn-primary { background: #4F46E5; border-color: #4F46E5; color: white; } .ds-modal-footer .btn-primary:hover { background: #4338CA; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes slideUp { from { transform: translateY(30px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } @media (prefers-color-scheme: dark) { .ds-toolbar:not(.light) { background: rgba(30,30,35,0.95); border-color: #3d3d45; color: #e0e0e0; } .ds-toolbar:not(.light) .btn { background: #3d3d45; border-color: #555; color: #e0e0e0; } .ds-toolbar:not(.light) .btn:hover { background: #4d4d55; } .ds-toolbar:not(.light) .stats { color: #aaa; } .ds-toolbar:not(.light) .stats strong { color: #e0e0e0; } .ds-modal { background: #2d2d33; color: #e0e0e0; } .ds-modal-header { border-color: #3d3d45; } .ds-modal-header h3 { color: #e0e0e0; } .ds-modal-header .ds-modal-stats { color: #aaa; } .ds-modal-body .ds-item:hover { background: #3a3a42; } .ds-modal-body .ds-item label { color: #e0e0e0; } .ds-modal-footer { border-color: #3d3d45; } .ds-modal-footer .btn { background: #3d3d45; border-color: #555; color: #e0e0e0; } .ds-modal-footer .btn-danger { background: #dc3545; border-color: #dc3545; color: white; } .ds-modal-footer .btn-success { background: #28a745; border-color: #28a745; color: white; } .ds-modal-footer .btn-secondary { background: #6c757d; border-color: #6c757d; color: white; } .ds-modal-footer .btn-primary { background: #4F46E5; border-color: #4F46E5; color: white; } .ds-modal-close { color: #aaa; } .ds-modal-close:hover { color: #fff; } .ds-modal-body .ds-top-actions a { color: #818cf8; } } `); // ==================== 核心类 ==================== class DeepSeekBatch { constructor() { this.items = []; this.selectedIds = new Set(); this.toolbar = null; this.floatStats = null; this.isProcessing = false; this.observer = null; this.loadSelection(); this.init(); } init() { this.buildToolbar(); this.scanItems(); this.updateStats(); this.showFloatStats(); this.observeChanges(); this.attachScrollListener(); } scanItems() { const links = document.querySelectorAll('a[href*="/chat/"], a[href*="/c/"]'); const items = []; const seen = new Set(); links.forEach(link => { if (link.closest('[hidden]')) return; const id = this.getConversationId(link); if (!id) return; if (seen.has(id)) return; seen.add(id); const title = link.textContent.trim(); if (!title) return; items.push({ link, title, id }); }); this.items = items; const validIds = new Set(items.map(item => item.id)); for (const sid of this.selectedIds) { if (!validIds.has(sid)) { this.selectedIds.delete(sid); } } this.saveSelection(); this.updateStats(); } getConversationId(link) { const href = link.getAttribute('href'); if (!href) return null; const parts = href.split('/'); const last = parts[parts.length - 1]; if (last && last.length >= 30 && last.includes('-')) { return last; } return null; } // ==================== 删除 API(动态获取 Token) ==================== async deleteItem(item) { const convId = item.id; if (!convId) return false; const headers = getHeaders(); if (!headers) return false; try { const response = await fetch(API_CONFIG.url, { method: API_CONFIG.method, headers: headers, credentials: API_CONFIG.credentials, body: JSON.stringify(API_CONFIG.body(convId)) }); const data = await response.json(); if (response.ok && data.code === 0) { return true; } else if (response.status === 401) { // Token 无效,清除存储并提示 clearToken(); alert('Token 已过期或无效,请重新输入。'); return false; } else { return false; } } catch { return false; } } // -------- 其他方法(与之前相同,无变化)-------- buildToolbar() { const old = document.querySelector('.ds-toolbar'); if (old) old.remove(); const toolbar = document.createElement('div'); toolbar.className = 'ds-toolbar'; toolbar.id = 'ds-toolbar'; const selectBtn = document.createElement('button'); selectBtn.className = 'btn btn-primary'; selectBtn.textContent = '📋 选择对话并操作'; selectBtn.addEventListener('click', () => this.openIntegratedModal()); toolbar.appendChild(selectBtn); const stats = document.createElement('span'); stats.className = 'stats'; stats.id = 'ds-stats'; stats.textContent = '共 0 个对话,已选 0 个'; toolbar.appendChild(stats); const sidebar = document.querySelector('nav, aside, div[class*="sidebar"]'); if (sidebar) { if (CONFIG.toolbarPosition === 'bottom') sidebar.appendChild(toolbar); else sidebar.insertBefore(toolbar, sidebar.firstChild); } else { document.body.insertBefore(toolbar, document.body.firstChild); } this.toolbar = toolbar; } openIntegratedModal() { this.scanItems(); const items = this.items; if (items.length === 0) { alert('没有找到任何对话。'); return; } const overlay = document.createElement('div'); overlay.className = 'ds-modal-overlay'; const modal = document.createElement('div'); modal.className = 'ds-modal'; const header = document.createElement('div'); header.className = 'ds-modal-header'; const titleH3 = document.createElement('h3'); titleH3.textContent = '管理对话'; header.appendChild(titleH3); const statsSpan = document.createElement('span'); statsSpan.className = 'ds-modal-stats'; statsSpan.id = 'ds-modal-stats'; statsSpan.textContent = `已选 0 / ${items.length}`; header.appendChild(statsSpan); const closeBtn = document.createElement('button'); closeBtn.className = 'ds-modal-close'; closeBtn.textContent = '×'; closeBtn.addEventListener('click', () => overlay.remove()); header.appendChild(closeBtn); modal.appendChild(header); const body = document.createElement('div'); body.className = 'ds-modal-body'; const topActions = document.createElement('div'); topActions.className = 'ds-top-actions'; const selectAllLink = document.createElement('a'); selectAllLink.textContent = '全选'; selectAllLink.addEventListener('click', (e) => { e.preventDefault(); const cbs = body.querySelectorAll('.ds-item input[type="checkbox"]'); cbs.forEach(cb => { cb.checked = true; }); this.updateModalStats(body, items.length); }); topActions.appendChild(selectAllLink); const deselectAllLink = document.createElement('a'); deselectAllLink.textContent = '全不选'; deselectAllLink.style.marginLeft = '16px'; deselectAllLink.addEventListener('click', (e) => { e.preventDefault(); const cbs = body.querySelectorAll('.ds-item input[type="checkbox"]'); cbs.forEach(cb => { cb.checked = false; }); this.updateModalStats(body, items.length); }); topActions.appendChild(deselectAllLink); body.appendChild(topActions); const listContainer = document.createElement('div'); items.forEach(item => { const div = document.createElement('div'); div.className = 'ds-item'; const label = document.createElement('label'); const cb = document.createElement('input'); cb.type = 'checkbox'; cb.checked = this.selectedIds.has(item.id); cb.dataset.id = item.id; cb.addEventListener('change', () => { this.updateModalStats(body, items.length); }); label.appendChild(cb); label.appendChild(document.createTextNode(item.title)); div.appendChild(label); listContainer.appendChild(div); }); body.appendChild(listContainer); modal.appendChild(body); const footer = document.createElement('div'); footer.className = 'ds-modal-footer'; const deleteBtn = document.createElement('button'); deleteBtn.className = 'btn btn-danger'; deleteBtn.textContent = '🗑 删除选中'; deleteBtn.addEventListener('click', () => { const selected = this.getCheckedItems(body); if (selected.length === 0) { alert('请至少选择一个对话'); return; } this.modalDeleteSelected(selected, overlay); }); footer.appendChild(deleteBtn); const exportBtn = document.createElement('button'); exportBtn.className = 'btn btn-success'; exportBtn.textContent = '📥 导出选中'; exportBtn.addEventListener('click', () => { const selected = this.getCheckedItems(body); if (selected.length === 0) { alert('请至少选择一个对话'); return; } this.exportSelected(selected); }); footer.appendChild(exportBtn); const clearBtn = document.createElement('button'); clearBtn.className = 'btn btn-secondary'; clearBtn.textContent = '🗑 清空全部'; clearBtn.style.background = '#dc3545'; clearBtn.style.borderColor = '#dc3545'; clearBtn.style.color = 'white'; clearBtn.addEventListener('click', () => { if (!confirm('确定删除全部对话吗?此操作不可撤销!')) return; const cbs = body.querySelectorAll('.ds-item input[type="checkbox"]'); cbs.forEach(cb => { cb.checked = true; }); const all = this.getCheckedItems(body); this.modalDeleteSelected(all, overlay); }); footer.appendChild(clearBtn); const closeFooterBtn = document.createElement('button'); closeFooterBtn.className = 'btn btn-primary'; closeFooterBtn.textContent = '关闭'; closeFooterBtn.addEventListener('click', () => overlay.remove()); footer.appendChild(closeFooterBtn); modal.appendChild(footer); overlay.appendChild(modal); document.body.appendChild(overlay); this.updateModalStats(body, items.length); overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); }); } getCheckedItems(body) { const cbs = body.querySelectorAll('.ds-item input[type="checkbox"]:checked'); const selected = []; cbs.forEach(cb => { const id = cb.dataset.id; const item = this.items.find(it => it.id === id); if (item) selected.push(item); }); return selected; } updateModalStats(body, total) { const checked = body.querySelectorAll('.ds-item input[type="checkbox"]:checked').length; const statsSpan = document.getElementById('ds-modal-stats'); if (statsSpan) statsSpan.textContent = `已选 ${checked} / ${total}`; } async modalDeleteSelected(selectedItems, overlay) { const titles = selectedItems.map(item => item.title); let msg = `确定删除选中的 ${selectedItems.length} 个对话吗?\n\n`; const max = 5; titles.slice(0, max).forEach((t, i) => msg += `${i+1}. ${t}\n`); if (titles.length > max) msg += `... 等 ${titles.length - max} 个`; if (!confirm(msg)) return; overlay.style.pointerEvents = 'none'; this.isProcessing = true; this.showProgress(0, selectedItems.length); let deleted = 0, failed = 0; for (let i = 0; i < selectedItems.length; i++) { const item = selectedItems[i]; try { const success = await this.deleteItem(item); if (success) { deleted++; const idx = this.items.indexOf(item); if (idx > -1) this.items.splice(idx, 1); this.selectedIds.delete(item.id); const link = item.link; if (link && link.parentElement) { const parent = link.parentElement; parent.style.transition = 'all 0.3s'; parent.style.opacity = '0'; parent.style.transform = 'translateX(-20px)'; setTimeout(() => parent.remove(), 300); } } else { failed++; } } catch { failed++; } this.showProgress(i+1, selectedItems.length); await sleep(150); } this.hideProgress(); this.isProcessing = false; overlay.style.pointerEvents = 'auto'; this.saveSelection(); this.updateStats(); if (deleted > 0) { this.scanItems(); const body = overlay.querySelector('.ds-modal-body'); if (body) { const listContainer = body.querySelector('div:not(.ds-top-actions)'); if (listContainer) listContainer.remove(); const newList = document.createElement('div'); this.items.forEach(item => { const div = document.createElement('div'); div.className = 'ds-item'; const label = document.createElement('label'); const cb = document.createElement('input'); cb.type = 'checkbox'; cb.checked = this.selectedIds.has(item.id); cb.dataset.id = item.id; cb.addEventListener('change', () => { this.updateModalStats(body, this.items.length); }); label.appendChild(cb); label.appendChild(document.createTextNode(item.title)); div.appendChild(label); newList.appendChild(div); }); body.appendChild(newList); this.updateModalStats(body, this.items.length); } this.updateStats(); this.updateFloatStats(this.items.length, this.selectedIds.size); } alert(`删除完成!成功: ${deleted},失败: ${failed}`); // 强制刷新页面 window.location.reload(); } exportSelected(selectedItems) { if (!selectedItems) { selectedItems = this.items.filter(item => this.selectedIds.has(item.id)); } if (selectedItems.length === 0) { alert('没有选中的对话'); return; } const data = selectedItems.map(item => ({ title: item.title, url: window.location.href, platform: 'DeepSeek', exportedAt: new Date().toISOString(), })); const json = JSON.stringify(data, null, 2); const blob = new Blob([json], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `deepseek_${new Date().toISOString().slice(0,10)}.json`; a.click(); URL.revokeObjectURL(url); } updateStats() { const statsEl = document.getElementById('ds-stats'); if (!statsEl) return; const total = this.items.length; const selected = this.selectedIds.size; statsEl.textContent = `共 ${total} 个对话,已选 ${selected} 个`; this.updateFloatStats(total, selected); } showFloatStats() { let el = document.getElementById('ds-float-stats'); if (!el) { el = document.createElement('div'); el.id = 'ds-float-stats'; el.className = 'ds-float-stats'; document.body.appendChild(el); } this.floatStats = el; this.updateStats(); } updateFloatStats(total, selected) { if (!this.floatStats) return; this.floatStats.textContent = selected > 0 ? `📊 ${selected}/${total} 已选` : `📊 ${total} 个对话`; this.floatStats.classList.remove('hidden'); } showProgress(current, total) { let el = document.getElementById('ds-progress'); if (!el) { el = document.createElement('div'); el.id = 'ds-progress'; el.className = 'ds-progress'; document.body.appendChild(el); } el.style.width = Math.round((current / total) * 100) + '%'; } hideProgress() { const el = document.getElementById('ds-progress'); if (el) { el.style.width = '0%'; setTimeout(() => el.remove(), 500); } } saveSelection() { GM_setValue(CONFIG.storageKey, JSON.stringify({ selected: Array.from(this.selectedIds) })); } loadSelection() { try { const raw = GM_getValue(CONFIG.storageKey, '{}'); const data = JSON.parse(raw); if (data.selected) { this.selectedIds = new Set(data.selected); } } catch {} } observeChanges() { if (this.observer) this.observer.disconnect(); this.observer = new MutationObserver(() => { clearTimeout(this._scanTimer); this._scanTimer = setTimeout(() => { this.scanItems(); this.updateStats(); }, 500); }); this.observer.observe(document.body, { childList: true, subtree: true }); } attachScrollListener() { const scrollContainer = document.querySelector('nav, aside, div[class*="sidebar"]') || document; scrollContainer.addEventListener('scroll', () => { clearTimeout(this._scrollTimer); this._scrollTimer = setTimeout(() => { this.scanItems(); this.updateStats(); }, 300); }, { passive: true }); } destroy() { if (this.observer) { this.observer.disconnect(); this.observer = null; } document.querySelector('.ds-toolbar')?.remove(); document.getElementById('ds-float-stats')?.remove(); document.getElementById('ds-progress')?.remove(); document.querySelectorAll('.ds-modal-overlay').forEach(el => el.remove()); } } // ==================== 启动 ==================== let manager = null; function init() { if (manager) { manager.destroy(); manager = null; } setTimeout(() => { try { manager = new DeepSeekBatch(); } catch {} }, 300); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } let lastUrl = location.href; setInterval(() => { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(init, 1000); } }, 2000); window.__DS_BATCH = { manager, init }; })();