您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
快速/直接删除 AI 对话,支持快捷键(Alt+Command+Backspace)
// ==UserScript== // @name 🚀 Delete AI Conversation // @namespace https://github.com/xianghongai/Tampermonkey-UserScript // @version 1.0.1 // @description Delete AI conversation quickly and directly. Support shortcut key(Alt+Command+Backspace) // @description:zh-CN 快速/直接删除 AI 对话,支持快捷键(Alt+Command+Backspace) // @description:zh-TW 快速/直接删除 AI 對話,支持快捷鍵(Alt+Command+Backspace) // @description:ja-JP AI 会話を迅速に/直接に削除します。ショートカットキー(Alt+Command+Backspace)をサポートします。 // @description:ko-KR AI 대화를 빠르고 직접적으로 삭제합니다. 단축키(Alt+Command+Backspace)를 지원합니다. // @description:ru-RU Быстро/непосредственно удалить AI-диалог. Поддерживает горячую клавишу(Alt+Command+Backspace) // @description:es-ES Eliminar rápidamente/ directamente una conversación de IA. Soporta el atajo de teclado(Alt+Command+Backspace) // @description:fr-FR Supprimer rapidement/ directement une conversation AI. Prise en charge des raccourcis clavier(Alt+Command+Backspace) // @author Nicholas Hsiang // @icon https://xinlu.ink/favicon.ico // @match https://monica.im/home* // @match https://grok.com/* // @match https://chatgpt.com/c/* // @match https://chat.deepseek.com/* // @match https://gemini.google.com/* // @grant GM.xmlHttpRequest // @grant GM_addStyle // @run-at document-end // @license MIT // ==/UserScript== (function () { 'use strict'; console.log(GM_info.script.name); // 两种删除交互 // 1. 通过右下角按钮,删除当前页面的对话 // 2. 在对话列表中,点击删除按钮,删除对应的对话 // 两种删除方式 // 1. API:通过服务删除 // 2. UI:借助原站点 UI 删除交互 // 两种获取初始对话数据方式 // 1. ID:从 URL 中获取 // 2. TITLE:从对话标题中获取 (借助 Developer Tools + getElementPath(temp) 获取元素路径) // 模式 const MODE = { ID_API: 'id_api', ID_UI: 'id_ui', TITLE_UI: 'title_ui', }; const CONFIG = { 'https://gemini.google.com': { mode: MODE.ID_UI, conversation_url_pattern: 'https://gemini.google.com/app/{id}', conversation_item_selector: '.conversation-items-container', conversation_item_action_selector: '.conversation-actions-menu-button', conversation_item_action_menu_item_selector: '[data-test-id="delete-button"]', delete_confirm_modal_button_selector: '[data-test-id="confirm-button"]', getConversationItemById(conversationId) { const conversationItems = Array.from(document.querySelectorAll('.conversation-items-container')); const conversationItem = conversationItems.find((item) => { const conversationIdElement = item.querySelector('.mat-mdc-tooltip-trigger.conversation'); return conversationIdElement?.getAttribute('jslog').includes(conversationId); }); return conversationItem; }, }, 'https://grok.com': { mode: MODE.ID_API, api_url: 'https://grok.com/rest/app-chat/conversations/soft/{id}', method: 'DELETE', // 以从 URL 中提取对话 ID conversation_url_pattern: 'https://grok.com/chat/{id}', // 获取所有"对话项"的 CSS 选择器。不能带层级。 // THINK: 通过选择器,可以做两件事: // 1. 获取所有对话项; // 2. 点击内部操作,获取该对话项。 conversation_item_selector: '[data-sidebar="menu-button"][href^="/chat/"]', // 从对话项中获取携带对话 ID 元素 getConversationIdElement(conversationItem) { const conversationIdElement = conversationItem.querySelector('[href^="/chat/"]') || conversationItem; return conversationIdElement; }, // 获取对话 ID,通过对话项中的元素 (对话项,或其内部元素) getConversationIdFormItem(element) { return element.getAttribute('href').split('/chat/')[1] }, }, 'https://monica.im': { mode: MODE.ID_UI, conversation_url_pattern: '?convId={id}', conversation_item_selector: '[class^="conversation-name-item-wrapper"]', conversation_item_action_selector: '[class^="popover-content-wrapper"]', conversation_item_action_menu_item_selector: '[class^="dropdown-menu-item"]', delete_confirm_modal_button_selector: '[class^="monica-btn"]', // 通过对话 ID 获取对话项元素,以进入更多菜单 getConversationItemById(conversationId) { const conversationItemSelector = '[href$="{id}"]'.replace('{id}', conversationId); return document.querySelector(conversationItemSelector); }, getConversationIdFormQueryValue(queryValue) { return queryValue.split('conv:')[1]; }, }, 'https://chatgpt.com': { mode: MODE.ID_API, api_url: 'https://chatgpt.com/backend-api/conversation/{id}', method: 'PATCH', api_body: JSON.stringify({ is_visible: false, conversation_id: '{id}', }), need_authorization: true, conversation_url_pattern: 'https://chatgpt.com/c/{id}', // THINK:不通过选择器,需要两个函数分别处理 // 获取所有对话项 getConversationItems() { return Array.from(document.querySelectorAll('.group.__menu-item')).filter((item) => item.getAttribute('href')?.startsWith('/c/')); }, // 点击内部操作,获取该对话项 getConversationItem(event) { const target = event.target; const conversationItem = parent(target, '.group.__menu-item'); return conversationItem; }, getConversationIdElement(conversationItem) { return conversationItem; }, getConversationIdFormItem(element) { return element.getAttribute('href').split('/c/')[1]; }, }, 'https://chat.deepseek.com': { mode: MODE.TITLE_UI, conversation_url_pattern: 'https://chat.deepseek.com/a/chat/s/{id}', conversation_item_selector: 'html > body:nth-child(2) > div:nth-child(1) > div:nth-child(1) > div:nth-child(1) > div:nth-child(2) > div:nth-child(1) > div:nth-child(1) > div:nth-child(3) > div:nth-child(1) > div > div[tabindex]', conversation_item_action_selector: 'div[tabindex]', conversation_item_action_menu_item_selector: '.ds-dropdown-menu-option.ds-dropdown-menu-option--error', delete_confirm_modal_button_selector: '.ds-modal-content .ds-button.ds-button--error', getConversationTitle() { return document.querySelector('html > body:nth-child(2) > div:nth-child(1) > div:nth-child(1) > div:nth-child(1) > div:nth-child(2) > div:nth-child(3) > div:nth-child(1) > div:nth-child(1) > div:nth-child(1) > div:nth-child(1) > div:nth-child(1)').textContent.trim(); }, }, // TODO: document_title_ui/document_title_api 模式 // 1. 获取 Document Title // 2. 获取 Document 中的所有对话项 // 3. 遍历对话项,找到与 Document Title 匹配的对话项 // 4. 点击对话项的操作按钮 // 5. 遍历对话项的操作菜单项,找到删除按钮 // 6. 点击删除按钮 // 7. 点击确认删除按钮 }; let config = null; // 添加删除状态跟踪变量 let isDeleting = false; // 保存删除按钮引用 let deleteButton = null; function getConfig() { if (config) { return config; } config = CONFIG[window.location.origin]; if (!config) { throw new Error(`未找到当前网站的配置: ${window.location.origin}`); } return config; } function getConversationIdFormUrl() { const currentUrl = window.location.href; const config = getConfig(); const { conversation_url_pattern, getConversationIdFormQueryValue } = config; // 如果模式以?开头,表示从查询参数中获取 if (conversation_url_pattern.startsWith('?')) { const paramName = conversation_url_pattern.slice(1, conversation_url_pattern.indexOf('=')); const urlParams = new URLSearchParams(window.location.search); const paramValue = urlParams.get(paramName); if (!paramValue) { console.error(`未找到查询参数: ${paramName}`); return null; } if (typeof getConversationIdFormQueryValue === 'function') { return getConversationIdFormQueryValue(paramValue); } return paramValue; } // 否则从路径中提取 else { const pattern = conversation_url_pattern.replace('{id}', '(.+)'); const regex = new RegExp(pattern); const match = currentUrl.match(regex); if (match && match[1]) { return match[1]; } console.error('无法从URL中提取对话ID'); return null; } } function deleteForIdUi(conversationItem) { setButtonLoading(true); const { conversation_item_action_selector, conversation_item_action_menu_item_selector, delete_confirm_modal_button_selector } = getConfig(); if (!conversationItem) { notification('未找到对话项', { type: 'error' }); setButtonLoading(false); return; } const itemActionElement = conversationItem.querySelector(conversation_item_action_selector); if (itemActionElement) { // 点击对话操作按钮 itemActionElement.click(); setTimeout(() => { // 更多菜单列表项 const menuItemElements = document.querySelectorAll(conversation_item_action_menu_item_selector); let foundDeleteButton = false; for (let i = 0; i < menuItemElements.length; i++) { const menuItemElement = menuItemElements[i]; const menuItemText = menuItemElement.textContent.trim(); const isDeleteElement = menuItemText.includes('Delete') || menuItemText.includes('删除'); if (isDeleteElement) { foundDeleteButton = true; // 点击删除按钮 menuItemElement.click(); // 需要“确认删除” if (delete_confirm_modal_button_selector) { setTimeout(() => { const confirmModalButtonElements = document.querySelectorAll(delete_confirm_modal_button_selector); let foundConfirmButton = false; for (let i = 0; i < confirmModalButtonElements.length; i++) { const confirmModalButtonElement = confirmModalButtonElements[i]; const confirmModalButtonText = confirmModalButtonElement.textContent.trim(); const isDeleteElement = confirmModalButtonText.includes('Delete') || confirmModalButtonText.includes('删除'); if (isDeleteElement) { foundConfirmButton = true; // 点击确认删除按钮 confirmModalButtonElement.click(); notification('已发起删除请求'); // 在操作完成后延迟重置按钮状态 setButtonLoading(false); break; } } if (!foundConfirmButton) { notification('未找到确认按钮', { type: 'error' }); setButtonLoading(false); } }, 500); } // 没有确认按钮,直接删除 else { notification('已发起删除请求'); // 在操作完成后延迟重置按钮状态 setButtonLoading(false); } break; } } if (!foundDeleteButton) { notification('未找到删除按钮', { type: 'error' }); setButtonLoading(false); } }, 500); } else { notification('未找到操作按钮', { type: 'error' }); setButtonLoading(false); } } async function deleteForIdApi(conversationId) { setButtonLoading(true); if (!conversationId) { notification('无法获取对话ID', { type: 'error' }); setButtonLoading(false); return; } const { api_url, method, api_body, need_authorization } = getConfig(); // 请求 URL 中替换 {id} 为对话 ID const url = api_url.replace('{id}', conversationId); const headers = { 'Content-Type': 'application/json', }; let cacheAuthorization = null; const cacheAuthorizationKey = `authorization__${window.location.origin}`; if (need_authorization) { // 从 localStorage 中获取 Authorization 值 cacheAuthorization = localStorage.getItem(cacheAuthorizationKey); if (cacheAuthorization) { headers.Authorization = cacheAuthorization; } else { // 提示用户提供 Authorization 值 const authorization = prompt('请输入 Authorization 值 (将通过 localStorage 缓存,下次删除时无需再次输入)'); if (authorization) { headers.Authorization = authorization; } else { notification('请输入 Authorization 值', { type: 'error' }); setButtonLoading(false); return; } } } try { if (method === 'DELETE') { const response = await fetch(url, { method, }); if (response.ok) { notification('已发起删除请求,通过 API 删除,需要刷新页面查看结果'); } else { console.error(response); notification('删除出现异常,请查看 Developer Tools 中的 Console 输出、检查 Network 请求', { type: 'error' }); } } else if (method === 'PATCH') { const response = await fetch(url, { method, body: api_body.replace('{id}', conversationId), headers, }); if (response.ok) { notification('已发起删除请求,通过 API 删除,需要刷新页面查看结果'); if (need_authorization) { // 缓存 Authorization 值,以当前网站的 origin 为 key localStorage.setItem(cacheAuthorizationKey, headers.Authorization); } } else { console.error(response); notification('删除出现异常,请查看 Developer Tools 中的 Console 输出、检查 Network 请求', { type: 'error' }); } } } catch (error) { console.error(error); notification('删除请求失败: ' + error.message, { type: 'error' }); } finally { // 无论成功或失败,都在1秒后恢复按钮状态 setTimeout(() => { setButtonLoading(false); }, 1000); } } function deleteForTitleUi(conversationTitle) { const config = getConfig(); const { conversation_item_selector, conversation_item_action_selector, conversation_item_action_menu_item_selector, delete_confirm_modal_button_selector } = config; const conversationItemElements = document.querySelectorAll(conversation_item_selector); for (let i = 0; i < conversationItemElements.length; i++) { const conversationItemElement = conversationItemElements[i]; const conversationItemText = conversationItemElement.textContent.trim(); if (conversationItemText === conversationTitle) { const conversationItemActionElement = conversationItemElement.querySelector(conversation_item_action_selector); // 点击对话操作按钮 conversationItemActionElement.click(); setTimeout(() => { const conversationItemActionMenuElements = document.querySelectorAll(conversation_item_action_menu_item_selector); for (let j = 0; j < conversationItemActionMenuElements.length; j++) { const conversationItemActionMenuElement = conversationItemActionMenuElements[j]; const conversationItemActionMenuElementText = conversationItemActionMenuElement.textContent.trim(); if (conversationItemActionMenuElementText.includes('Delete') || conversationItemActionMenuElementText.includes('删除')) { // 点击删除按钮 conversationItemActionMenuElement.click(); setTimeout(() => { const deleteConfirmModalButtonElements = document.querySelectorAll(delete_confirm_modal_button_selector); for (let k = 0; k < deleteConfirmModalButtonElements.length; k++) { const deleteConfirmModalButtonElement = deleteConfirmModalButtonElements[k]; const deleteConfirmModalButtonElementText = deleteConfirmModalButtonElement.textContent.trim(); if (deleteConfirmModalButtonElementText.includes('Delete') || deleteConfirmModalButtonElementText.includes('删除')) { // 点击确认删除按钮 deleteConfirmModalButtonElement.click(); notification('已删除对话'); break; } } }, 500); break; } } }, 500); break; } } } function handleDelete() { if (isDeleting) { notification('正在处理删除请求,请稍候...', { type: 'warning' }); return; } const config = getConfig(); if (!config) { notification('无法获取配置信息', { type: 'error' }); return; } const { mode, getConversationTitle, getConversationItemById } = config; const conversationId = getConversationIdFormUrl(); if (mode === MODE.ID_API) { deleteForIdApi(conversationId); } else if (mode === MODE.ID_UI) { deleteForIdUi(getConversationItemById(conversationId)); } else if (mode === MODE.TITLE_UI) { deleteForTitleUi(getConversationTitle()); } } function createElement() { const wrap = document.createElement('div'); wrap.className = 'x-conversation-action-wrap'; // 删除按钮 const btn = document.createElement('button'); btn.textContent = 'Delete'; btn.className = 'x-conversation-action x-conversation-delete'; btn.onclick = handleDelete; deleteButton = btn; wrap.appendChild(btn); // 侦测对话项,添加删除按钮 const inspectConversationItemBtn = document.createElement('button'); inspectConversationItemBtn.textContent = 'Inspect'; inspectConversationItemBtn.className = 'x-conversation-action x-conversation-inspect'; inspectConversationItemBtn.onclick = createRemoveActionForConversationItem; wrap.appendChild(inspectConversationItemBtn); document.body.appendChild(wrap); } // 为每个对话项添加删除按钮 function createRemoveActionForConversationItem() { const config = getConfig(); const { getConversationItems, conversation_item_selector } = config; let conversationItemElements = [] if (typeof getConversationItems === 'function') { conversationItemElements = getConversationItems(); } else { conversationItemElements = document.querySelectorAll(conversation_item_selector); } for (let i = 0; i < conversationItemElements.length; i++) { const conversationItemElement = conversationItemElements[i]; conversationItemElement.style.position = 'relative'; // 是否已经添加过删除按钮 const removeIconElement = conversationItemElement.querySelector('.x-conversation-item-remove'); if (removeIconElement) { // 切换显示状态 if (removeIconElement.classList.contains('hidden')) { removeIconElement.classList.remove('hidden'); } else { removeIconElement.classList.add('hidden'); } continue; } // 添加删除按钮 const iconElement = document.createElement('i'); iconElement.innerHTML = getRemoveIcon(); iconElement.className = 'x-conversation-item-remove'; iconElement.setAttribute('title', 'Delete Conversation'); conversationItemElement.appendChild(iconElement); } } // 添加快捷键监听功能 function setupKeyboardShortcut() { document.addEventListener('keydown', function (event) { // 检测 Alt+Command+Backspace 组合键 (macOS) // 或 Alt+Win+Backspace (Windows) if (event.altKey && event.metaKey && event.key === 'Backspace') { event.preventDefault(); // 阻止默认行为 handleDelete(); // 显示快捷键触发提示 notification('通过快捷键触发删除操作'); } }); } // 为对话项删除按钮委托事件 function addEventListenerForConversationItem() { document.addEventListener('click', function (event) { const target = event.target; const { mode, conversation_item_selector, getConversationItem, getConversationIdElement, getConversationIdFormItem } = getConfig(); if (matches(target, '.x-conversation-item-remove')) { // 1. 能从对话项中获取到对话 ID 的网站 if (mode === MODE.ID_UI) { event.stopPropagation(); event.preventDefault(); const conversationItem = parent(target, conversation_item_selector); deleteForIdUi(conversationItem); return; } else if (mode === MODE.ID_API) { event.stopPropagation(); event.preventDefault(); let conversationItem = null; if (typeof getConversationItem === 'function') { conversationItem = getConversationItem(event); } else if (typeof conversation_item_selector === 'string') { conversationItem = parent(target, conversation_item_selector); } const conversationIdElement = getConversationIdElement(conversationItem); const conversationId = getConversationIdFormItem(conversationIdElement); deleteForIdApi(conversationId); return; } // 2. 不能从对话项中获取对话 ID 的网站,只能将事件先冒泡,进入对话项之后,再调用通过 URL 删除逻辑 setTimeout(() => { handleDelete(); }, 1000); } }); } function main() { createStyle(); createElement(); setupKeyboardShortcut(); addEventListenerForConversationItem(); } main(); // 添加通知 function notification(message, { type = 'success', duration = 5000 } = {}) { const notification = document.createElement('div'); notification.textContent = message; notification.className = `x-conversation-delete-notification ${type}`; // 移除旧提示 const existing = document.querySelector('.x-conversation-delete-notification'); if (existing) existing.remove(); document.body.appendChild(notification); setTimeout(() => notification.remove(), duration); } // 设置按钮为加载状态 function setButtonLoading(loading) { if (!deleteButton) return; isDeleting = loading; if (loading) { deleteButton.classList.add('loading'); deleteButton.textContent = '正在删除...'; deleteButton.disabled = true; } else { deleteButton.classList.remove('loading'); deleteButton.textContent = 'Delete'; deleteButton.disabled = false; } } function createStyle() { // 初始化 GM_addStyle(` .x-conversation-action-wrap { position: fixed; bottom: 18px; right: 18px; z-index: 99999; display: flex; gap: 8px; align-items: center; } .x-conversation-action { padding: 4px 8px; color: white; border: none; border-radius: 4px; font-size: 12px; cursor: pointer; transition: all 0.3s ease; } .x-conversation-inspect { background:rgb(255, 163, 24); } .x-conversation-inspect:hover { background: #ff9800; } .x-conversation-delete { background: #ff4444; color: white; } .x-conversation-delete:disabled { cursor: not-allowed; opacity: 0.7; } .x-conversation-delete.loading { background: #999; position: relative; padding-right: 24px; /* 为加载图标留出空间 */ } .x-conversation-delete.loading::after { content: ""; position: absolute; width: 10px; height: 10px; top: 50%; right: 8px; margin-top: -5px; border: 2px solid rgba(255, 255, 255, 0.5); border-top-color: white; border-radius: 50%; animation: loader-spin 1s linear infinite; } .x-conversation-delete-notification { position: fixed; bottom: 62px; right: 18px; padding: 4px 8px; border-radius: 4px; color: white; font-family: system-ui; font-size: 12px; animation: slideIn 0.3s; z-index: 99999; } .x-conversation-item-remove { position: absolute; left: 100%; transform: translateX(-60px) translateY(-50%); top: 50%; display: block; width: 18px; height: 18px; line-height: 18px; cursor: pointer; } .x-conversation-item-remove.hidden { display: none; } .x-conversation-delete-notification.success { background: #4CAF50; } .x-conversation-delete-notification.error { background: #ff4444; } .x-conversation-delete-notification.warning { background: #ff9800; } @keyframes slideIn { from { transform: translateX(100%); } to { transform: translateX(0); } } @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } @keyframes loader-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } `); } function getRemoveIcon() { const removeIcon = `<?xml version="1.0" encoding="UTF-8"?><svg width="16" height="16" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M18.4237 10.5379C18.794 10.1922 19.2817 10 19.7883 10H42C43.1046 10 44 10.8954 44 12V36C44 37.1046 43.1046 38 42 38H19.7883C19.2817 38 18.794 37.8078 18.4237 37.4621L4 24L18.4237 10.5379Z" fill="#d0021b" stroke="#d0021b" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M36 19L26 29" stroke="#FFF" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M26 19L36 29" stroke="#FFF" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>`; return removeIcon; } // 获取元素的绝对路径作为 CSS 选择器,用于 DevTools function getElementPath(element) { if (!element || element.nodeType !== Node.ELEMENT_NODE) { return ''; } const path = []; while (element && element.nodeType === Node.ELEMENT_NODE) { let selector = element.tagName.toLowerCase(); const parent = element.parentElement; if (parent) { const siblings = Array.from(parent.children); const index = siblings.indexOf(element) + 1; selector += `:nth-child(${index})`; } path.unshift(selector); element = parent; } return path.join(' > '); } /** * 判断当前元素及父元素是否匹配给定的 CSS 选择器 * @param {Element} currentElement 当前元素 * @param {string} selector CSS 选择器 * @returns {boolean} 是否匹配 */ function matches(currentElement, selector) { while (currentElement !== null && currentElement !== document.body) { if (currentElement.matches(selector)) { return true; } currentElement = currentElement.parentElement; } // 检查 body 元素 return document.body.matches(selector); } /** * 获取当前元素的父元素,直到找到匹配给定 CSS 选择器的元素 * @param {Element} currentElement 当前元素 * @param {string} selector CSS 选择器 * @returns {Element|null} 匹配的父元素或 null */ function parent(currentElement, selector) { for (; currentElement && currentElement !== document; currentElement = currentElement.parentNode) { if (currentElement.matches(selector)) return currentElement; } return null; } })();