Fixed moderation detection with more robust selectors and flexible text matching
// ==UserScript==
// @name Grok Auto-Retry + Prompt Snippets + History + Favorites (v20.5 - Wait for Error Clear)
// @namespace http://tampermonkey.net/
// @version 27
// @description Fixed moderation detection with more robust selectors and flexible text matching
// @author You
// @license MIT
// @match https://grok.com/*
// @match https://*.grok.com/*
// @match https://grok.x.ai/*
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
(function() {
'use strict';
// --- CONFIGURATION ---
const TARGET_TEXTAREA_SELECTOR = 'textarea[aria-label="Make a video"]';
const IMAGE_EDITOR_SELECTOR = 'textarea[aria-label="Type to edit image..."]';
const IMAGE_PROMPT_SELECTOR = 'textarea[aria-label="Image prompt"]';
const IMAGE_IMAGINE_SELECTOR = 'p[data-placeholder="Type to imagine"]';
const RETRY_BUTTON_SELECTOR = 'button[aria-label="Make video"]';
const IMAGE_EDITOR_BUTTON_SELECTOR = 'button[aria-label="Generate"]';
const IMAGE_SUBMIT_BUTTON_SELECTOR = 'button[aria-label="Submit"]';
// Multiple possible moderation text patterns (case-insensitive)
const MODERATION_PATTERNS = [
"content moderated",
"try a different idea",
"moderated",
"content policy",
"cannot generate",
"unable to generate"
];
const RETRY_DELAY_MS = 1500;
const COOLDOWN_MS = 2500;
const OBSERVER_THROTTLE_MS = 300;
const MAX_HISTORY_ITEMS = 100;
const DEBUG_MODE = true; // Set to false to disable console logs
// --- DEFAULT SNIPPETS ---
const DEFAULT_SNIPPETS = [
{
id: 'b1', label: 'Anime Stickers (Provocative)',
text: 'Surrounding the central image: thick decorative border made of overlapping colorful anime-style stickers featuring nude anime girls with exaggerated proportions in various provocative poses. Each sticker has a white outline and slight drop shadow. The stickers completely frame all four edges of the image with some overlap into the main content.'
},
{
id: 'b2', label: 'Anime Stickers (SFW)',
text: 'Surrounding the central image: thick decorative border made of overlapping colorful anime-style stickers featuring anime girls with exaggerated proportions in various poses. Each sticker has a white outline and slight drop shadow. The stickers completely frame all four edges of the image with some overlap into the main content.'
},
{ id: '1', label: 'Motion: Slow Mo', text: 'slow motion, high frame rate, smooth movement' },
{ id: '2', label: 'Style: Photorealistic', text: 'photorealistic, 8k resolution, highly detailed, unreal engine 5 render' },
{ id: '3', label: 'Lighting: Golden Hour', text: 'golden hour lighting, warm sun rays, lens flare, soft shadows' },
];
// --- LOAD SAVED SETTINGS ---
let maxRetries = GM_getValue('maxRetries', 5);
let uiToggleKey = GM_getValue('uiToggleKey', 'h');
let autoClickEnabled = GM_getValue('autoClickEnabled', true);
let isUiVisible = GM_getValue('isUiVisible', true);
let savedSnippets = GM_getValue('savedSnippets', DEFAULT_SNIPPETS);
let videoPromptHistory = GM_getValue('videoPromptHistory', []);
let imagePromptHistory = GM_getValue('imagePromptHistory', []);
let videoFavorites = GM_getValue('videoFavorites', []);
let imageFavorites = GM_getValue('imageFavorites', []);
let panelSize = GM_getValue('panelSize', { width: '300px', height: '400px' });
let isRetryEnabled = true;
let limitReached = false;
let currentRetryCount = 0;
let lastTypedPrompt = "";
let lastRetryTimestamp = 0;
let lastGenerationTimestamp = 0;
const GENERATION_COOLDOWN_MS = 3000;
let observerThrottle = false;
let moderationDetected = false;
let processingModeration = false;
let currentHistoryTab = 'video';
let currentFavoritesTab = 'video';
let currentEditingFavId = null;
let lastModerationCheck = 0;
let errorWaitInterval = null; // Store reference to polling interval
// --- DEBUG LOGGER ---
function debugLog(...args) {
if (DEBUG_MODE) {
console.log('[Grok Auto-Retry]', ...args);
}
}
// --- STYLES ---
GM_addStyle(`
#grok-control-panel {
position: fixed; bottom: 20px; right: 20px;
width: ${panelSize.width}; height: ${panelSize.height};
min-width: 280px; min-height: 250px; max-width: 90vw; max-height: 90vh;
background: linear-gradient(145deg, #0a0a0a 0%, #1a1a1a 100%);
border: 1px solid #2a2a2a;
border-radius: 16px;
padding: 15px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
color: #e0e0e0;
z-index: 99998;
box-shadow: 0 8px 32px rgba(0,0,0,0.9), 0 0 0 1px rgba(255,255,255,0.05);
display: flex;
flex-direction: column;
gap: 10px;
}
#grok-control-panel.hidden { display: none; }
#grok-resize-handle {
position: absolute; top: 0; left: 0; width: 15px; height: 15px;
cursor: nwse-resize; z-index: 99999;
}
#grok-resize-handle::after {
content: ''; position: absolute; top: 2px; left: 2px;
border-top: 6px solid #3b82f6; border-right: 6px solid transparent;
width: 0; height: 0; opacity: 0.7;
}
#grok-resize-handle:hover::after { opacity: 1; border-top-color: #60a5fa; }
.grok-header {
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
margin-left: 10px;
padding-bottom: 8px;
border-bottom: 1px solid #2a2a2a;
}
.grok-title {
font-weight: bold;
font-size: 14px;
color: #f0f0f0;
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
}
.grok-toggle-btn {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
border: none;
color: white;
padding: 6px 14px;
border-radius: 20px;
font-size: 11px;
font-weight: bold;
cursor: pointer;
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.4);
transition: all 0.2s ease;
}
.grok-toggle-btn:hover {
background: linear-gradient(135deg, #059669 0%, #047857 100%);
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.6);
}
.grok-toggle-btn.off {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.4);
}
.grok-toggle-btn.off:hover {
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.6);
}
.grok-controls {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 12px;
color: #9ca3af;
flex-shrink: 0;
padding: 8px 0;
}
.grok-checkbox {
display: flex;
align-items: center;
cursor: pointer;
color: #d1d5db;
}
.grok-checkbox input {
margin-right: 6px;
cursor: pointer;
accent-color: #3b82f6;
}
.grok-num-input {
width: 40px;
background: #1f1f1f;
border: 1px solid #2a2a2a;
color: #e0e0e0;
border-radius: 6px;
padding: 4px 6px;
text-align: center;
transition: all 0.2s ease;
}
.grok-num-input:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
}
.grok-prompt-label {
font-size: 11px;
font-weight: bold;
color: #9ca3af;
margin-bottom: -5px;
flex-shrink: 0;
}
#grok-panel-prompt {
width: 100%;
flex-grow: 1;
background: #0f0f0f;
border: 1px solid #2a2a2a;
border-radius: 8px;
color: #e0e0e0;
padding: 10px;
font-size: 12px;
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
resize: none;
box-sizing: border-box;
transition: all 0.2s ease;
}
#grok-panel-prompt:focus {
border-color: #3b82f6;
outline: none;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}
#grok-panel-prompt::placeholder {
color: #4b5563;
}
.grok-btn-row {
display: flex;
gap: 8px;
flex-shrink: 0;
}
.grok-action-btn {
flex: 1;
padding: 10px;
border-radius: 8px;
border: none;
cursor: pointer;
font-weight: 600;
font-size: 12px;
transition: all 0.2s ease;
position: relative;
overflow: hidden;
}
.grok-action-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent);
transition: left 0.5s;
}
.grok-action-btn:hover::before {
left: 100%;
}
#btn-open-library {
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
color: white;
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
}
#btn-open-library:hover {
background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.5);
transform: translateY(-1px);
}
#btn-open-favorites {
background: linear-gradient(135deg, #ec4899 0%, #db2777 100%);
color: white;
box-shadow: 0 2px 8px rgba(236, 72, 153, 0.3);
}
#btn-open-favorites:hover {
background: linear-gradient(135deg, #db2777 0%, #be185d 100%);
box-shadow: 0 4px 12px rgba(236, 72, 153, 0.5);
transform: translateY(-1px);
}
#btn-open-history {
background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
color: white;
box-shadow: 0 2px 8px rgba(139, 92, 246, 0.3);
}
#btn-open-history:hover {
background: linear-gradient(135deg, #7c3aed 0%, #6d28d9 100%);
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.5);
transform: translateY(-1px);
}
#btn-generate {
background: linear-gradient(135deg, #1f1f1f 0%, #2a2a2a 100%);
color: #e0e0e0;
border: 1px solid #3a3a3a;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
#btn-generate:hover {
background: linear-gradient(135deg, #2a2a2a 0%, #353535 100%);
border-color: #4a4a4a;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
transform: translateY(-1px);
}
#grok-status {
text-align: center;
font-size: 11px;
color: #10b981;
padding-top: 8px;
border-top: 1px solid #2a2a2a;
flex-shrink: 0;
font-weight: 500;
}
.status-error {
color: #ef4444 !important;
}
.status-warning {
color: #f59e0b !important;
}
/* Modal Styles */
.grok-modal {
position: fixed;
right: 20px;
width: 350px;
height: 400px;
background: linear-gradient(145deg, #0a0a0a 0%, #1a1a1a 100%);
border: 1px solid #2a2a2a;
border-radius: 16px;
display: none;
flex-direction: column;
z-index: 99999;
box-shadow: 0 8px 32px rgba(0,0,0,0.9), 0 0 0 1px rgba(255,255,255,0.05);
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
.grok-modal.active { display: flex; }
.gl-header {
padding: 12px 15px;
background: linear-gradient(135deg, #1a1a1a 0%, #0f0f0f 100%);
display: flex;
justify-content: space-between;
align-items: center;
font-weight: bold;
font-size: 13px;
color: #f0f0f0;
border-bottom: 1px solid #2a2a2a;
border-radius: 16px 16px 0 0;
}
.gl-close {
cursor: pointer;
font-size: 20px;
line-height: 1;
color: #6b7280;
transition: all 0.2s ease;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
}
.gl-close:hover {
color: #f0f0f0;
background: #2a2a2a;
}
/* History Tab Styles */
.history-tabs {
display: flex;
background: #0f0f0f;
border-bottom: 1px solid #2a2a2a;
}
.history-tab {
flex: 1;
padding: 10px;
text-align: center;
cursor: pointer;
font-size: 11px;
font-weight: 600;
color: #6b7280;
transition: all 0.2s ease;
border-bottom: 2px solid transparent;
}
.history-tab:hover {
color: #9ca3af;
background: #1a1a1a;
}
.history-tab.active {
color: #8b5cf6;
border-bottom-color: #8b5cf6;
background: #1a1a1a;
}
.gl-view-list {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.gl-list-content {
overflow-y: auto;
padding: 12px;
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.gl-list-content::-webkit-scrollbar {
width: 8px;
}
.gl-list-content::-webkit-scrollbar-track {
background: #0f0f0f;
}
.gl-list-content::-webkit-scrollbar-thumb {
background: #2a2a2a;
border-radius: 4px;
}
.gl-list-content::-webkit-scrollbar-thumb:hover {
background: #3a3a3a;
}
.gl-item {
background: linear-gradient(135deg, #1a1a1a 0%, #151515 100%);
border: 1px solid #2a2a2a;
padding: 10px;
border-radius: 8px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
color: #e0e0e0;
transition: all 0.2s ease;
}
.gl-item:hover {
border-color: #3b82f6;
background: linear-gradient(135deg, #1f1f1f 0%, #1a1a1a 100%);
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.2);
transform: translateY(-1px);
}
.gl-item-text {
cursor: pointer;
flex: 1;
margin-right: 10px;
}
.gl-item-text b {
display: block;
margin-bottom: 4px;
color: #f0f0f0;
}
.gl-item-text span {
color: #9ca3af;
font-size: 10px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
line-height: 1.4;
}
.gl-item-actions {
display: flex;
gap: 6px;
}
.gl-icon-btn {
background: #1f1f1f;
border: 1px solid #2a2a2a;
cursor: pointer;
font-size: 14px;
color: #9ca3af;
padding: 6px;
border-radius: 6px;
transition: all 0.2s ease;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
}
.gl-icon-btn:hover {
color: #f0f0f0;
background: #2a2a2a;
border-color: #3a3a3a;
transform: scale(1.1);
}
.gl-icon-btn.favorite {
color: #ec4899;
}
.gl-icon-btn.favorite:hover {
color: #db2777;
transform: scale(1.2);
}
.gl-create-btn {
margin: 12px;
padding: 10px;
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
text-align: center;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
font-size: 12px;
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
transition: all 0.2s ease;
}
.gl-create-btn:hover {
background: linear-gradient(135deg, #059669 0%, #047857 100%);
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.5);
transform: translateY(-1px);
}
.gl-view-editor {
display: none;
flex-direction: column;
padding: 15px;
height: 100%;
gap: 10px;
}
.gl-view-editor.active { display: flex; }
.gl-input, .gl-textarea {
background: #0f0f0f;
border: 1px solid #2a2a2a;
color: #e0e0e0;
padding: 10px;
border-radius: 8px;
font-size: 12px;
width: 100%;
box-sizing: border-box;
transition: all 0.2s ease;
}
.gl-input:focus, .gl-textarea:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}
.gl-textarea {
flex-grow: 1;
resize: none;
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
}
.gl-editor-buttons {
display: flex;
gap: 10px;
margin-top: auto;
}
.gl-btn {
flex: 1;
padding: 10px;
border-radius: 8px;
border: none;
cursor: pointer;
font-weight: 600;
color: white;
font-size: 12px;
transition: all 0.2s ease;
}
.gl-btn-save {
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
}
.gl-btn-save:hover {
background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.5);
transform: translateY(-1px);
}
.gl-btn-cancel {
background: linear-gradient(135deg, #374151 0%, #1f2937 100%);
box-shadow: 0 2px 8px rgba(55, 65, 81, 0.3);
}
.gl-btn-cancel:hover {
background: linear-gradient(135deg, #4b5563 0%, #374151 100%);
box-shadow: 0 4px 12px rgba(55, 65, 81, 0.5);
transform: translateY(-1px);
}
/* History Specific Styles */
.history-item-time {
font-size: 9px;
color: #6b7280;
margin-top: 3px;
}
.history-clear-btn {
margin: 12px;
padding: 10px;
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
color: white;
text-align: center;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
font-size: 12px;
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
transition: all 0.2s ease;
}
.history-clear-btn:hover {
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.5);
transform: translateY(-1px);
}
`);
// --- DOM CREATION ---
const panel = document.createElement('div');
panel.id = 'grok-control-panel';
if (!isUiVisible) panel.classList.add('hidden');
panel.innerHTML = `
<div id="grok-resize-handle" title="Drag to Resize"></div>
<div class="grok-header">
<span class="grok-title">Grok Tools v20.5</span>
<button id="grok-toggle-btn" class="grok-toggle-btn">ON</button>
</div>
<div class="grok-controls">
<label class="grok-checkbox">
<input type="checkbox" id="grok-autoclick-cb" ${autoClickEnabled ? 'checked' : ''}> Auto-Retry
</label>
<div>
Max: <input type="number" id="grok-retry-limit" value="${maxRetries}" class="grok-num-input" min="1">
</div>
</div>
<div class="grok-prompt-label">Prompt Editor</div>
<textarea id="grok-panel-prompt" placeholder="Type or paste prompt here..."></textarea>
<div class="grok-btn-row">
<button id="btn-open-library" class="grok-action-btn">Snippets</button>
<button id="btn-open-favorites" class="grok-action-btn">❤️</button>
<button id="btn-open-history" class="grok-action-btn">History</button>
<button id="btn-generate" class="grok-action-btn">Generate</button>
</div>
<div id="grok-status">Ready</div>
<div style="font-size:9px; color:#555; text-align:center;">Hide: Alt+${uiToggleKey.toUpperCase()}</div>
`;
document.body.appendChild(panel);
// --- LIBRARY MODAL ---
const modal = document.createElement('div');
modal.id = 'grok-library-modal';
modal.className = 'grok-modal';
modal.innerHTML = `
<div class="gl-header"><span>Snippets Library</span><span class="gl-close">×</span></div>
<div class="gl-view-list" id="gl-view-list">
<div class="gl-list-content" id="gl-list-container"></div>
<div class="gl-create-btn" id="btn-create-snippet">Create New Snippet</div>
</div>
<div class="gl-view-editor" id="gl-view-editor">
<label style="font-size:11px; color:#8b98a5;">Label</label>
<input type="text" class="gl-input" id="gl-edit-label" placeholder="e.g. Cinematic Lighting">
<label style="font-size:11px; color:#8b98a5;">Prompt Text</label>
<textarea class="gl-textarea" id="gl-edit-text" placeholder="Content to append..."></textarea>
<div class="gl-editor-buttons">
<button class="gl-btn gl-btn-cancel" id="btn-edit-cancel">Cancel</button>
<button class="gl-btn gl-btn-save" id="btn-edit-save">Save Snippet</button>
</div>
</div>
`;
document.body.appendChild(modal);
// --- FAVORITES MODAL ---
const favoritesModal = document.createElement('div');
favoritesModal.id = 'grok-favorites-modal';
favoritesModal.className = 'grok-modal';
favoritesModal.innerHTML = `
<div class="gl-header"><span>Favorites ❤️</span><span class="gl-close favorites-close">×</span></div>
<div class="history-tabs">
<div class="history-tab active" data-tab="video">🎥 Video</div>
<div class="history-tab" data-tab="image">🖼️ Image</div>
</div>
<div class="gl-view-list" id="favorites-view-list">
<div class="gl-list-content" id="favorites-list-container"></div>
</div>
<div class="gl-view-editor" id="favorites-view-viewer">
<label style="font-size:11px; color:#8b98a5;">Name / Label</label>
<input type="text" class="gl-input" id="fav-edit-label" placeholder="Favorite Name">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; margin-top:10px;">
<label style="font-size:11px; color:#8b98a5;">Prompt Text</label>
<span id="favorites-viewer-time" style="font-size:9px; color:#6b7280;"></span>
</div>
<textarea class="gl-textarea" id="favorites-viewer-text"></textarea>
<div class="gl-editor-buttons">
<button class="gl-btn gl-btn-cancel" id="btn-fav-viewer-back">Cancel</button>
<button class="gl-btn gl-btn-save" id="btn-fav-viewer-save">Save Changes</button>
</div>
</div>
`;
document.body.appendChild(favoritesModal);
// --- HISTORY MODAL ---
const historyModal = document.createElement('div');
historyModal.id = 'grok-history-modal';
historyModal.className = 'grok-modal';
historyModal.innerHTML = `
<div class="gl-header"><span>Prompt History (100 max)</span><span class="gl-close history-close">×</span></div>
<div class="history-tabs">
<div class="history-tab active" data-tab="video">🎥 Video</div>
<div class="history-tab" data-tab="image">🖼️ Image</div>
</div>
<div class="gl-view-list" id="history-view-list">
<div class="gl-list-content" id="history-list-container"></div>
<div class="history-clear-btn" id="btn-clear-history">Clear This Tab's History</div>
</div>
<div class="gl-view-editor history-viewer" id="history-view-viewer">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px;">
<label style="font-size:11px; color:#9ca3af; font-weight: 600;">Full Prompt</label>
<span id="history-viewer-time" style="font-size:9px; color:#6b7280;"></span>
</div>
<textarea class="gl-textarea" id="history-viewer-text" readonly></textarea>
<div class="gl-editor-buttons">
<button class="gl-btn gl-btn-cancel" id="btn-viewer-back">← Back</button>
<button class="gl-btn gl-btn-save" id="btn-viewer-use">Use This Prompt</button>
</div>
</div>
`;
document.body.appendChild(historyModal);
// --- RESIZE LOGIC ---
const resizeHandle = document.getElementById('grok-resize-handle');
let isResizing = false;
let startX, startY, startWidth, startHeight;
function updateModalPosition() {
const pHeight = panel.offsetHeight;
const bottom = (20 + pHeight + 10) + 'px';
modal.style.bottom = bottom;
historyModal.style.bottom = bottom;
favoritesModal.style.bottom = bottom;
}
resizeHandle.addEventListener('mousedown', (e) => {
isResizing = true;
startX = e.clientX;
startY = e.clientY;
const rect = panel.getBoundingClientRect();
startWidth = rect.width;
startHeight = rect.height;
e.preventDefault();
document.body.style.cursor = 'nwse-resize';
});
document.addEventListener('mousemove', (e) => {
if (!isResizing) return;
const deltaX = startX - e.clientX;
const deltaY = startY - e.clientY;
const newWidth = Math.max(280, startWidth + deltaX);
const newHeight = Math.max(250, startHeight + deltaY);
panel.style.width = newWidth + 'px';
panel.style.height = newHeight + 'px';
updateModalPosition();
});
document.addEventListener('mouseup', () => {
if (isResizing) {
isResizing = false;
document.body.style.cursor = '';
const rect = panel.getBoundingClientRect();
GM_setValue('panelSize', { width: rect.width + 'px', height: rect.height + 'px' });
updateModalPosition();
}
});
// --- REFS ---
const toggleBtn = document.getElementById('grok-toggle-btn');
const autoClickCb = document.getElementById('grok-autoclick-cb');
const limitInput = document.getElementById('grok-retry-limit');
const statusText = document.getElementById('grok-status');
const promptBox = document.getElementById('grok-panel-prompt');
const openLibBtn = document.getElementById('btn-open-library');
const openFavoritesBtn = document.getElementById('btn-open-favorites');
const openHistoryBtn = document.getElementById('btn-open-history');
const generateBtn = document.getElementById('btn-generate');
const modalEl = document.getElementById('grok-library-modal');
const favoritesModalEl = document.getElementById('grok-favorites-modal');
const historyModalEl = document.getElementById('grok-history-modal');
const listContainer = document.getElementById('gl-list-container');
const favoritesListContainer = document.getElementById('favorites-list-container');
const historyListContainer = document.getElementById('history-list-container');
const createBtn = document.getElementById('btn-create-snippet');
const clearHistoryBtn = document.getElementById('btn-clear-history');
const editLabel = document.getElementById('gl-edit-label');
const editText = document.getElementById('gl-edit-text');
let editingId = null;
// --- HELPER FUNCTIONS ---
function nativeValueSet(el, value) {
if (!el) return;
const setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
setter.call(el, value);
el.dispatchEvent(new Event('input', { bubbles: true }));
}
function resetState(msg) {
limitReached = false;
currentRetryCount = 0;
moderationDetected = false;
processingModeration = false;
if (errorWaitInterval) {
clearInterval(errorWaitInterval);
errorWaitInterval = null;
}
updateStatus(msg);
}
function updateStatus(msg, type) {
statusText.textContent = msg;
statusText.className = '';
if (type === 'error') statusText.classList.add('status-error');
if (type === 'warning') statusText.classList.add('status-warning');
}
// --- ROBUST MODERATION DETECTION ---
function checkForModerationContent() {
const now = Date.now();
// Prevent checking too frequently
if (now - lastModerationCheck < 200) return null;
lastModerationCheck = now;
// Method 1: Check toast notifications
const toastSelectors = [
'section[aria-label="Notifications alt+T"]',
'section[aria-label*="Notification"]',
'[role="alert"]',
'[data-sonner-toast]',
'.toast',
'[class*="toast"]',
'[class*="Toast"]'
];
for (const selector of toastSelectors) {
try {
const containers = document.querySelectorAll(selector);
for (const container of containers) {
const text = (container.textContent || container.innerText || '').toLowerCase();
for (const pattern of MODERATION_PATTERNS) {
if (text.includes(pattern.toLowerCase())) {
debugLog('Moderation detected via toast:', selector, text.substring(0, 100));
return { element: container, method: 'toast', text: text };
}
}
}
} catch (e) {}
}
// Method 2: Check error data types
try {
const errorElements = document.querySelectorAll('[data-type="error"], [data-state="error"], [class*="error"]');
for (const el of errorElements) {
const text = (el.textContent || el.innerText || '').toLowerCase();
for (const pattern of MODERATION_PATTERNS) {
if (text.includes(pattern.toLowerCase())) {
debugLog('Moderation detected via error element:', text.substring(0, 100));
return { element: el, method: 'error-element', text: text };
}
}
}
} catch (e) {}
// Method 3: Scan all visible text elements
try {
const textElements = document.querySelectorAll('span, p, div, li');
for (const el of textElements) {
// Skip our own panel
if (el.closest('#grok-control-panel') || el.closest('.grok-modal')) continue;
// Only check elements with limited text content (avoid scanning large containers)
const directText = el.childNodes.length === 1 && el.childNodes[0].nodeType === Node.TEXT_NODE;
if (!directText && el.children.length > 0) continue;
const text = (el.textContent || el.innerText || '').trim().toLowerCase();
if (text.length < 10 || text.length > 200) continue;
for (const pattern of MODERATION_PATTERNS) {
if (text.includes(pattern.toLowerCase())) {
debugLog('Moderation detected via text scan:', text);
return { element: el, method: 'text-scan', text: text };
}
}
}
} catch (e) {}
// Method 4: Check for specific Grok error styling
try {
const redTextElements = document.querySelectorAll('[class*="red"], [class*="danger"], [style*="color: red"], [style*="color:red"]');
for (const el of redTextElements) {
if (el.closest('#grok-control-panel')) continue;
const text = (el.textContent || el.innerText || '').toLowerCase();
for (const pattern of MODERATION_PATTERNS) {
if (text.includes(pattern.toLowerCase())) {
debugLog('Moderation detected via styled element:', text.substring(0, 100));
return { element: el, method: 'styled', text: text };
}
}
}
} catch (e) {}
return null;
}
function hideElement(element) {
if (!element) return;
try {
element.style.display = 'none';
element.style.visibility = 'hidden';
element.style.opacity = '0';
element.style.pointerEvents = 'none';
} catch (e) {
debugLog('Error hiding element:', e);
}
}
function addToHistory(prompt, type) {
if (!prompt || !prompt.trim()) return;
const historyArray = type === 'image' ? imagePromptHistory : videoPromptHistory;
const filtered = historyArray.filter(item => item.text !== prompt);
filtered.unshift({
id: Date.now().toString(),
text: prompt,
timestamp: Date.now(),
type: type
});
const limited = filtered.slice(0, MAX_HISTORY_ITEMS);
if (type === 'image') {
imagePromptHistory = limited;
GM_setValue('imagePromptHistory', imagePromptHistory);
} else {
videoPromptHistory = limited;
GM_setValue('videoPromptHistory', videoPromptHistory);
}
}
function addToFavorites(prompt, type) {
if (!prompt || !prompt.trim()) return;
const favArray = type === 'image' ? imageFavorites : videoFavorites;
const exists = favArray.some(item => item.text === prompt);
if (exists) {
updateStatus(`Already in favorites!`, 'error');
setTimeout(() => updateStatus('Ready'), 2000);
return;
}
const newFav = {
id: Date.now().toString(),
text: prompt,
label: prompt.length > 40 ? prompt.substring(0, 40) + '...' : prompt,
timestamp: Date.now(),
type: type
};
if (type === 'image') {
imageFavorites.unshift(newFav);
GM_setValue('imageFavorites', imageFavorites);
} else {
videoFavorites.unshift(newFav);
GM_setValue('videoFavorites', videoFavorites);
}
updateStatus(`Added to favorites! ❤️`);
setTimeout(() => updateStatus('Ready'), 2000);
}
function isInFavorites(prompt, type) {
const favArray = type === 'image' ? imageFavorites : videoFavorites;
return favArray.some(item => item.text === prompt);
}
function formatTimestamp(timestamp) {
const date = new Date(timestamp);
const now = new Date();
const diff = now - date;
if (diff < 60000) return 'Just now';
if (diff < 3600000) {
const mins = Math.floor(diff / 60000);
return `${mins} minute${mins > 1 ? 's' : ''} ago`;
}
if (diff < 86400000) {
const hours = Math.floor(diff / 3600000);
return `${hours} hour${hours > 1 ? 's' : ''} ago`;
}
return date.toLocaleDateString();
}
function detectPromptType() {
const videoBtn = document.querySelector(RETRY_BUTTON_SELECTOR);
const videoTA = document.querySelector(TARGET_TEXTAREA_SELECTOR);
if (videoBtn && !videoBtn.disabled) return 'video';
if (videoTA && (document.activeElement === videoTA || videoTA.value.trim())) return 'video';
if (document.querySelector(IMAGE_PROMPT_SELECTOR) ||
document.querySelector(IMAGE_IMAGINE_SELECTOR) ||
document.querySelector(IMAGE_EDITOR_SELECTOR)) {
return 'image';
}
return 'video';
}
// --- PROMPT SYNC: Panel -> Grok ---
let syncTimeout = null;
let isUserTyping = false;
promptBox.addEventListener('input', () => {
clearTimeout(syncTimeout);
syncTimeout = setTimeout(() => {
const grokTA = document.querySelector(TARGET_TEXTAREA_SELECTOR) ||
document.querySelector(IMAGE_EDITOR_SELECTOR) ||
document.querySelector(IMAGE_PROMPT_SELECTOR);
const imagineP = document.querySelector(IMAGE_IMAGINE_SELECTOR);
if (!isUserTyping) {
if (grokTA && document.activeElement !== grokTA) {
lastTypedPrompt = promptBox.value;
nativeValueSet(grokTA, lastTypedPrompt);
resetState("Ready");
} else if (imagineP && document.activeElement !== imagineP) {
lastTypedPrompt = promptBox.value;
resetState("Ready");
}
}
}, 100);
});
// --- PROMPT SYNC: Grok -> Panel ---
let lastSyncedValue = '';
let typingTimer = null;
document.addEventListener('input', (e) => {
if (e.target.matches(TARGET_TEXTAREA_SELECTOR) ||
e.target.matches(IMAGE_EDITOR_SELECTOR) ||
e.target.matches(IMAGE_PROMPT_SELECTOR)) {
isUserTyping = true;
clearTimeout(typingTimer);
const val = e.target.value;
if (val !== lastSyncedValue) {
lastSyncedValue = val;
promptBox.value = val;
lastTypedPrompt = val;
if (val.trim()) resetState("Ready");
}
typingTimer = setTimeout(() => {
isUserTyping = false;
}, 500);
}
}, { capture: true, passive: true });
document.addEventListener('keydown', (e) => {
const editor = document.querySelector('.tiptap.ProseMirror');
const imagineP = document.querySelector(IMAGE_IMAGINE_SELECTOR);
if ((editor && e.target.closest('.tiptap.ProseMirror')) ||
(imagineP && document.activeElement === imagineP)) {
isUserTyping = true;
clearTimeout(typingTimer);
typingTimer = setTimeout(() => {
isUserTyping = false;
let capturedText = '';
if (editor && editor.textContent.trim()) {
capturedText = editor.textContent.trim();
} else if (imagineP && (imagineP.textContent || imagineP.innerText || '').trim()) {
capturedText = (imagineP.textContent || imagineP.innerText || '').trim();
}
if (capturedText && capturedText !== promptBox.value) {
promptBox.value = capturedText;
lastTypedPrompt = capturedText;
resetState("Ready");
}
}, 500);
}
}, { capture: true, passive: true });
let lastImagineValue = '';
const imagineObserver = new MutationObserver((mutations) => {
if (isUserTyping) {
const editor = document.querySelector('.tiptap.ProseMirror');
const imagineP = document.querySelector(IMAGE_IMAGINE_SELECTOR);
let val = '';
if (editor && editor.textContent.trim()) {
val = editor.textContent.trim();
} else if (imagineP) {
val = (imagineP.textContent || imagineP.innerText || '').trim();
}
if (val && val !== lastImagineValue && val !== promptBox.value) {
lastImagineValue = val;
promptBox.value = val;
lastTypedPrompt = val;
if (imagineP && imagineP.classList.contains('is-empty') && val) {
imagineP.classList.remove('is-empty', 'is-editor-empty');
}
}
}
});
const startImagineObserver = () => {
const editor = document.querySelector('.tiptap.ProseMirror');
const imagineP = document.querySelector(IMAGE_IMAGINE_SELECTOR);
if (editor) {
try {
imagineObserver.observe(editor, { characterData: true, childList: true, subtree: true, attributes: false });
} catch(e) {}
}
if (imagineP) {
try {
imagineObserver.observe(imagineP, { characterData: true, childList: true, subtree: true, attributes: false });
} catch(e) {}
}
};
startImagineObserver();
document.addEventListener('input', (e) => {
const target = e.target;
if (target && target.closest && target.closest('.tiptap.ProseMirror')) {
isUserTyping = true;
clearTimeout(typingTimer);
const editor = document.querySelector('.tiptap.ProseMirror');
const val = editor ? (editor.textContent || editor.innerText || '').trim() : '';
if (val && val !== lastImagineValue) {
lastImagineValue = val;
promptBox.value = val;
lastTypedPrompt = val;
resetState("Ready");
}
typingTimer = setTimeout(() => {
isUserTyping = false;
}, 500);
}
}, true);
// --- MANUAL GENERATE ---
generateBtn.addEventListener('click', () => {
const grokTA = document.querySelector(TARGET_TEXTAREA_SELECTOR) ||
document.querySelector(IMAGE_EDITOR_SELECTOR) ||
document.querySelector(IMAGE_PROMPT_SELECTOR);
const realBtn = document.querySelector(RETRY_BUTTON_SELECTOR) ||
document.querySelector(IMAGE_EDITOR_BUTTON_SELECTOR) ||
document.querySelector(IMAGE_SUBMIT_BUTTON_SELECTOR);
const imagineP = document.querySelector(IMAGE_IMAGINE_SELECTOR);
if (!grokTA && !imagineP) {
updateStatus("Grok elements not found!", "error");
return;
}
if (!realBtn) {
updateStatus("Generate button not found!", "error");
return;
}
const now = Date.now();
if (now - lastGenerationTimestamp < GENERATION_COOLDOWN_MS) {
const remaining = Math.ceil((GENERATION_COOLDOWN_MS - (now - lastGenerationTimestamp)) / 1000);
updateStatus(`Cooldown: ${remaining}s remaining`, "error");
return;
}
const promptToGenerate = promptBox.value.trim();
if (promptToGenerate) {
let promptType = 'image';
if (realBtn.matches(RETRY_BUTTON_SELECTOR)) {
promptType = 'video';
} else if (realBtn.matches(IMAGE_SUBMIT_BUTTON_SELECTOR) ||
realBtn.matches(IMAGE_EDITOR_BUTTON_SELECTOR)) {
promptType = 'image';
} else {
promptType = detectPromptType();
}
addToHistory(promptToGenerate, promptType);
}
if (grokTA) {
nativeValueSet(grokTA, promptBox.value);
} else if (imagineP) {
imagineP.textContent = promptBox.value;
if (imagineP.classList.contains('is-empty') && promptBox.value) {
imagineP.classList.remove('is-empty');
}
}
setTimeout(() => {
if (!realBtn.disabled) {
realBtn.click();
lastGenerationTimestamp = Date.now();
updateStatus("Generation Started...");
} else {
updateStatus("Grok button disabled/processing.", "error");
}
}, 50);
});
// --- CAPTURE IMAGE SUBMIT CLICKS ---
let isAutoRetryClick = false;
document.addEventListener('mousedown', (e) => {
if (isAutoRetryClick) return;
const submitBtn = e.target.closest('button[aria-label="Submit"]');
if (submitBtn) {
const editor = document.querySelector('.tiptap.ProseMirror');
const imagineP = document.querySelector(IMAGE_IMAGINE_SELECTOR);
const imageTA = document.querySelector(IMAGE_PROMPT_SELECTOR);
let promptToCapture = '';
if (editor && editor.textContent.trim().length > 0) {
promptToCapture = editor.textContent.trim();
} else if (imagineP) {
const imagineText = (imagineP.textContent || imagineP.innerText || '').trim();
if (imagineText) promptToCapture = imagineText;
} else if (imageTA && imageTA.value.trim()) {
promptToCapture = imageTA.value.trim();
} else if (promptBox.value.trim()) {
promptToCapture = promptBox.value.trim();
} else if (lastTypedPrompt.trim()) {
promptToCapture = lastTypedPrompt.trim();
}
if (promptToCapture && promptToCapture.length > 2) {
addToHistory(promptToCapture, 'image');
updateStatus("Image prompt captured!");
setTimeout(() => { if (isRetryEnabled) updateStatus("Ready"); }, 2000);
}
}
}, true);
document.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
if (e.target.matches('textarea[aria-label="Make a video"]')) return;
if (e.target.matches('textarea[aria-label="Image prompt"]')) {
const val = e.target.value.trim();
if (val && val.length > 2) {
addToHistory(val, 'image');
updateStatus("Image prompt captured!");
setTimeout(() => { if (isRetryEnabled) updateStatus("Ready"); }, 2000);
}
}
else if (e.target.closest('.tiptap.ProseMirror')) {
const editor = e.target.closest('.tiptap.ProseMirror');
if (editor && editor.textContent.trim().length > 2) {
const val = editor.textContent.trim();
addToHistory(val, 'image');
updateStatus("Image prompt captured!");
setTimeout(() => { if (isRetryEnabled) updateStatus("Ready"); }, 2000);
}
}
else if (e.target.matches(IMAGE_IMAGINE_SELECTOR)) {
const imagineP = e.target;
const val = (imagineP.textContent || imagineP.innerText || '').trim();
if (val && val.length > 2) {
addToHistory(val, 'image');
updateStatus("Image prompt captured!");
setTimeout(() => { if (isRetryEnabled) updateStatus("Ready"); }, 2000);
}
}
}
}, true);
// --- FAVORITES MODAL LOGIC ---
const favoritesTabs = favoritesModal.querySelectorAll('.history-tab');
favoritesTabs.forEach(tab => {
tab.addEventListener('click', () => {
favoritesTabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
currentFavoritesTab = tab.dataset.tab;
renderFavorites();
});
});
function renderFavorites() {
favoritesListContainer.innerHTML = '';
const favArray = currentFavoritesTab === 'image' ? imageFavorites : videoFavorites;
if (favArray.length === 0) {
favoritesListContainer.innerHTML = `<div style="text-align:center; padding:20px; color:#555;">No ${currentFavoritesTab} favorites yet</div>`;
return;
}
favArray.forEach(item => {
const el = document.createElement('div');
el.className = 'gl-item';
const label = item.label || (item.text.length > 40 ? item.text.substring(0, 40) + '...' : item.text);
el.innerHTML = `
<div class="gl-item-text">
<b>${escapeHtml(label)}</b>
<span>${escapeHtml(item.text)}</span>
<div class="history-item-time">${formatTimestamp(item.timestamp)}</div>
</div>
<div class="gl-item-actions">
<button class="gl-icon-btn fav-btn-edit" title="Edit Name/Text">✎</button>
<button class="gl-icon-btn fav-btn-del" title="Remove">🗑</button>
</div>`;
el.querySelector('.gl-item-text').addEventListener('click', () => {
promptBox.value = item.text;
promptBox.dispatchEvent(new Event('input'));
favoritesModalEl.classList.remove('active');
});
el.querySelector('.fav-btn-edit').addEventListener('click', (e) => {
e.stopPropagation();
editFavorite(item);
});
el.querySelector('.fav-btn-del').addEventListener('click', (e) => {
e.stopPropagation();
if (confirm(`Remove favorite "${label}"?`)) {
if (currentFavoritesTab === 'image') {
imageFavorites = imageFavorites.filter(h => h.id !== item.id);
GM_setValue('imageFavorites', imageFavorites);
} else {
videoFavorites = videoFavorites.filter(h => h.id !== item.id);
GM_setValue('videoFavorites', videoFavorites);
}
renderFavorites();
}
});
favoritesListContainer.appendChild(el);
});
}
function editFavorite(item) {
document.getElementById('favorites-view-list').style.display = 'none';
document.getElementById('favorites-view-viewer').classList.add('active');
const label = item.label || (item.text.length > 40 ? item.text.substring(0, 40) + '...' : item.text);
document.getElementById('fav-edit-label').value = label;
document.getElementById('favorites-viewer-text').value = item.text;
document.getElementById('favorites-viewer-time').textContent = formatTimestamp(item.timestamp);
currentEditingFavId = item.id;
}
function closeFavoritesEditor() {
document.getElementById('favorites-view-viewer').classList.remove('active');
document.getElementById('favorites-view-list').style.display = 'flex';
currentEditingFavId = null;
}
openFavoritesBtn.addEventListener('click', () => {
favoritesModalEl.classList.add('active');
updateModalPosition();
renderFavorites();
});
favoritesModal.querySelector('.favorites-close').addEventListener('click', () => {
favoritesModalEl.classList.remove('active');
closeFavoritesEditor();
});
document.getElementById('btn-fav-viewer-back').addEventListener('click', closeFavoritesEditor);
document.getElementById('btn-fav-viewer-save').addEventListener('click', () => {
const newLabel = document.getElementById('fav-edit-label').value.trim() || "Untitled";
const newText = document.getElementById('favorites-viewer-text').value.trim();
if (!newText) { alert("Prompt text cannot be empty."); return; }
if (!currentEditingFavId) return;
let favArray = currentFavoritesTab === 'image' ? imageFavorites : videoFavorites;
const idx = favArray.findIndex(f => f.id === currentEditingFavId);
if (idx !== -1) {
favArray[idx].label = newLabel;
favArray[idx].text = newText;
if (currentFavoritesTab === 'image') GM_setValue('imageFavorites', favArray);
else GM_setValue('videoFavorites', favArray);
renderFavorites();
closeFavoritesEditor();
}
});
// --- HISTORY TAB SWITCHING ---
const historyTabs = historyModal.querySelectorAll('.history-tab');
historyTabs.forEach(tab => {
tab.addEventListener('click', () => {
historyTabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
currentHistoryTab = tab.dataset.tab;
renderHistory();
});
});
function renderHistory() {
historyListContainer.innerHTML = '';
const historyArray = currentHistoryTab === 'image' ? imagePromptHistory : videoPromptHistory;
if (historyArray.length === 0) {
historyListContainer.innerHTML = `<div style="text-align:center; padding:20px; color:#555;">No ${currentHistoryTab} history yet</div>`;
return;
}
historyArray.forEach(item => {
const el = document.createElement('div');
el.className = 'gl-item history-item';
const isFavorited = isInFavorites(item.text, item.type);
el.innerHTML = `
<div class="gl-item-text history-text">
<span class="history-preview">${escapeHtml(item.text)}</span>
<div class="history-item-time">${formatTimestamp(item.timestamp)}</div>
</div>
<div class="gl-item-actions">
<button class="gl-icon-btn history-btn-fav ${isFavorited ? 'favorite' : ''}" title="${isFavorited ? 'Remove from favorites' : 'Add to favorites'}">❤️</button>
<button class="gl-icon-btn history-btn-view" title="View Full">👁</button>
<button class="gl-icon-btn history-btn-del" title="Delete">🗑</button>
</div>`;
el.querySelector('.gl-item-text').addEventListener('click', () => {
promptBox.value = item.text;
promptBox.dispatchEvent(new Event('input'));
historyModalEl.classList.remove('active');
});
el.querySelector('.history-btn-fav').addEventListener('click', (e) => {
e.stopPropagation();
if (isFavorited) {
if (item.type === 'image') {
imageFavorites = imageFavorites.filter(f => f.text !== item.text);
GM_setValue('imageFavorites', imageFavorites);
} else {
videoFavorites = videoFavorites.filter(f => f.text !== item.text);
GM_setValue('videoFavorites', videoFavorites);
}
updateStatus(`Removed from favorites`);
} else {
addToFavorites(item.text, item.type);
}
renderHistory();
setTimeout(() => updateStatus('Ready'), 2000);
});
el.querySelector('.history-btn-view').addEventListener('click', (e) => {
e.stopPropagation();
showHistoryViewer(item);
});
el.querySelector('.history-btn-del').addEventListener('click', (e) => {
e.stopPropagation();
if (currentHistoryTab === 'image') {
imagePromptHistory = imagePromptHistory.filter(h => h.id !== item.id);
GM_setValue('imagePromptHistory', imagePromptHistory);
} else {
videoPromptHistory = videoPromptHistory.filter(h => h.id !== item.id);
GM_setValue('videoPromptHistory', videoPromptHistory);
}
renderHistory();
});
historyListContainer.appendChild(el);
});
}
function showHistoryViewer(item) {
document.getElementById('history-view-list').style.display = 'none';
document.getElementById('history-view-viewer').classList.add('active');
document.getElementById('history-viewer-text').value = item.text;
document.getElementById('history-viewer-time').textContent = formatTimestamp(item.timestamp);
historyModalEl.dataset.currentItemId = item.id;
}
function closeHistoryViewer() {
document.getElementById('history-view-viewer').classList.remove('active');
document.getElementById('history-view-list').style.display = 'flex';
}
openHistoryBtn.addEventListener('click', () => {
historyModalEl.classList.add('active');
updateModalPosition();
renderHistory();
});
clearHistoryBtn.addEventListener('click', () => {
const tabName = currentHistoryTab === 'image' ? 'image' : 'video';
if (confirm(`Clear all ${tabName} prompt history?`)) {
if (currentHistoryTab === 'image') {
imagePromptHistory = [];
GM_setValue('imagePromptHistory', []);
} else {
videoPromptHistory = [];
GM_setValue('videoPromptHistory', []);
}
renderHistory();
}
});
historyModal.querySelector('.history-close').addEventListener('click', () => {
historyModalEl.classList.remove('active');
closeHistoryViewer();
});
document.getElementById('btn-viewer-back').addEventListener('click', closeHistoryViewer);
document.getElementById('btn-viewer-use').addEventListener('click', () => {
const text = document.getElementById('history-viewer-text').value;
promptBox.value = text;
promptBox.dispatchEvent(new Event('input'));
historyModalEl.classList.remove('active');
closeHistoryViewer();
});
// --- SNIPPETS MODAL LOGIC ---
function renderSnippets() {
listContainer.innerHTML = '';
savedSnippets.forEach(item => {
const el = document.createElement('div');
el.className = 'gl-item';
el.innerHTML = `
<div class="gl-item-text"><b>${escapeHtml(item.label)}</b><span>${escapeHtml(item.text)}</span></div>
<div class="gl-item-actions">
<button class="gl-icon-btn gl-btn-edit">✎</button>
<button class="gl-icon-btn gl-btn-del">🗑</button>
</div>`;
el.querySelector('.gl-item-text').addEventListener('click', () => {
const cur = promptBox.value;
promptBox.value = cur + (cur && !cur.endsWith(' ') ? ' ' : '') + item.text;
promptBox.dispatchEvent(new Event('input'));
modalEl.classList.remove('active');
});
el.querySelector('.gl-btn-edit').addEventListener('click', (e) => { e.stopPropagation(); showEditor(item); });
el.querySelector('.gl-btn-del').addEventListener('click', (e) => {
e.stopPropagation();
if (confirm(`Delete "${item.label}"?`)) {
savedSnippets = savedSnippets.filter(s => s.id !== item.id);
GM_setValue('savedSnippets', savedSnippets);
renderSnippets();
}
});
listContainer.appendChild(el);
});
}
function showEditor(item = null) {
document.getElementById('gl-view-list').style.display = 'none';
document.getElementById('gl-view-editor').classList.add('active');
editingId = item ? item.id : null;
editLabel.value = item ? item.label : '';
editText.value = item ? item.text : '';
editText.focus();
}
createBtn.addEventListener('click', () => showEditor(null));
document.getElementById('btn-edit-save').addEventListener('click', () => {
const label = editLabel.value.trim() || 'Untitled';
const text = editText.value.trim();
if (!text) return alert("Empty text");
if (editingId) {
const idx = savedSnippets.findIndex(s => s.id === editingId);
if (idx > -1) {
savedSnippets[idx].label = label;
savedSnippets[idx].text = text;
}
} else {
savedSnippets.push({ id: Date.now().toString(), label, text });
}
GM_setValue('savedSnippets', savedSnippets);
document.getElementById('gl-view-editor').classList.remove('active');
document.getElementById('gl-view-list').style.display = 'flex';
renderSnippets();
});
document.getElementById('btn-edit-cancel').addEventListener('click', () => {
document.getElementById('gl-view-editor').classList.remove('active');
document.getElementById('gl-view-list').style.display = 'flex';
});
openLibBtn.addEventListener('click', () => {
modalEl.classList.add('active');
updateModalPosition();
renderSnippets();
});
modal.querySelector('.gl-close').addEventListener('click', () => modalEl.classList.remove('active'));
function escapeHtml(text) {
return text ? text.replace(/&/g, "&").replace(/</g, "<") : '';
}
// --- TOGGLE BUTTON ---
toggleBtn.addEventListener('click', () => {
isRetryEnabled = !isRetryEnabled;
toggleBtn.textContent = isRetryEnabled ? "ON" : "OFF";
toggleBtn.classList.toggle('off', !isRetryEnabled);
resetState(isRetryEnabled ? "Ready" : "Disabled");
if (!isRetryEnabled) statusText.className = 'status-error';
});
autoClickCb.addEventListener('change', (e) => {
autoClickEnabled = e.target.checked;
GM_setValue('autoClickEnabled', autoClickEnabled);
});
limitInput.addEventListener('change', (e) => {
maxRetries = parseInt(e.target.value);
GM_setValue('maxRetries', maxRetries);
});
// --- MAIN MUTATION OBSERVER WITH IMPROVED DETECTION ---
const observer = new MutationObserver(() => {
if (observerThrottle || !isRetryEnabled || limitReached) return;
observerThrottle = true;
setTimeout(() => { observerThrottle = false; }, OBSERVER_THROTTLE_MS);
// Check for moderation content using robust detection
if (!processingModeration) {
const moderationResult = checkForModerationContent();
if (moderationResult && moderationResult.found !== false) {
debugLog('Moderation detected!', moderationResult);
moderationDetected = true;
processingModeration = true;
updateStatus(`Moderation detected! Waiting for message to clear...`, 'warning');
// --- NEW LOGIC: WAIT FOR ERROR TO DISAPPEAR ---
// We no longer force hide the element immediately.
// We start polling to see when it's gone.
waitForErrorDisappearance(moderationResult.element);
}
}
startImagineObserver();
// Video progress detection
const progressEl = document.querySelector('.text-xs.font-semibold.w-\\[4ch\\].mb-\\[1px\\].tabular-nums');
if (progressEl) {
const txt = progressEl.textContent.trim();
if (txt.includes('%')) {
const val = parseInt(txt);
if (!isNaN(val) && val >= 5 && val < 100) {
const videoTA = document.querySelector(TARGET_TEXTAREA_SELECTOR);
if (videoTA && videoTA.value.trim()) {
const prompt = videoTA.value.trim();
const existsInVideo = videoPromptHistory.some(item => item.text === prompt);
if (!existsInVideo) {
addToHistory(prompt, 'video');
updateStatus("Video prompt captured!");
setTimeout(() => { if (isRetryEnabled) updateStatus("Ready"); }, 2000);
}
}
}
}
}
});
observer.observe(document.body, { childList: true, subtree: true, attributes: true, characterData: true });
// --- WAIT FOR ERROR CLEARANCE FUNCTION ---
function waitForErrorDisappearance(element) {
if (errorWaitInterval) clearInterval(errorWaitInterval);
let safetyCounter = 0;
const POLL_MS = 500;
const MAX_WAIT_MS = 10000; // 10 seconds max wait
errorWaitInterval = setInterval(() => {
safetyCounter += POLL_MS;
// Check if element is still in DOM
const isConnected = document.body.contains(element);
// Check if element is visible (opacity/display)
let isVisible = false;
if (isConnected) {
try {
const style = window.getComputedStyle(element);
isVisible = style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0';
} catch(e) { isVisible = false; }
}
debugLog('Waiting for error clearance...', { isConnected, isVisible, safetyCounter });
// If gone or timed out
if (!isConnected || !isVisible || safetyCounter >= MAX_WAIT_MS) {
clearInterval(errorWaitInterval);
errorWaitInterval = null;
if (safetyCounter >= MAX_WAIT_MS) {
debugLog('Error wait timed out, proceeding anyway.');
} else {
debugLog('Error message cleared.');
}
updateStatus('Message cleared. Retrying...');
const now = Date.now();
handleRetry(now);
}
}, POLL_MS);
}
// --- RETRY HANDLER ---
function handleRetry(now) {
debugLog('handleRetry called', { currentRetryCount, maxRetries, autoClickEnabled });
if (now - lastGenerationTimestamp < GENERATION_COOLDOWN_MS) {
debugLog('Generation cooldown active');
return;
}
lastRetryTimestamp = now;
const grokTA = document.querySelector(TARGET_TEXTAREA_SELECTOR) ||
document.querySelector(IMAGE_EDITOR_SELECTOR) ||
document.querySelector(IMAGE_PROMPT_SELECTOR);
const btn = document.querySelector(RETRY_BUTTON_SELECTOR) ||
document.querySelector(IMAGE_EDITOR_BUTTON_SELECTOR) ||
document.querySelector(IMAGE_SUBMIT_BUTTON_SELECTOR);
const imagineP = document.querySelector(IMAGE_IMAGINE_SELECTOR);
debugLog('Elements found:', { grokTA: !!grokTA, btn: !!btn, imagineP: !!imagineP, lastTypedPrompt });
if ((grokTA || imagineP) && lastTypedPrompt) {
// Re-fill the prompt
if (grokTA) {
nativeValueSet(grokTA, lastTypedPrompt);
debugLog('Prompt refilled to textarea');
} else if (imagineP) {
imagineP.textContent = lastTypedPrompt;
if (imagineP.classList.contains('is-empty') && lastTypedPrompt) {
imagineP.classList.remove('is-empty');
}
debugLog('Prompt refilled to imagineP');
}
if (autoClickEnabled && currentRetryCount >= maxRetries) {
updateStatus(`Limit Reached (${maxRetries}/${maxRetries})`, "error");
limitReached = true;
processingModeration = false;
moderationDetected = false;
return;
}
if (autoClickEnabled && btn) {
currentRetryCount++;
updateStatus(`Retrying (${currentRetryCount}/${maxRetries})...`, 'warning');
debugLog(`Scheduling retry ${currentRetryCount}/${maxRetries}`);
isAutoRetryClick = true;
setTimeout(() => {
if (!btn.disabled) {
debugLog('Clicking button...');
btn.click();
lastGenerationTimestamp = Date.now();
updateStatus(`Retry ${currentRetryCount}/${maxRetries} submitted`);
} else {
debugLog('Button is disabled');
updateStatus("Button disabled, waiting...", "error");
}
isAutoRetryClick = false;
// Reset processing state after a delay
setTimeout(() => {
processingModeration = false;
moderationDetected = false;
}, 2000);
}, RETRY_DELAY_MS);
} else {
debugLog('Auto-click disabled or button not found');
processingModeration = false;
moderationDetected = false;
}
} else {
debugLog('No textarea/prompt found or lastTypedPrompt empty');
processingModeration = false;
moderationDetected = false;
}
}
// --- PERIODIC MODERATION CHECK (Backup) ---
setInterval(() => {
if (!isRetryEnabled || limitReached || processingModeration) return;
const moderationResult = checkForModerationContent();
if (moderationResult && moderationResult.found !== false) {
debugLog('Periodic check found moderation');
moderationDetected = true;
processingModeration = true;
updateStatus(`Moderation detected! Waiting...`, 'warning');
waitForErrorDisappearance(moderationResult.element);
}
}, 1000);
// --- KEYBOARD TOGGLE ---
document.addEventListener('keydown', (e) => {
if (e.altKey && e.key.toLowerCase() === uiToggleKey) {
isUiVisible = !isUiVisible;
GM_setValue('isUiVisible', isUiVisible);
panel.classList.toggle('hidden', !isUiVisible);
e.preventDefault();
}
});
// --- CLEANUP ---
window.addEventListener('beforeunload', () => {
observer.disconnect();
imagineObserver.disconnect();
if (errorWaitInterval) clearInterval(errorWaitInterval);
});
// Initialize modal positions
updateModalPosition();
// Log startup
debugLog('Grok Auto-Retry v20.5 initialized. Debug mode:', DEBUG_MODE);
debugLog('Monitoring for patterns:', MODERATION_PATTERNS);
})();