// ==UserScript==
// @name DeepSeek 增强工具集
// @namespace http://tampermonkey.net/
// @version 2.2
// @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 rootObserver = null;
// 批量删除相关变量
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;
// ---------- 添加自定义样式 ----------
GM_addStyle(`
/* 折叠按钮样式 */
.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;
}
/* 批量删除按钮样式 */
.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 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');
}
});
}
// ---------- 折叠与展开辅助函数(批量删除耦合) ----------
function expandHistoryIfCollapsed() {
const leftList = document.querySelector(SELECTORS.leftList);
if (!leftList) return;
if (leftList.classList.contains('ds-history-collapsed')) {
leftList.classList.remove('ds-history-collapsed');
const collapseBtn = document.querySelector('.ds-collapse-btn');
if (collapseBtn && collapseBtn.classList.contains('collapsed')) {
collapseBtn.classList.remove('collapsed');
}
}
}
// ---------- 批量删除功能(仅登录后启用)----------
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 () {
expandHistoryIfCollapsed();
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 (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) {}
}
});
rootObserver.observe(document.body, { childList: true, subtree: true });
}
window.addEventListener('beforeunload', () => {
disconnectAllObservers();
});
// ---------- 启动脚本 ----------
function start() {
addCollapseButton();
initBatchDelete();
observeForReinit();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', start);
} else {
start();
}
})();