您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Delete Grok chat with only Cmd/Ctrl+Shift+Delete, auto-confirms popup
// ==UserScript== // @name Grok Easy Delete Chat // @namespace nisc // @version 2025.06.08-A // @description Delete Grok chat with only Cmd/Ctrl+Shift+Delete, auto-confirms popup // @homepageURL https://github.com/nisc/grok-userscripts/ // @author nisc // @match https://grok.com/* // @icon https://grok.com/favicon.ico // @run-at document-end // @grant none // ==/UserScript== (function() { 'use strict'; // Configuration object containing all adjustable settings and constants const CONFIG = { platform: { isMac: navigator.userAgent.includes('Mac') }, shortcut: { key: navigator.userAgent.includes('Mac') ? 'Backspace' : 'Delete', modifier: navigator.userAgent.includes('Mac') ? 'metaKey' : 'ctrlKey', shift: true }, // Timing delays (in ms) for various operations delays: { RETRY: 200, // Delay between retries when finding chat element HOVER: 75, // Delay after simulating hover events KEY_PRESS: 75, // Delay between key press events MENU_OPEN: 125, // Delay for menu to open after clicking history CLICK_TO_DELETE: 125, // Delay between click and delete command DELETE_TO_ENTER: 125, // Delay between delete and enter press FINAL_ESCAPE: 250, // Final delay before pressing escape }, // Key codes for special keys keyCodes: { Enter: { code: 'Enter', keyCode: 13 }, Escape: { code: 'Escape', keyCode: 27 } }, // DOM selectors used throughout the script selectors: { historyButton: 'button[aria-label="History"]', dialog: '[role="dialog"]', clickableArea: '.cursor-pointer', cmdkItem: '[cmdk-item]', queryBar: '.query-bar', privateClasses: ['bg-purple-50', 'dark:bg-purple-1000'] }, // Mouse event simulation coordinates mouse: { clientX: 100, clientY: 100 }, // Maximum retries for finding elements maxRetries: 5, // Text constants text: { current: 'Current' } }; /** * Utility functions for common operations */ const utils = { delay: ms => new Promise(resolve => setTimeout(resolve, ms)), querySelector: (selector, context = document) => context.querySelector(selector), querySelectorAll: (selector, context = document) => Array.from(context.querySelectorAll(selector)), hasClass: (element, className) => { if (!element) return false; // Check the element itself if (element.classList.contains(className)) return true; // Check parent elements let parent = element; while (parent) { if (parent.classList && parent.classList.contains(className)) { return true; } parent = parent.parentElement; } return false; } }; /** * DOM Cache for frequently accessed elements */ const domCache = { _queryBar: null, get queryBar() { if (!this._queryBar) { this._queryBar = utils.querySelector(CONFIG.selectors.queryBar); } return this._queryBar; }, clearCache() { this._queryBar = null; } }; /** * Event simulation utilities */ const eventSimulator = { createMouseEvent(eventType) { return new MouseEvent(eventType, { bubbles: true, cancelable: true, view: window, clientX: CONFIG.mouse.clientX, clientY: CONFIG.mouse.clientY }); }, createKeyEvent(key, options = {}, type = 'keydown') { return new KeyboardEvent(type, { key, code: this._getKeyCode(key), bubbles: true, cancelable: true, keyCode: this._getKeyCodeNumber(key), ...options }); }, _getKeyCode(key) { return CONFIG.keyCodes[key]?.code || `Key${key.toUpperCase()}`; }, _getKeyCodeNumber(key) { return CONFIG.keyCodes[key]?.keyCode || key.toUpperCase().charCodeAt(0); }, async simulateMouseEvent(element, eventType) { element.dispatchEvent(this.createMouseEvent(eventType)); }, async simulateHover(element) { const parent = element.parentElement; if (parent) { ['mouseover', 'mouseenter'].forEach(event => this.simulateMouseEvent(parent, event)); await utils.delay(CONFIG.delays.HOVER); } ['mouseover', 'mouseenter'].forEach(event => this.simulateMouseEvent(element, event)); await utils.delay(CONFIG.delays.HOVER); element.classList.add('hover'); }, async simulateKeyPress(element, key, options = {}) { element.focus(); await utils.delay(CONFIG.delays.KEY_PRESS); ['keydown', 'keyup'].forEach(async (type) => { element.dispatchEvent(this.createKeyEvent(key, options, type)); document.dispatchEvent(this.createKeyEvent(key, options, type)); await utils.delay(CONFIG.delays.KEY_PRESS); }); } }; /** * Chat operations handling */ const chatOperations = { isValidInput(target) { return target.tagName === 'TEXTAREA' || (!['INPUT'].includes(target.tagName) && !target.isContentEditable); }, isPrivateChat() { const queryBar = domCache.queryBar; if (!queryBar) return false; // Check for purple classes either directly on the query bar or in its parent containers return CONFIG.selectors.privateClasses.some(className => utils.hasClass(queryBar, className)); }, clickHistoryButton() { const button = utils.querySelector(CONFIG.selectors.historyButton); if (button) { button.click(); return true; } return false; }, findSelectedChat(retryCount = 0) { const findElement = () => { const dialog = utils.querySelector(CONFIG.selectors.dialog); if (!dialog) return null; const currentSpan = utils.querySelectorAll('span', dialog) .find(span => span.textContent.trim() === CONFIG.text.current); if (!currentSpan) return null; return currentSpan.closest(CONFIG.selectors.cmdkItem); }; return new Promise((resolve, reject) => { const element = findElement(); if (element) { resolve(element); } else if (retryCount < CONFIG.maxRetries) { setTimeout(() => this.findSelectedChat(retryCount + 1).then(resolve).catch(reject), CONFIG.delays.RETRY); } else { reject(new Error('Selected chat not found')); } }); }, simulatePrivateChatShortcut() { return eventSimulator.simulateKeyPress(document.body, 'j', { metaKey: CONFIG.platform.isMac, ctrlKey: !CONFIG.platform.isMac, shiftKey: true }); }, async triggerDeleteSequence() { try { const currentChat = await this.findSelectedChat(); const clickableArea = currentChat.querySelector(CONFIG.selectors.clickableArea); if (!clickableArea) throw new Error('Clickable area not found'); await eventSimulator.simulateHover(currentChat); await eventSimulator.simulateHover(clickableArea); ['mousedown', 'click'].forEach(event => eventSimulator.simulateMouseEvent(clickableArea, event)); await utils.delay(CONFIG.delays.CLICK_TO_DELETE); await eventSimulator.simulateKeyPress(clickableArea, 'd', { metaKey: CONFIG.platform.isMac, shiftKey: true }); await utils.delay(CONFIG.delays.DELETE_TO_ENTER); await eventSimulator.simulateKeyPress(clickableArea, 'Enter'); await utils.delay(CONFIG.delays.FINAL_ESCAPE); await eventSimulator.simulateKeyPress(document.body, 'Escape'); return true; } catch { return false; } } }; /** * Main keyboard event handler */ const handleKeyDown = (e) => { if (!chatOperations.isValidInput(document.activeElement)) return; if (e.key === CONFIG.shortcut.key && e[CONFIG.shortcut.modifier] && e.shiftKey && !e.altKey) { e.preventDefault(); e.stopPropagation(); if (chatOperations.isPrivateChat()) { chatOperations.simulatePrivateChatShortcut(); } else if (chatOperations.clickHistoryButton()) { setTimeout(chatOperations.triggerDeleteSequence.bind(chatOperations), CONFIG.delays.MENU_OPEN); } } }; /** * Initialize the script */ const init = () => { document.addEventListener('keydown', handleKeyDown); window.addEventListener('popstate', () => domCache.clearCache()); }; init(); })();