// ==UserScript== // @name DeepSeek 增强工具集 // @namespace http://tampermonkey.net/ // @version 2.0 // @description 为DeepSeek添加批量删除、历史对话搜索和侧边栏折叠功能 // @author Assistant // @match https://chat.deepseek.com/* // @grant GM_addStyle // ==/UserScript== (function() { 'use strict'; // ---------- 配置选择器 ---------- const SELECTORS = { // 开启新对话按钮 newChatBtn: '#root > div > div > div.c3ecdb44 > div.dc04ec1d > div > div._5a8ac7a.a084f19e', // 左侧历史对话列表容器 leftList: '#root > div > div > div.c3ecdb44 > div.dc04ec1d > div', // 单个对话项(用于点击跳转) chatItem: 'a', // 历史对话滚动区域(批量删除需要) scrollArea: '.ds-scroll-area' }; // ---------- 全局变量 ---------- let searchBtn = null; let modalPanel = null; let panelListContainer = null; let searchInput = null; let isPanelVisible = false; let leftObserver = null; // 监听左侧列表变化的观察器 let rootObserver = null; // 监听DOM整体变化的观察器 let itemElementMap = new Map(); // 批量删除相关变量 let btnBatchDelete = null; let btnCancel = null; let btnReverse = null; let btnConfirm = null; let checkboxes = []; let batchContainer = null; // 批量删除按钮容器 let actionContainer = null; // 操作按钮容器 let originalNewChatBtn = null; // 保存原始新对话按钮引用 let batchDeleteObserver = null; // 批量删除内部监听新对话的观察器 // ---------- 添加自定义样式(移除了脆弱的CSS规则)---------- GM_addStyle(` /* 浮层面板 */ .ds-history-modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 600px; height: 400px; background: var(--dsw-alias-bg-primary, #fff); border-radius: 16px; box-shadow: 0 20px 35px -12px rgba(0,0,0,0.3), 0 0 0 1px rgba(0,0,0,0.05); display: flex; flex-direction: column; z-index: 10000; overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; } @media (prefers-color-scheme: dark) { .ds-history-modal { background: #1e1e2f; color: #e4e4e7; box-shadow: 0 20px 35px -12px rgba(0,0,0,0.5); } .ds-history-modal input { background: #2a2a36; color: #fff; border-color: #3e3e4a; } .ds-history-modal .ds-chat-item:hover { background: #2a2a36; } } .ds-modal-header { display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; border-bottom: 1px solid var(--dsw-alias-border-divider, #e5e5e7); font-weight: 600; font-size: 18px; color: #3964fe; } .ds-modal-header .close-btn { cursor: pointer; font-size: 24px; line-height: 1; background: none; border: none; color: #3964fe; opacity: 0.7; } .ds-modal-header .close-btn:hover { opacity: 1; } .ds-search-input-wrapper { padding: 12px 20px; border-bottom: 1px solid var(--dsw-alias-border-divider, #e5e5e7); } .ds-search-input-wrapper input { width: 100%; padding: 10px 14px; font-size: 14px; border: 1px solid var(--dsw-alias-border-input, #ccc); border-radius: 10px; background: var(--dsw-alias-bg-secondary, #f9f9fb); outline: none; transition: 0.2s; box-sizing: border-box; } .ds-search-input-wrapper input:focus { border-color: #3b82f6; box-shadow: 0 0 0 2px rgba(59,130,246,0.2); } .ds-list-container { flex: 1; overflow-y: auto; padding: 12px 8px; } .ds-group-title { font-size: 12px; font-weight: 500; color: var(--dsw-alias-label-tertiary, #8e8e93); padding: 8px 12px; letter-spacing: 0.3px; } .ds-chat-item { display: flex; align-items: center; gap: 10px; padding: 10px 12px; margin: 2px 0; border-radius: 10px; cursor: pointer; transition: background 0.2s; color: var(--dsw-alias-label-primary, #1f1f1f); text-decoration: none; } .ds-chat-item:hover { background: var(--dsw-alias-fill-hover, #f2f2f5); } .ds-chat-item svg { flex-shrink: 0; width: 18px; height: 18px; color: var(--dsw-alias-label-tertiary, #8e8e93); } .ds-chat-item .title { flex: 1; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .ds-modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.4); z-index: 9999; backdrop-filter: blur(2px); } .ds-no-results { text-align: center; padding: 40px 20px; color: var(--dsw-alias-label-tertiary, #8e8e93); font-size: 14px; } /* 折叠按钮样式 */ .ds-collapse-btn { display: flex; align-items: center; justify-content: space-between; width: 100%; padding: 8px 12px; margin: 8px 0; background: transparent; border: none; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 500; color: var(--dsw-alias-label-secondary, #666); transition: all 0.2s ease; } .ds-collapse-btn:hover { background-color: var(--dsw-alias-fill-hover, rgba(0,0,0,0.05)); color: var(--dsw-alias-label-primary, #1f1f1f); } .ds-collapse-btn svg { width: 16px; height: 16px; fill: currentColor; transition: transform 0.2s ease; } .ds-collapse-btn.collapsed svg { transform: rotate(-90deg); } .ds-history-collapsed ._3098d02 { display: none; } .ds-list-container ._3098d02 { margin-bottom: 16px; } .ds-list-container ._546d736 { display: flex; align-items: center; gap: 10px; padding: 10px 12px; margin: 2px 0; border-radius: 10px; cursor: pointer; transition: background 0.2s; color: var(--dsw-alias-label-primary, #1f1f1f); text-decoration: none; } .ds-list-container ._546d736:hover { background: var(--dsw-alias-fill-hover, #f2f2f5); } .ds-list-container .f3d18f6a { font-size: 12px; font-weight: 500; color: var(--dsw-alias-label-tertiary, #8e8e93); padding: 8px 12px; letter-spacing: 0.3px; } /* 批量删除按钮样式 */ .btn::after { content: "";} .btn-danger { color: var(--dsw-alias-state-error-primary); overflow: hidden} .btn-danger::before { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0); transition: background-color .2s; pointer-events: none; } .btn-danger:hover::before { background-color: var(--dsw-alias-interactive-bg-hover-danger); } .ds-button-container { display: flex; flex-direction: column; gap: 12px; margin-bottom: 12px; } .ds-button-row { display: flex; gap: 12px; } `); // ---------- 辅助函数 ---------- function waitForElement(selector, timeout = 10000) { return new Promise((resolve) => { const el = document.querySelector(selector); if (el) return resolve(el); const obs = new MutationObserver(() => { const target = document.querySelector(selector); if (target) { obs.disconnect(); resolve(target); } }); obs.observe(document.body, { childList: true, subtree: true }); setTimeout(() => { obs.disconnect(); resolve(null); }, timeout); }); } // 获取原始左侧列表中的对话项(克隆时过滤复选框) function getOriginalChatGroups() { let leftContainer = document.querySelector(SELECTORS.leftList); if (!leftContainer) { leftContainer = document.querySelector('#root > div > div > div.c3ecdb44 > div.dc04ec1d'); } if (!leftContainer) return []; const groups = []; const chatItems = leftContainer.querySelectorAll('a'); if (chatItems.length > 0) { const container = document.createElement('div'); container.className = '_3098d02'; chatItems.forEach(item => { if (item.textContent.trim() !== '') { const clonedItem = item.cloneNode(true); // 过滤掉批量删除添加的复选框 const checkbox = clonedItem.querySelector('input[type="checkbox"]'); if (checkbox) checkbox.remove(); container.appendChild(clonedItem); } }); if (container.children.length > 0) { groups.push(container); } } return groups; } // 渲染搜索面板列表 function renderPanelList(searchTerm = '') { if (!panelListContainer) return; let groups = getOriginalChatGroups(); panelListContainer.innerHTML = ''; itemElementMap.clear(); let hasAnyVisible = false; const lowerSearch = searchTerm.toLowerCase().trim(); if (groups.length === 0) { const container = document.createElement('div'); container.className = '_3098d02'; const sampleItems = [ { title: '测试对话 1', href: '#' }, { title: '测试对话 2', href: '#' }, { title: '测试对话 3', href: '#' } ]; sampleItems.forEach(item => { const link = document.createElement('a'); link.href = item.href; link.className = '_546d736'; link.style.cssText = 'display:block; padding:10px 12px; margin:2px 0; border-radius:10px; cursor:pointer; color:var(--dsw-alias-label-primary, #1f1f1f); text-decoration:none;'; link.innerHTML = `${item.title}`; link.addEventListener('click', (e) => { e.stopPropagation(); hideModal(); }); container.appendChild(link); }); groups.push(container); } for (const group of groups) { const clonedGroup = group.cloneNode(true); if (lowerSearch) { const chatItems = clonedGroup.querySelectorAll(SELECTORS.chatItem); let groupHasVisible = false; chatItems.forEach(item => { const titleSpan = item.querySelector('.c08e6e93'); const title = titleSpan ? titleSpan.innerText.trim() : item.textContent.trim(); if (title.toLowerCase().includes(lowerSearch)) { groupHasVisible = true; } else { item.remove(); } }); if (!groupHasVisible) continue; } hasAnyVisible = true; const clonedChatItems = clonedGroup.querySelectorAll(SELECTORS.chatItem); clonedChatItems.forEach(item => { item.addEventListener('click', (e) => { e.stopPropagation(); const href = item.getAttribute('href'); if (href) { const originalItem = document.querySelector(`${SELECTORS.chatItem}[href="${href}"]`); if (originalItem && originalItem.click) { originalItem.click(); hideModal(); } else { hideModal(); } } else { hideModal(); } }); }); panelListContainer.appendChild(clonedGroup); } if (!hasAnyVisible && lowerSearch !== '') { const noResults = document.createElement('div'); noResults.className = 'ds-no-results'; noResults.textContent = '没有找到相关对话'; panelListContainer.appendChild(noResults); } else if (groups.length === 0) { const noResults = document.createElement('div'); noResults.className = 'ds-no-results'; noResults.textContent = '暂无历史对话'; panelListContainer.appendChild(noResults); } } let currentSearchTerm = ''; function syncPanelList() { if (panelListContainer && isPanelVisible) { renderPanelList(currentSearchTerm); } } function startObservingLeftList() { const leftContainer = document.querySelector(SELECTORS.leftList); if (!leftContainer) return; if (leftObserver) leftObserver.disconnect(); leftObserver = new MutationObserver(() => { syncPanelList(); }); leftObserver.observe(leftContainer, { childList: true, subtree: true, attributes: false }); } function showModal() { if (!modalPanel) return; renderPanelList(currentSearchTerm); modalPanel.style.display = 'flex'; if (!document.querySelector('.ds-modal-overlay')) { const overlay = document.createElement('div'); overlay.className = 'ds-modal-overlay'; overlay.addEventListener('click', hideModal); document.body.appendChild(overlay); } isPanelVisible = true; if (searchInput) { searchInput.focus(); searchInput.value = currentSearchTerm; renderPanelList(currentSearchTerm); } } function hideModal() { if (modalPanel) modalPanel.style.display = 'none'; const overlay = document.querySelector('.ds-modal-overlay'); if (overlay) overlay.remove(); isPanelVisible = false; } // 添加历史对话折叠按钮(防重复) function addCollapseButton() { const leftList = document.querySelector(SELECTORS.leftList); if (!leftList) return; if (document.querySelector('.ds-collapse-btn')) return; const collapseBtn = document.createElement('button'); collapseBtn.className = 'ds-collapse-btn collapsed'; collapseBtn.innerHTML = ` 历史对话 `; leftList.insertBefore(collapseBtn, leftList.firstChild); leftList.classList.add('ds-history-collapsed'); collapseBtn.addEventListener('click', () => { const isCollapsed = leftList.classList.contains('ds-history-collapsed'); if (isCollapsed) { leftList.classList.remove('ds-history-collapsed'); collapseBtn.classList.remove('collapsed'); } else { leftList.classList.add('ds-history-collapsed'); collapseBtn.classList.add('collapsed'); } }); } // ---------- 搜索功能(独立模块)---------- async function initSearch() { // 创建模态框 modalPanel = document.createElement('div'); modalPanel.className = 'ds-history-modal'; modalPanel.style.display = 'none'; modalPanel.innerHTML = `
历史对话
`; document.body.appendChild(modalPanel); panelListContainer = modalPanel.querySelector('#ds-panel-list'); searchInput = modalPanel.querySelector('#ds-history-search-input'); const closeBtn = modalPanel.querySelector('.close-btn'); searchInput.addEventListener('input', (e) => { currentSearchTerm = e.target.value; renderPanelList(currentSearchTerm); }); closeBtn.addEventListener('click', hideModal); modalPanel.addEventListener('click', (e) => e.stopPropagation()); startObservingLeftList(); renderPanelList(''); // 创建搜索按钮(克隆新对话按钮) const newChatBtn = await waitForElement(SELECTORS.newChatBtn); if (!newChatBtn) return; if (document.querySelector('[data-is-search-btn="true"]')) return; searchBtn = newChatBtn.cloneNode(true); searchBtn.setAttribute('data-is-search-btn', 'true'); searchBtn.innerHTML = ` 搜索历史对话 `; searchBtn.addEventListener('click', (e) => { e.stopPropagation(); if (isPanelVisible) hideModal(); else showModal(); }); newChatBtn.parentNode.insertBefore(searchBtn, newChatBtn.nextSibling); } // ---------- 批量删除功能(仅登录后启用)---------- function initBatchDelete() { // 检查登录状态 const userTokenRaw = localStorage.getItem("userToken"); if (!userTokenRaw) return; try { const userToken = JSON.parse(userTokenRaw); if (!userToken || !userToken.value) return; } catch(e) { return; } // 等待必要元素出现 const scrollArea = document.querySelector(SELECTORS.scrollArea); if (!scrollArea) { setTimeout(initBatchDelete, 500); return; } const btnNewChat = document.querySelector(SELECTORS.newChatBtn); if (!btnNewChat) { setTimeout(initBatchDelete, 500); return; } // 防止重复初始化 if (document.querySelector('.ds-button-container[data-batch="true"]')) return; originalNewChatBtn = btnNewChat; // 创建批量删除按钮容器 batchContainer = document.createElement("div"); batchContainer.className = 'ds-button-container'; batchContainer.setAttribute('data-batch', 'true'); const buttonRow = document.createElement('div'); buttonRow.className = 'ds-button-row'; btnBatchDelete = originalNewChatBtn.cloneNode(true); btnBatchDelete.innerHTML = '批量删除'; btnBatchDelete.classList.add('btn-danger'); btnBatchDelete.style.width = "40%"; const newChatButton = originalNewChatBtn.cloneNode(true); newChatButton.innerHTML = '开启新对话'; newChatButton.style.width = "55%"; newChatButton.addEventListener('click', () => { originalNewChatBtn.click(); }); buttonRow.appendChild(btnBatchDelete); buttonRow.appendChild(newChatButton); batchContainer.appendChild(buttonRow); // 操作按钮容器(取消、反选、删除) actionContainer = document.createElement("div"); actionContainer.className = 'ds-button-container'; actionContainer.style.visibility = "hidden"; actionContainer.style.opacity = "0"; actionContainer.style.height = "0"; actionContainer.style.overflow = "hidden"; actionContainer.style.transition = "all 0.3s ease"; const actionRow = document.createElement('div'); actionRow.className = 'ds-button-row'; btnCancel = originalNewChatBtn.cloneNode(true); btnCancel.textContent = "取消"; btnCancel.style.width = "30%"; btnReverse = originalNewChatBtn.cloneNode(true); btnReverse.textContent = "反选"; btnReverse.style.width = "30%"; btnConfirm = originalNewChatBtn.cloneNode(true); btnConfirm.textContent = "删除"; btnConfirm.classList.add('btn-danger'); btnConfirm.style.width = "30%"; actionRow.appendChild(btnCancel); actionRow.appendChild(btnReverse); actionRow.appendChild(btnConfirm); actionContainer.appendChild(actionRow); // 插入到原始新对话按钮之前,并隐藏原始按钮 originalNewChatBtn.before(batchContainer); batchContainer.before(actionContainer); originalNewChatBtn.style.display = 'none'; // 添加事件 btnBatchDelete.addEventListener("click", function () { batchContainer.style.visibility = "hidden"; batchContainer.style.opacity = "0"; batchContainer.style.height = "0"; batchContainer.style.overflow = "hidden"; actionContainer.style.visibility = "visible"; actionContainer.style.opacity = "1"; actionContainer.style.height = "auto"; actionContainer.style.overflow = "visible"; addCheckbox(); }); btnCancel.addEventListener("click", function () { batchContainer.style.visibility = "visible"; batchContainer.style.opacity = "1"; batchContainer.style.height = "auto"; batchContainer.style.overflow = "visible"; actionContainer.style.visibility = "hidden"; actionContainer.style.opacity = "0"; actionContainer.style.height = "0"; actionContainer.style.overflow = "hidden"; removeCheckbox(); }); btnReverse.addEventListener("click", function () { for (let checkbox of checkboxes) { checkbox.checked = !checkbox.checked; } }); btnConfirm.addEventListener("click", confirmDelete); // 监听新对话添加,恢复被隐藏的日期分组 const innerScrollArea = document.querySelector(".ds-scroll-area")?.querySelector(".ds-scroll-area"); if (innerScrollArea && innerScrollArea.firstChild) { if (batchDeleteObserver) batchDeleteObserver.disconnect(); batchDeleteObserver = new MutationObserver((mutations) => { for (let mutation of mutations) { try { for (let node of mutation.addedNodes) { if (node.tagName === "A") { node.parentElement.style.display = ""; } } } catch(e) {} } }); batchDeleteObserver.observe(innerScrollArea.firstChild, { childList: true, subtree: true }); } } function addCheckbox() { const scrollArea = document.querySelector(".ds-scroll-area")?.querySelector(".ds-scroll-area"); if (!scrollArea) return; const chats = scrollArea.querySelectorAll("a"); for (let a of chats) { if (a.querySelector('input[type="checkbox"]')) continue; // 避免重复添加 a.style.justifyContent = "unset"; let checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.style.cssText = `height:100%; aspect-ratio: 1;`; checkbox.name = a.href.toString().split("/").pop(); checkbox.refer = a; checkbox.onclick = (e) => { e.stopPropagation(); }; a.prepend(checkbox); checkboxes.push(checkbox); } } function removeCheckbox() { for (let checkbox of checkboxes) { checkbox.remove(); } checkboxes = []; } function removeEmptyDateGroup() { const scrollArea = document.querySelector(".ds-scroll-area")?.querySelector(".ds-scroll-area"); if (!scrollArea || !scrollArea.firstChild) return; let dateGroups = scrollArea.firstChild.childNodes; for (let dateGroup of dateGroups) { let flagNoContent = true; for (let a of dateGroup.querySelectorAll("a")) { if (a.style.display !== "none") { flagNoContent = false; } } if (flagNoContent) { dateGroup.style.display = "none"; } } } function confirmDelete() { let userToken; try { userToken = JSON.parse(localStorage.getItem("userToken")).value; } catch(e) { return; } (async () => { const promises = []; for (let checkbox of checkboxes) { if (!checkbox.checked) continue; let sessionID = checkbox.name; promises.push(fetch("https://chat.deepseek.com/api/v0/chat_session/delete", { "credentials": "include", "headers": { "Accept": "*/*", "authorization": `Bearer ${userToken}`, "content-type": "application/json", }, "body": JSON.stringify({chat_session_id: sessionID}), "method": "POST", }).then(r => { if (r.ok) { if (checkbox.name === location.href.toString().split('/').pop()) { const newChatBtn = document.querySelector(SELECTORS.newChatBtn); if (newChatBtn) newChatBtn.click(); } checkbox.refer.style.display = "none"; } })); } await Promise.allSettled(promises); removeEmptyDateGroup(); })(); removeCheckbox(); batchContainer.style.visibility = "visible"; batchContainer.style.opacity = "1"; batchContainer.style.height = "auto"; batchContainer.style.overflow = "visible"; actionContainer.style.visibility = "hidden"; actionContainer.style.opacity = "0"; actionContainer.style.height = "0"; actionContainer.style.overflow = "hidden"; } // ---------- 观察器生命周期管理 ---------- function disconnectAllObservers() { if (leftObserver) { leftObserver.disconnect(); leftObserver = null; } if (rootObserver) { rootObserver.disconnect(); rootObserver = null; } if (batchDeleteObserver) { batchDeleteObserver.disconnect(); batchDeleteObserver = null; } } function observeForReinit() { if (rootObserver) rootObserver.disconnect(); rootObserver = new MutationObserver(() => { // 重新添加折叠按钮(如果缺失) const leftList = document.querySelector(SELECTORS.leftList); if (leftList && !document.querySelector('.ds-collapse-btn')) { addCollapseButton(); } // 重新初始化批量删除(如果登录但按钮丢失) const userTokenRaw = localStorage.getItem("userToken"); if (userTokenRaw) { try { const userToken = JSON.parse(userTokenRaw); if (userToken && userToken.value && !document.querySelector('.ds-button-container[data-batch="true"]')) { initBatchDelete(); } } catch(e) {} } // 重新初始化搜索按钮(如果丢失) if (!document.querySelector('[data-is-search-btn="true"]') && document.querySelector(SELECTORS.newChatBtn)) { initSearch(); } }); rootObserver.observe(document.body, { childList: true, subtree: true }); } // 页面卸载时断开所有观察器(可选) window.addEventListener('beforeunload', () => { disconnectAllObservers(); }); // ---------- 启动脚本 ---------- async function start() { // 先初始化搜索(始终可用) await initSearch(); // 添加折叠按钮 addCollapseButton(); // 尝试初始化批量删除(仅登录后生效) initBatchDelete(); // 监听后续变化 observeForReinit(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', start); } else { start(); } })();