The ultimate tool for deleting ChatGPT conversations. Features a premium UI with enhanced shadows, icons, and a selection cursor. No pop-ups.
// ==UserScript==
// @name ChatGPT Bulk Deleter ✨
// @namespace http://tampermonkey.net/
// @version 6.0.0
// @description The ultimate tool for deleting ChatGPT conversations. Features a premium UI with enhanced shadows, icons, and a selection cursor. No pop-ups.
// @author @SavitarStorm @Tano (Deluxe Edition by Gemini)
// @match https://chatgpt.com/*
// @connect chatgpt.com
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @grant GM_notification
// @license MIT
// ==/UserScript==
(function() {
'use strict';
GM_addStyle(`
/* Container fits perfectly into sidebar */
.bulk-delete-controls {
padding: 8px 12px;
display: flex;
flex-direction: column;
gap: 8px;
border-bottom: 1px solid rgba(255,255,255,0.1);
margin-bottom: 4px;
background: transparent;
}
/* Stealth Buttons (Matches ChatGPT UI) */
.stealth-btn {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 8px 12px;
border: 1px solid rgba(255,255,255,0.1);
border-radius: 6px;
cursor: pointer;
font-size: 13px;
font-weight: 400;
color: #ececf1;
background: transparent; /* Seamless blend */
transition: background 0.2s ease;
}
.stealth-btn:hover { background: rgba(255,255,255,0.08); }
.stealth-btn:active { opacity: 0.8; }
.stealth-btn:disabled { opacity: 0.4; cursor: not-allowed; }
/* Delete Button (Subtle Red) */
.danger-btn {
border-color: rgba(185, 28, 28, 0.5);
color: #fca5a5;
background: rgba(50, 20, 20, 0.3);
}
.danger-btn:hover { background: rgba(127, 29, 29, 0.4); }
/* Active State */
.active-mode {
background: rgba(255,255,255,0.15);
color: #fff;
border-color: rgba(255,255,255,0.3);
}
/* Search Input */
.search-wrapper {
position: relative;
display: none; /* Hidden by default */
}
.stealth-input {
width: 100%;
padding: 6px 10px 6px 28px;
border-radius: 5px;
border: 1px solid rgba(255,255,255,0.1);
background: rgba(0,0,0,0.3);
color: #fff;
font-size: 13px;
outline: none;
}
.stealth-input:focus { border-color: rgba(255,255,255,0.3); }
.search-icon {
position: absolute;
left: 8px;
top: 50%;
transform: translateY(-50%);
font-size: 12px;
opacity: 0.5;
}
/* Action Row */
.action-row { display: flex; gap: 6px; display: none; }
.action-row > button { flex: 1; }
/* --- INTERACTION & ANIMATION --- */
/* 1. Selection Cursor (Plus) */
.chat-selectable { cursor: cell !important; }
/* 2. Selected Highlight */
a.chat-selected {
background: rgba(255, 255, 255, 0.08) !important;
border-left: 3px solid #ef4444 !important; /* Red selection marker */
}
/* 3. Fire Animation */
@keyframes burnEffect {
0% { filter: brightness(1); transform: scale(1); }
30% { filter: brightness(2) sepia(1) hue-rotate(-10deg); transform: scale(1.02); }
100% { filter: grayscale(1) brightness(0); opacity: 0; transform: scale(0.9) translateX(-10px); }
}
.burning {
animation: burnEffect 0.5s forwards ease-in-out;
pointer-events: none;
}
`);
let selectionMode = false;
const selectedChats = new Set();
let authToken = null;
// --- Auth ---
async function getAuthToken() {
if (authToken) return authToken;
try {
const response = await fetch("https://chatgpt.com/api/auth/session");
if (!response.ok) throw new Error("Auth Failed");
const data = await response.json();
authToken = data.accessToken;
return authToken;
} catch (e) {
console.error("Auth Error", e);
return null;
}
}
// --- Init ---
function initialize() {
const headerDiv = document.querySelector('#sidebar-header');
if (!headerDiv || document.getElementById('toggle-select-btn')) return;
const controls = document.createElement('div');
controls.className = 'bulk-delete-controls';
// 1. Toggle
const toggleBtn = document.createElement('button');
toggleBtn.id = 'toggle-select-btn';
toggleBtn.className = 'stealth-btn';
toggleBtn.textContent = 'Select Chats';
toggleBtn.onclick = toggleSelectionMode;
// 2. Search
const searchDiv = document.createElement('div');
searchDiv.className = 'search-wrapper';
searchDiv.innerHTML = '<span class="search-icon">🔍</span>';
const searchInput = document.createElement('input');
searchInput.className = 'stealth-input';
searchInput.placeholder = 'Search...';
searchInput.oninput = handleSearch;
searchDiv.appendChild(searchInput);
// 3. Actions
const row = document.createElement('div');
row.className = 'action-row';
const allBtn = document.createElement('button');
allBtn.className = 'stealth-btn';
allBtn.textContent = 'All';
allBtn.onclick = selectAllChats;
const noneBtn = document.createElement('button');
noneBtn.className = 'stealth-btn';
noneBtn.textContent = 'None';
noneBtn.onclick = deselectAllChats;
row.append(allBtn, noneBtn);
// 4. Delete
const delBtn = document.createElement('button');
delBtn.id = 'del-btn';
delBtn.className = 'stealth-btn danger-btn';
delBtn.innerHTML = '🔥 Delete';
delBtn.style.display = 'none';
delBtn.disabled = true;
delBtn.onclick = deleteSelectedChats;
controls.append(toggleBtn, searchDiv, row, delBtn);
headerDiv.parentElement.appendChild(controls);
}
// --- Logic ---
function toggleSelectionMode() {
selectionMode = !selectionMode;
const toggleBtn = document.getElementById('toggle-select-btn');
const hiddenEls = [
document.querySelector('.search-wrapper'),
document.querySelector('.action-row'),
document.getElementById('del-btn')
];
const chatItems = document.querySelectorAll('a[href^="/c/"]');
if (selectionMode) {
toggleBtn.textContent = 'Cancel';
toggleBtn.classList.add('active-mode');
hiddenEls.forEach(el => el.style.display = el.classList.contains('action-row') ? 'flex' : 'block');
chatItems.forEach(chat => {
chat.classList.add('chat-selectable');
chat.addEventListener('click', handleChatClick, true);
});
} else {
toggleBtn.textContent = 'Select Chats';
toggleBtn.classList.remove('active-mode');
hiddenEls.forEach(el => el.style.display = 'none');
// Reset search
document.querySelector('.stealth-input').value = '';
handleSearch({target: {value: ''}});
chatItems.forEach(chat => {
chat.classList.remove('chat-selectable', 'chat-selected');
chat.removeEventListener('click', handleChatClick, true);
});
selectedChats.clear();
updateDelBtn();
}
}
function handleSearch(e) {
const q = e.target.value.toLowerCase();
document.querySelectorAll('a[href^="/c/"]').forEach(chat => {
chat.style.display = chat.textContent.toLowerCase().includes(q) ? 'flex' : 'none';
});
}
function handleChatClick(e) {
e.preventDefault();
e.stopPropagation();
const el = e.currentTarget;
if (selectedChats.has(el)) {
selectedChats.delete(el);
el.classList.remove('chat-selected');
} else {
selectedChats.add(el);
el.classList.add('chat-selected');
}
updateDelBtn();
}
function updateDelBtn(text) {
const btn = document.getElementById('del-btn');
if (btn) {
btn.innerHTML = text ? text : `🔥 Delete (${selectedChats.size})`;
btn.disabled = selectedChats.size === 0;
}
}
const selectAllChats = () => {
document.querySelectorAll('a[href^="/c/"]').forEach(chat => {
if (chat.style.display !== 'none') {
selectedChats.add(chat);
chat.classList.add('chat-selected');
}
});
updateDelBtn();
};
const deselectAllChats = () => {
selectedChats.forEach(c => c.classList.remove('chat-selected'));
selectedChats.clear();
updateDelBtn();
};
// --- Core: Turbo Delete ---
async function deleteSelectedChats() {
if (selectedChats.size === 0) return;
// Native confirmation only for large batches to prevent accidents
if (selectedChats.size > 10 && !confirm(`Permanently delete ${selectedChats.size} chats?`)) return;
const token = await getAuthToken();
if (!token) return;
const chats = Array.from(selectedChats);
const delBtn = document.getElementById('del-btn');
const toggleBtn = document.getElementById('toggle-select-btn');
delBtn.disabled = true;
toggleBtn.disabled = true;
let done = 0;
const total = chats.length;
const queue = [...chats];
const active = new Set();
const MAX_ASYNC = 5;
const updateUI = () => updateDelBtn(`🔥 ${done}/${total}`);
async function runner() {
while (queue.length > 0 || active.size > 0) {
while (active.size < MAX_ASYNC && queue.length > 0) {
const el = queue.shift();
const id = el.getAttribute('href').split('/').pop().split('?')[0];
const p = fetch(`https://chatgpt.com/backend-api/conversation/${id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
body: JSON.stringify({ is_visible: false })
})
.then(res => {
if (!res.ok) throw new Error();
done++;
el.classList.add('burning');
setTimeout(() => { el.style.display = 'none'; el.remove(); }, 500);
})
.catch(() => {})
.finally(() => {
active.delete(p);
updateUI();
});
active.add(p);
}
if (active.size > 0) await Promise.race(active);
else break;
}
}
await runner();
toggleBtn.disabled = false;
toggleSelectionMode(); // Exit clean
}
// --- Observer ---
const observer = new MutationObserver(() => {
if (document.querySelector('#sidebar-header') && !document.getElementById('toggle-select-btn')) {
initialize();
}
});
observer.observe(document.body, { childList: true, subtree: true });
})();