侧栏 hover 显示快速删除;不改动原生 ⋯ 菜单
// ==UserScript==
// @name DeepSeek quick delete
// @namespace http://tampermonkey.net/
// @version 1.0
// @description 侧栏 hover 显示快速删除;不改动原生 ⋯ 菜单
// @match https://chat.deepseek.com/*
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const STYLE_ID = 'tm-ds-qdel-style';
const BTN_CLS = 'tm-ds-qdel-btn';
const HOST_CLS = 'tm-ds-qdel-host';
const MARK = 'tmDsQdel';
const deletedIds = new Set();
function ensureStyle() {
if (document.getElementById(STYLE_ID)) return;
const style = document.createElement('style');
style.id = STYLE_ID;
style.textContent = `
a.${HOST_CLS} {
position: relative !important;
}
.${BTN_CLS} {
position: absolute;
right: 32px; /* 越大越靠左;对应以前 margin-right:32px 的间距感 */
top: 50%;
transform: translateY(-50%);
z-index: 6;
width: 22px;
height: 22px;
line-height: 20px;
text-align: center;
border-radius: 6px;
color: #f66;
cursor: pointer;
font-size: 16px;
user-select: none;
opacity: 0;
pointer-events: none;
transition: opacity .12s ease, background .12s ease;
}
a.${HOST_CLS}:hover .${BTN_CLS} {
opacity: 1;
pointer-events: auto;
}
.${BTN_CLS}:hover {
opacity: 1 !important;
pointer-events: auto !important;
background: rgba(255,80,80,.14);
}
`;
document.documentElement.appendChild(style);
}
function toast(msg) {
console.error('[ds-qdel]', msg);
const el = document.createElement('div');
el.textContent = msg;
el.style.cssText =
'position:fixed;z-index:2147483647;right:16px;bottom:16px;max-width:360px;' +
'padding:10px 14px;border-radius:8px;background:#222;color:#fff;font:13px/1.4 sans-serif;';
document.body.appendChild(el);
setTimeout(() => el.remove(), 3200);
}
function getId(a) {
const m = (a.getAttribute('href') || '').match(/\/a\/chat\/s\/([a-f0-9-]+)/i);
return m ? m[1] : null;
}
function pickLinks() {
return [...document.querySelectorAll('a[href*="/a/chat/s/"]')].filter(getId);
}
function getToken() {
for (const k of ['userToken', 'USER_TOKEN', 'token']) {
const raw = localStorage.getItem(k);
if (!raw) continue;
try {
const p = JSON.parse(raw);
const t = typeof p === 'object' && p ? (p.value || p.token || p.accessToken) : p;
if (t) return String(t);
} catch {
if (raw.length > 10) return raw;
}
}
throw new Error('未找到 userToken,请先登录');
}
async function deleteChat(id) {
const token = getToken();
const r = await fetch('/api/v0/chat_session/delete', {
method: 'POST',
credentials: 'include',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
Accept: '*/*',
'X-Client-Platform': 'web',
'X-Client-Locale': 'zh_CN',
'X-App-Version': '2025.04.25',
},
body: JSON.stringify({ chat_session_id: id }),
});
const text = await r.text();
let j = null;
try { j = JSON.parse(text); } catch { /* ignore */ }
if (!r.ok) throw new Error(`HTTP ${r.status} ${text.slice(0, 120)}`);
const code = j?.code ?? j?.data?.biz_code ?? j?.biz_code;
if (code != null && Number(code) !== 0) {
throw new Error(j?.msg || j?.message || `code=${code}`);
}
}
function findChatRow(a) {
let el = a;
while (el.parentElement) {
const p = el.parentElement;
if (p === document.body || p === document.documentElement) break;
if (p.matches('nav, aside, [role="navigation"]')) break;
const others = pickLinks().filter(x => x !== a && p.contains(x));
if (others.length) return el;
el = p;
}
return el;
}
function hideChatRow(a) {
const row = findChatRow(a);
row.style.setProperty('display', 'none', 'important');
return row;
}
function showChatRow(row) {
if (!row) return;
row.style.removeProperty('display');
}
function goHomeSoft() {
const btn = [
...document.querySelectorAll('a[href="/"], a[href="/a/chat"], a[href="/a/chat/"], button, div[role="button"]'),
].find(el => {
const tx = (el.getAttribute('aria-label') || el.textContent || '').trim();
return (
el.matches('a[href="/"], a[href="/a/chat"], a[href="/a/chat/"]') ||
/新对话|新聊天|new chat|开启新对话/i.test(tx)
);
});
if (btn) btn.click();
else location.assign('https://chat.deepseek.com/');
}
function onDeleteClick(e, a, btn, id) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
if (btn.dataset.busy) return;
btn.dataset.busy = '1';
const viewing = location.pathname.includes(`/a/chat/s/${id}`);
deletedIds.add(id);
const row = hideChatRow(a);
if (viewing) goHomeSoft();
deleteChat(id)
.catch(err => {
deletedIds.delete(id);
showChatRow(row);
delete a.dataset[MARK];
toast(String(err?.message || err));
})
.finally(() => {
delete btn.dataset.busy;
btn.textContent = '×';
});
}
function attach(a) {
const id = getId(a);
if (!id) return;
if (deletedIds.has(id)) {
hideChatRow(a);
return;
}
if (a.dataset[MARK]) return;
a.dataset[MARK] = '1';
a.classList.add(HOST_CLS);
const btn = document.createElement('span');
btn.className = BTN_CLS;
btn.textContent = '×';
btn.title = 'Delete';
// mousedown + click 双保险,避免被 <a> 导航抢走
const handler = e => onDeleteClick(e, a, btn, id);
btn.addEventListener('mousedown', handler, true);
btn.addEventListener('click', handler, true);
// 挂到 <a> 自身,不进原生 ⋯ 容器
a.appendChild(btn);
}
function scan() {
ensureStyle();
pickLinks().forEach(attach);
}
let timer = 0;
new MutationObserver(() => {
if (timer) return;
timer = setTimeout(() => {
timer = 0;
scan();
}, 50);
}).observe(document.documentElement, { childList: true, subtree: true });
scan();
})();