Grok Easy Delete Chat

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

Ekde 2025/06/07. Vidu La ĝisdata versio.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Grok Easy Delete Chat
// @namespace    nisc
// @version      2025.06.07-A
// @description  Delete Grok chat with only Cmd/Ctrl+Shift+Delete, auto-confirms popup.
// @homepageURL  https://raw.githubusercontent.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();
})();