您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a "Select Chats" button to ChatGPT for deleting multiple conversations at once. Bypasses the UI and uses direct API calls for speed and reliability.
// ==UserScript== // @name ChatGPT Bulk Deleter // @namespace http://tampermonkey.net/ // @version 2.0.0 // @description Adds a "Select Chats" button to ChatGPT for deleting multiple conversations at once. Bypasses the UI and uses direct API calls for speed and reliability. // @author @SavitarStorm @Tano // @match https://chatgpt.com/* // @connect chatgpt.com // @grant GM_addStyle // @grant GM_xmlhttpRequest // @license MIT // ==/UserScript== (function() { 'use strict'; GM_addStyle(` .bulk-delete-controls { padding: 8px; display: flex; flex-direction: column; gap: 8px; width: 100%; border-bottom: 1px solid var(--token-border-light); } .bulk-delete-btn { display: inline-block; width: 100%; padding: 10px 12px; border: none; border-radius: 8px; cursor: pointer; text-align: center; font-size: 14px; font-weight: 500; transition: background-color 0.2s, color 0.2s; } #toggle-select-btn { background-color: #4C50D3; color: white; } #toggle-select-btn:hover { background-color: #3a3eab; } #toggle-select-btn.selection-active { background-color: #FFD6D6; color: #D34C4C; } #delete-selected-btn { background-color: #D34C4C; color: white; display: none; } #delete-selected-btn:hover { background-color: #b03a3a; } #delete-selected-btn:disabled { background-color: #7c7c7c; cursor: not-allowed; } .chat-selectable { cursor: cell !important; } a.chat-selected { background-color: rgba(76, 80, 211, 0.25) !important; outline: 2px solid #4C50D3 !important; border-radius: 8px; } `); let selectionMode = false; const selectedChats = new Set(); let authToken = null; async function getAuthToken() { if (authToken) return authToken; try { const response = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: "https://chatgpt.com/api/auth/session", onload: resolve, onerror: reject }); }); const data = JSON.parse(response.responseText); if (data && data.accessToken) { authToken = data.accessToken; return authToken; } throw new Error("accessToken not found in session response."); } catch (error) { console.error("Failed to retrieve auth token:", error); alert("Could not retrieve authorization token. The script cannot continue."); return null; } } function initialize() { const headerDiv = document.querySelector('#sidebar-header'); if (!headerDiv || document.getElementById('toggle-select-btn')) { return; } const targetContainer = headerDiv.parentElement; if (!targetContainer) { return; } getAuthToken(); const controlsContainer = document.createElement('div'); controlsContainer.className = 'bulk-delete-controls'; const toggleBtn = document.createElement('button'); toggleBtn.id = 'toggle-select-btn'; toggleBtn.className = 'bulk-delete-btn'; toggleBtn.textContent = 'Select Chats to Delete'; toggleBtn.onclick = toggleSelectionMode; const deleteBtn = document.createElement('button'); deleteBtn.id = 'delete-selected-btn'; deleteBtn.className = 'bulk-delete-btn'; deleteBtn.textContent = 'Delete Selected (0)'; deleteBtn.onclick = deleteSelectedChats; controlsContainer.appendChild(toggleBtn); controlsContainer.appendChild(deleteBtn); targetContainer.appendChild(controlsContainer); } function toggleSelectionMode() { selectionMode = !selectionMode; const toggleBtn = document.getElementById('toggle-select-btn'); const deleteBtn = document.getElementById('delete-selected-btn'); const chatItems = document.querySelectorAll('div#history a[href^="/c/"], div[role="presentation"] nav a[href^="/c/"]'); if (selectionMode) { toggleBtn.textContent = 'Cancel Selection'; toggleBtn.classList.add('selection-active'); deleteBtn.style.display = 'block'; chatItems.forEach(chat => { chat.classList.add('chat-selectable'); chat.addEventListener('click', handleChatClick, true); }); } else { toggleBtn.textContent = 'Select Chats to Delete'; toggleBtn.classList.remove('selection-active'); deleteBtn.style.display = 'none'; chatItems.forEach(chat => { chat.classList.remove('chat-selectable', 'chat-selected'); chat.removeEventListener('click', handleChatClick, true); }); selectedChats.clear(); updateDeleteButton(); } } function handleChatClick(event) { event.preventDefault(); event.stopPropagation(); const chatElement = event.currentTarget; if (selectedChats.has(chatElement)) { selectedChats.delete(chatElement); chatElement.classList.remove('chat-selected'); } else { selectedChats.add(chatElement); chatElement.classList.add('chat-selected'); } updateDeleteButton(); } function updateDeleteButton() { const deleteBtn = document.getElementById('delete-selected-btn'); if(deleteBtn) { deleteBtn.textContent = `Delete Selected (${selectedChats.size})`; deleteBtn.disabled = selectedChats.size === 0; } } async function deleteSelectedChats() { if (selectedChats.size === 0) return; const token = await getAuthToken(); if (!token) return; if (!confirm(`Are you sure you want to delete ${selectedChats.size} chat(s)? This action is irreversible.`)) return; const chatsToDelete = Array.from(selectedChats); const total = chatsToDelete.length; const deleteBtn = document.getElementById('delete-selected-btn'); const toggleBtn = document.getElementById('toggle-select-btn'); deleteBtn.disabled = true; toggleBtn.disabled = true; deleteBtn.textContent = `Deleting ${total} chats... Please wait.`; const promises = chatsToDelete.map(chatElement => { const href = chatElement.getAttribute('href'); const conversationId = href.split('/').pop(); const chatTitle = chatElement.textContent.trim(); return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "PATCH", url: `https://chatgpt.com/backend-api/conversation/${conversationId}`, headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` }, data: JSON.stringify({ is_visible: false }), onload: (response) => { if (response.status >= 200 && response.status < 300) { resolve({ chatElement }); } else { reject({ chatElement, chatTitle, error: new Error(`Server responded with status ${response.status}`) }); } }, onerror: (error) => reject({ chatElement, chatTitle, error }) }); }); }); const results = await Promise.allSettled(promises); let successCount = 0; let errorCount = 0; results.forEach(result => { if (result.status === 'fulfilled') { const { chatElement } = result.value; chatElement.style.transition = 'opacity 0.5s'; chatElement.style.opacity = '0'; setTimeout(() => chatElement.remove(), 500); successCount++; } else { const { chatElement, chatTitle, error } = result.reason; console.error(` -> [FAIL] API call failed for "${chatTitle}":`, error); chatElement.style.outline = '2px solid red'; errorCount++; } }); alert(`Complete. Successfully deleted: ${successCount}. Errors: ${errorCount}.`); deleteBtn.disabled = false; toggleBtn.disabled = false; toggleSelectionMode(); } const observer = new MutationObserver(() => { if (document.querySelector('#sidebar-header') && !document.getElementById('toggle-select-btn')) { initialize(); } }); observer.observe(document.body, { childList: true, subtree: true }); })();