Grok Auto-Regenerate

Auto-regenerate with a persistent prompt

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Grok Auto-Regenerate
// @namespace    http://tampermonkey.net/
// @version      4.4
// @description  Auto-regenerate with a persistent prompt
// @icon         https://www.google.com/s2/favicons?sz=64&domain=grok.com
// @author       bigelelya
// @license GNU GPLv3
// @match        *://grok.com/*
// @grant        GM_addStyle
// @run-at       document-idle
// ==/UserScript==

(function () {
  'use strict';

  let isAutoRegenEnabled = true;
  let isCurrentlyAttemptingRegen = false;
  let lastRegenTime = 0;
  let retryCount = 0;
  const REGEN_COOLDOWN_MS = 5000;

  let dock, header, statusChip, statusDot, statusText, retryCountRef, switchBtn, progressWrap, progressFill, progressLabel, promptInputRef;

  // Selectors
  const popupSelector = 'body > section > ol > li > div > span';
  const popupText = 'Content Moderated. Try a different idea.';
  const grokInputSelector = 'textarea[aria-label="Make a video"]';
  const regenerateButtonSelector = 'button[aria-label="Make video"]';

  console.log('[Grok Auto-Regen] Loaded: v4.4');

  function injectPrompt() {
    const grokInput = document.querySelector(grokInputSelector);
    const myPrompt = promptInputRef ? promptInputRef.value : '';

    if (grokInput && myPrompt) {
      console.log('[Grok Auto-Regen] Injecting custom prompt...');

      const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
      nativeInputValueSetter.call(grokInput, myPrompt);

      const inputEvent = new Event('input', { bubbles: true });
      grokInput.dispatchEvent(inputEvent);
    }
  }


  function getCurrentGenerationProgress() {
    const candidateDivs = document.querySelectorAll('div.font-semibold');
    const percentageRegex = /^(\d{1,3})%$/;
    for (const div of candidateDivs) {
      const match = div.textContent.trim().match(percentageRegex);
      if (match) return Math.min(100, Math.max(0, parseInt(match[1], 10)));
    }
    return null;
  }

  function updateProgressBar() {
    if (!progressWrap) return;
    const progress = getCurrentGenerationProgress();
    const now = Date.now();

    if (progress !== null) {
      // Active State
      progressWrap.classList.add('active');
      progressFill.style.width = `${progress}%`;
      progressLabel.textContent = `${progress}%`;

      statusText.textContent = 'Generating';
      statusChip.dataset.state = 'active';
      isCurrentlyAttemptingRegen = true;

    } else {
      // Idle State
      progressWrap.classList.remove('active');
      progressFill.style.width = `0%`;
      progressLabel.textContent = `Ready`;

      // Safe reset logic
      if (isCurrentlyAttemptingRegen && (now - lastRegenTime > REGEN_COOLDOWN_MS)) {
        isCurrentlyAttemptingRegen = false;
        statusText.textContent = 'Standby';
        statusChip.dataset.state = 'idle';
      }
    }
  }

  function updateRetryUI() {
      if(retryCountRef) {
          retryCountRef.innerText = retryCount;
          retryCountRef.parentElement.animate([
              { transform: 'scale(1)' },
              { transform: 'scale(1.2)' },
              { transform: 'scale(1)' }
          ], { duration: 200 });
      }
  }


  const observerCallback = function () {
    if (!isAutoRegenEnabled) return;
    if (Date.now() - lastRegenTime < REGEN_COOLDOWN_MS) return;

    const potentialPopup = document.querySelector(popupSelector);
    if (potentialPopup && potentialPopup.textContent.trim() === popupText) {
      const btn = document.querySelector(regenerateButtonSelector);

      if (btn) {
        console.log('[Grok Auto-Regen] Moderation detected. Initiating Injection & Regen...');
        lastRegenTime = Date.now();
        isCurrentlyAttemptingRegen = true;

        // INCREMENT RETRY COUNT
        retryCount++;
        updateRetryUI();

        statusText.textContent = 'Injecting...';
        statusChip.dataset.state = 'active';

        injectPrompt();

        setTimeout(() => {
             statusText.textContent = 'Retrying...';
             btn.click();
        }, 500);
      }
    }
  };

  // UI
  function createModernUI() {
    dock = document.createElement('div');
    dock.id = 'grok-glass-dock';

    // Header
    header = document.createElement('div');
    header.className = 'glass-header';
    header.innerHTML = `
      <div class="glass-title-group">
        <div class="glass-icon">
          <img src="https://www.google.com/s2/favicons?sz=64&domain=grok.com" alt="Grok Logo" draggable="false" />
        </div>
        <span class="glass-title">Auto-Regen</span>
      </div>
      <div class="glass-controls">
        <button id="grok-switch" class="modern-switch on" role="switch">
          <div class="switch-handle">
            <div class="switch-glow"></div>
          </div>
        </button>
      </div>
      <div class="drag-grip">
        <span></span><span></span><span></span>
      </div>
    `;

    // Status Row
    const statusRow = document.createElement('div');
    statusRow.className = 'glass-info-row';

    // Status Chip
    statusChip = document.createElement('div');
    statusChip.className = 'glass-status';
    statusChip.dataset.state = 'idle';
    statusChip.innerHTML = `
      <div class="status-dot"></div>
      <span class="status-text">Standby</span>
    `;

    // Retry Counter
    const retryWrapper = document.createElement('div');
    retryWrapper.className = 'glass-counter';
    retryWrapper.innerHTML = `
      <span class="counter-label">RETRIES</span>
      <span class="counter-value" id="grok-retry-val">0</span>
    `;

    statusRow.appendChild(statusChip);
    statusRow.appendChild(retryWrapper);

    // Manual Prompt Input Area
    const inputArea = document.createElement('div');
    inputArea.className = 'glass-input-wrapper';
    inputArea.innerHTML = `
      <label class="input-label">Persistent Prompt</label>
      <textarea id="grok-manual-prompt" placeholder="Type prompt here. Script will use this for every regen."></textarea>
    `;

    // Progress Bar
    progressWrap = document.createElement('div');
    progressWrap.className = 'glass-progress-container';
    progressWrap.innerHTML = `
      <div class="glass-track">
        <div class="glass-fill" id="grok-fill">
          <div class="liquid-light"></div>
        </div>
      </div>
      <div class="progress-label" id="grok-label">Ready</div>
    `;

    dock.appendChild(header);
    dock.appendChild(statusRow);
    dock.appendChild(inputArea);
    dock.appendChild(progressWrap);
    document.body.appendChild(dock);

    // Ref Assignments
    switchBtn = dock.querySelector('#grok-switch');
    statusDot = dock.querySelector('.status-dot');
    statusText = dock.querySelector('.status-text');
    retryCountRef = dock.querySelector('#grok-retry-val');
    progressFill = dock.querySelector('#grok-fill');
    progressLabel = dock.querySelector('#grok-label');
    promptInputRef = dock.querySelector('#grok-manual-prompt');

    // Load saved prompt
    const savedPrompt = localStorage.getItem('grokAutoRegenPrompt');
    if (savedPrompt) {
      promptInputRef.value = savedPrompt;
    }

    // Event Listeners
    switchBtn.addEventListener('click', toggleSwitch);

    // Prompt Save
    promptInputRef.addEventListener('input', (e) => {
        localStorage.setItem('grokAutoRegenPrompt', e.target.value);
        // Reset retry count when user types new prompt
        retryCount = 0;
        updateRetryUI();
    });

    // Draggable Logic
    makeDraggable(dock, header);
    restorePosition(dock);
  }

  function toggleSwitch() {
    isAutoRegenEnabled = !isAutoRegenEnabled;
    switchBtn.classList.toggle('on', isAutoRegenEnabled);

    if (isAutoRegenEnabled) {
      statusText.textContent = 'Standby';
      statusChip.dataset.state = 'idle';
    } else {
      statusText.textContent = 'Disabled';
      statusChip.dataset.state = 'off';
    }
  }

  // Draggable Logic
  function makeDraggable(el, handle) {
    let isDown = false, startX, startY, startLeft, startTop;

    handle.addEventListener('mousedown', (e) => {
      if (e.target.closest('.modern-switch')) return;
      isDown = true;
      const rect = el.getBoundingClientRect();
      startX = e.clientX; startY = e.clientY;
      startLeft = rect.left; startTop = rect.top;
      el.classList.add('grabbing');
      document.addEventListener('mousemove', onMove);
      document.addEventListener('mouseup', onUp);
    });

    function onMove(e) {
      if (!isDown) return;
      const dx = e.clientX - startX;
      const dy = e.clientY - startY;
      el.style.left = `${startLeft + dx}px`;
      el.style.top = `${startTop + dy}px`;
      el.style.right = 'auto'; el.style.bottom = 'auto';
    }

    function onUp() {
      isDown = false;
      el.classList.remove('grabbing');
      savePosition(el);
      document.removeEventListener('mousemove', onMove);
      document.removeEventListener('mouseup', onUp);
    }
  }

  function savePosition(el) {
    const rect = el.getBoundingClientRect();
    localStorage.setItem('grokDockPos_v4', JSON.stringify({ left: rect.left, top: rect.top }));
  }

  function restorePosition(el) {
    const saved = localStorage.getItem('grokDockPos_v4');
    if (saved) {
      const pos = JSON.parse(saved);
      el.style.left = `${pos.left}px`;
      el.style.top = `${pos.top}px`;
      el.style.right = 'auto'; el.style.bottom = 'auto';
    }
  }

  // CSS
  GM_addStyle(`
    /* Fonts & Variables */
    @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@500&display=swap');

    :root {
      --glass-bg: rgba(20, 20, 23, 0.65);
      --glass-border: rgba(255, 255, 255, 0.08);
      --glass-highlight: rgba(255, 255, 255, 0.15);
      --glass-input-bg: rgba(0, 0, 0, 0.3);
      --accent-primary: #3b82f6;
      --accent-glow: rgba(59, 130, 246, 0.5);
      --text-main: #ffffff;
      --text-muted: rgba(255, 255, 255, 0.6);
      --ease-elastic: cubic-bezier(0.175, 0.885, 0.32, 1.275);
    }

    /* Main Dock */
    #grok-glass-dock {
      position: fixed;
      bottom: 30px;
      right: 30px;
      width: 300px;
      background: var(--glass-bg);
      backdrop-filter: blur(24px) saturate(180%);
      -webkit-backdrop-filter: blur(24px) saturate(180%);
      border-radius: 24px;
      border: 1px solid var(--glass-border);
      box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(0,0,0, 0.2);
      padding: 16px;
      font-family: 'Inter', sans-serif;
      color: var(--text-main);
      z-index: 999999;
      animation: slideUp FadeIn 0.6s var(--ease-elastic);
    }

    #grok-glass-dock::before {
      content: '';
      position: absolute;
      inset: 0;
      opacity: 0.07;
      pointer-events: none;
      background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");
    }

    /* Header */
    .glass-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-bottom: 16px;
      cursor: grab;
      position: relative;
    }

    .glass-title-group { display: flex; align-items: center; gap: 10px; }
    .glass-icon {
      width: 32px; height: 32px;
      background: linear-gradient(135deg, rgba(255,255,255,0.1), rgba(255,255,255,0.02));
      border: 1px solid var(--glass-border);
      border-radius: 10px;
      display: flex; align-items: center; justify-content: center;
    }
    .glass-icon img { width: 20px; height: 20px; object-fit: contain; }
    .glass-title { font-weight: 600; font-size: 14px; }

    /* Controls */
    .modern-switch {
      width: 48px; height: 28px;
      background: rgba(0,0,0,0.3);
      border: 1px solid var(--glass-border);
      border-radius: 99px;
      position: relative; cursor: pointer;
      transition: all 0.4s var(--ease-elastic);
    }
    .switch-handle {
      width: 20px; height: 20px;
      background: #fff; border-radius: 50%;
      position: absolute; top: 3px; left: 4px;
      transition: all 0.4s var(--ease-elastic);
    }
    .modern-switch.on { background: var(--accent-primary); }
    .modern-switch.on .switch-handle { transform: translateX(20px); }

    /* Status Row Flex */
    .glass-info-row {
      display: flex; gap: 8px; margin-bottom: 12px;
    }

    /* Status Chip */
    .glass-status {
      flex: 1;
      display: flex; align-items: center; gap: 8px;
      background: rgba(255,255,255,0.03);
      border: 1px solid var(--glass-border);
      padding: 8px 12px;
      border-radius: 12px;
    }
    .glass-status[data-state="active"] { background: rgba(59, 130, 246, 0.1); border-color: rgba(59, 130, 246, 0.2); }
    .status-dot { width: 8px; height: 8px; border-radius: 50%; background: #fbbf24; transition: all 0.3s ease; }
    .glass-status[data-state="active"] .status-dot { background: #4ade80; box-shadow: 0 0 0 4px rgba(74, 222, 128, 0.2); animation: pulse 2s infinite; }
    .glass-status[data-state="off"] .status-dot { background: #ef4444; }
    .status-text { font-size: 12px; font-weight: 500; color: var(--text-muted); text-transform: uppercase; white-space: nowrap; }

    /* Retry Counter Style */
    .glass-counter {
      display: flex; flex-direction: column; align-items: center; justify-content: center;
      background: rgba(0,0,0,0.4);
      border: 1px solid var(--glass-border);
      border-radius: 12px;
      min-width: 60px; padding: 0 8px;
    }
    .counter-label { font-size: 8px; color: var(--text-muted); font-weight: 700; letter-spacing: 0.05em; margin-bottom: 2px; }
    .counter-value { font-family: 'JetBrains Mono', monospace; font-size: 14px; font-weight: 600; color: #fff; }

    /* New Prompt Input Area */
    .glass-input-wrapper {
      margin-bottom: 16px;
    }
    .input-label {
      display: block;
      font-size: 11px;
      color: var(--text-muted);
      margin-bottom: 6px;
      font-weight: 600;
      letter-spacing: 0.02em;
    }
    #grok-manual-prompt {
      width: 100%;
      height: 60px;
      background: var(--glass-input-bg);
      border: 1px solid var(--glass-border);
      border-radius: 12px;
      padding: 10px;
      color: var(--text-main);
      font-family: 'Inter', sans-serif;
      font-size: 12px;
      resize: vertical;
      outline: none;
      transition: border-color 0.2s;
      box-sizing: border-box;
    }
    #grok-manual-prompt:focus {
      border-color: var(--accent-primary);
      background: rgba(0,0,0,0.5);
    }
    #grok-manual-prompt::placeholder {
      color: rgba(255,255,255,0.3);
    }

    /* Progress */
    .glass-progress-container { position: relative; }
    .glass-track { height: 6px; width: 100%; background: rgba(255,255,255,0.1); border-radius: 99px; overflow: hidden; }
    .glass-fill { height: 100%; width: 0%; background: var(--accent-primary); border-radius: 99px; position: relative; transition: width 0.6s var(--ease-elastic); }
    .liquid-light {
      position: absolute; inset: 0;
      background: linear-gradient(90deg, transparent, rgba(255,255,255,0.6), transparent);
      transform: translateX(-100%); animation: shimmer 1.5s infinite;
    }
    .progress-label {
      position: absolute; right: 0; top: -20px;
      font-size: 11px; color: var(--text-muted);
      opacity: 0; transform: translateY(5px); transition: all 0.3s ease;
    }
    .glass-progress-container.active .progress-label { opacity: 1; transform: translateY(0); }

    @keyframes slideUp { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
    @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(74, 222, 128, 0.4); } 70% { box-shadow: 0 0 0 6px rgba(74, 222, 128, 0); } 100% { box-shadow: 0 0 0 0 rgba(74, 222, 128, 0); } }
    @keyframes shimmer { 100% { transform: translateX(100%); } }
  `);

  // Init
  function initialize() {
    createModernUI();
    setInterval(updateProgressBar, 250);
    new MutationObserver(observerCallback).observe(document.body, { childList: true, subtree: true });
  }

  window.addEventListener('load', initialize);
})();