Grok Easy Delete Chat

Delete Grok chat with only Cmd/Ctrl+Shift+Delete, auto-confirms popup

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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();
})();