Fixed memory leaks, crash issues, and added image editor support.
目前為
// ==UserScript==
// @name Grok Auto-Retry + Prompt Snippets (v16.0 - Image Editor Support)
// @namespace http://tampermonkey.net/
// @version 19
// @description Fixed memory leaks, crash issues, and added image editor support.
// @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..."]'; // NEW
const RETRY_BUTTON_SELECTOR = 'button[aria-label="Make video"]';
const IMAGE_EDITOR_BUTTON_SELECTOR = 'button[aria-label="Generate"]'; // NEW
const MODERATION_TEXT = "Content Moderated. Try a different idea.";
const RETRY_DELAY_MS = 1000;
const COOLDOWN_MS = 2000;
const OBSERVER_THROTTLE_MS = 500;
// --- 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 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;
// --- 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-color: #15202b; border: 1px solid #38444d; border-radius: 12px;
padding: 15px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
color: white; z-index: 99998; box-shadow: 0 4px 12px rgba(0,0,0,0.6);
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 #1d9bf0; border-right: 6px solid transparent;
width: 0; height: 0; opacity: 0.7;
}
#grok-resize-handle:hover::after { opacity: 1; border-top-color: #fff; }
.grok-header { display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; margin-left: 10px; }
.grok-title { font-weight: bold; font-size: 14px; color: #fff; }
.grok-toggle-btn {
background: #00ba7c; border: none; color: white;
padding: 4px 12px; border-radius: 15px;
font-size: 11px; font-weight: bold; cursor: pointer;
}
.grok-toggle-btn.off { background: #f4212e; }
.grok-controls { display: flex; align-items: center; justify-content: space-between; font-size: 12px; color: #8b98a5; flex-shrink: 0; }
.grok-checkbox { display: flex; align-items: center; cursor: pointer; color: #fff; }
.grok-checkbox input { margin-right: 6px; }
.grok-num-input {
width: 40px; background: #273340; border: 1px solid #38444d;
color: white; border-radius: 4px; padding: 2px 5px; text-align: center;
}
.grok-prompt-label { font-size: 11px; font-weight: bold; color: #8b98a5; margin-bottom: -5px; flex-shrink: 0; }
#grok-panel-prompt {
width: 100%; flex-grow: 1; background: #000; border: 1px solid #38444d; border-radius: 6px;
color: #fff; padding: 8px; font-size: 12px; font-family: sans-serif;
resize: none; box-sizing: border-box;
}
#grok-panel-prompt:focus { border-color: #1d9bf0; outline: none; }
.grok-btn-row { display: flex; gap: 8px; flex-shrink: 0; }
.grok-action-btn {
flex: 1; padding: 8px; border-radius: 6px; border: none;
cursor: pointer; font-weight: bold; font-size: 12px;
transition: background 0.2s, border-color 0.2s;
}
#btn-open-library { background: #1d9bf0; color: white; }
#btn-open-library:hover { background: #1a8cd8; }
#btn-generate { background: #273340; color: #eff3f4; border: 1px solid #38444d; }
#btn-generate:hover { background: #38444d; border-color: #6b7d8c; }
#grok-status { text-align: center; font-size: 11px; color: #00ba7c; padding-top: 5px; border-top: 1px solid #38444d; flex-shrink: 0; }
.status-error { color: #f4212e !important; }
#grok-library-modal {
position: fixed; right: 20px; width: 350px; height: 400px;
background: #15202b; border: 1px solid #38444d; border-radius: 12px;
display: none; flex-direction: column; z-index: 99999;
box-shadow: 0 4px 20px rgba(0,0,0,0.8);
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
#grok-library-modal.active { display: flex; }
.gl-header {
padding: 10px; background: #192734; display: flex;
justify-content: space-between; align-items: center;
font-weight: bold; font-size: 13px; color: white;
border-bottom: 1px solid #38444d;
}
.gl-close { cursor: pointer; font-size: 18px; line-height: 1; color: #8b98a5; }
.gl-view-list { display: flex; flex-direction: column; height: 100%; overflow: hidden; }
.gl-list-content { overflow-y: auto; padding: 10px; flex: 1; display: flex; flex-direction: column; gap: 6px; }
.gl-item {
background: #192734; border: 1px solid #38444d; padding: 8px;
border-radius: 4px; display: flex; justify-content: space-between; align-items: center;
font-size: 12px; color: white;
}
.gl-item:hover { border-color: #1d9bf0; }
.gl-item-text { cursor: pointer; flex: 1; margin-right: 10px; }
.gl-item-text b { display: block; margin-bottom: 2px; }
.gl-item-text span { color: #888; font-size: 10px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.gl-item-actions { display: flex; gap: 5px; }
.gl-icon-btn { background: none; border: none; cursor: pointer; font-size: 14px; color: #8b98a5; padding: 2px; }
.gl-icon-btn:hover { color: white; }
.gl-create-btn {
margin: 10px; padding: 8px; background: #00ba7c; color: white;
text-align: center; border-radius: 6px; cursor: pointer; font-weight: bold; font-size: 12px;
}
.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: #273340; border: 1px solid #38444d; color: white;
padding: 8px; border-radius: 4px; font-size: 12px; width: 100%; box-sizing: border-box;
}
.gl-textarea { flex-grow: 1; resize: none; }
.gl-editor-buttons { display: flex; gap: 10px; margin-top: auto; }
.gl-btn { flex: 1; padding: 8px; border-radius: 6px; border: none; cursor: pointer; font-weight: bold; color: white; }
.gl-btn-save { background: #1d9bf0; }
.gl-btn-cancel { background: #38444d; }
`);
// --- 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 v16.0</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-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.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);
// --- RESIZE LOGIC ---
const resizeHandle = document.getElementById('grok-resize-handle');
let isResizing = false;
let startX, startY, startWidth, startHeight;
function updateModalPosition() {
const pHeight = panel.offsetHeight;
modal.style.bottom = (20 + pHeight + 10) + 'px';
}
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 generateBtn = document.getElementById('btn-generate');
const modalEl = document.getElementById('grok-library-modal');
const listContainer = document.getElementById('gl-list-container');
const createBtn = document.getElementById('btn-create-snippet');
const editLabel = document.getElementById('gl-edit-label');
const editText = document.getElementById('gl-edit-text');
let editingId = null;
// --- HELPER FUNCTIONS ---
function nativeValueSet(el, value) {
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;
updateStatus(msg);
}
function updateStatus(msg, type) {
statusText.textContent = msg;
statusText.className = type === 'error' ? 'status-error' : '';
}
// --- PROMPT SYNC: Panel -> Grok (UNCHANGED) ---
promptBox.addEventListener('input', () => {
const grokTA = document.querySelector(TARGET_TEXTAREA_SELECTOR) ||
document.querySelector(IMAGE_EDITOR_SELECTOR); // UPDATED
if (grokTA) {
lastTypedPrompt = promptBox.value;
nativeValueSet(grokTA, lastTypedPrompt);
resetState("Ready");
}
});
// --- PROMPT SYNC: Grok -> Panel (UPDATED for Image Editor) ---
document.addEventListener('input', (e) => {
if (e.target.matches(TARGET_TEXTAREA_SELECTOR) ||
e.target.matches(IMAGE_EDITOR_SELECTOR)) { // UPDATED
const val = e.target.value;
if (val !== promptBox.value) {
promptBox.value = val;
lastTypedPrompt = val;
}
if (val.trim()) resetState("Ready");
}
}, { capture: true, passive: true });
// --- MANUAL GENERATE (UPDATED for Image Editor) ---
generateBtn.addEventListener('click', () => {
const grokTA = document.querySelector(TARGET_TEXTAREA_SELECTOR) ||
document.querySelector(IMAGE_EDITOR_SELECTOR); // UPDATED
const realBtn = document.querySelector(RETRY_BUTTON_SELECTOR) ||
document.querySelector(IMAGE_EDITOR_BUTTON_SELECTOR); // UPDATED
if (!grokTA || !realBtn) {
updateStatus("Grok elements 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;
}
nativeValueSet(grokTA, promptBox.value);
setTimeout(() => {
if (!realBtn.disabled) {
realBtn.click();
lastGenerationTimestamp = Date.now();
updateStatus("Generation Started...");
} else {
updateStatus("Grok button disabled/processing.", "error");
}
}, 50);
});
// --- MODAL LOGIC (UNCHANGED) ---
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();
});
document.querySelector('.gl-close').addEventListener('click', () => modalEl.classList.remove('active'));
function escapeHtml(text) {
return text ? text.replace(/&/g, "&").replace(/</g, "<") : '';
}
// --- TOGGLE BUTTON (UNCHANGED) ---
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);
});
// --- MUTATION OBSERVER (UNCHANGED - already optimized) ---
const observer = new MutationObserver(() => {
if (observerThrottle || !isRetryEnabled || limitReached || processingModeration) return;
observerThrottle = true;
setTimeout(() => { observerThrottle = false; }, OBSERVER_THROTTLE_MS);
const toastContainer = document.querySelector('section[aria-label="Notifications alt+T"]');
if (toastContainer) {
const errorToast = toastContainer.querySelector('li[data-type="error"]');
if (errorToast && !moderationDetected) {
moderationDetected = true;
setTimeout(() => {
try {
if (errorToast && errorToast.parentNode) {
errorToast.parentNode.removeChild(errorToast);
}
} catch (e) { /* Already removed */ }
}, 2000);
}
}
if (moderationDetected && !processingModeration) {
const spans = document.querySelectorAll('span');
let modSpan = null;
for (let i = 0; i < spans.length; i++) {
if (spans[i].textContent.trim() === MODERATION_TEXT) {
modSpan = spans[i];
break;
}
}
if (modSpan) {
processingModeration = true;
const now = Date.now();
setTimeout(() => {
try {
if (modSpan && modSpan.parentElement && modSpan.parentElement.parentNode) {
modSpan.parentElement.parentNode.removeChild(modSpan.parentElement);
}
} catch (e) { /* Already removed */ }
moderationDetected = false;
processingModeration = false;
}, 2000);
if (now - lastRetryTimestamp >= COOLDOWN_MS && now - lastGenerationTimestamp >= GENERATION_COOLDOWN_MS) {
handleRetry(now);
}
}
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: false
});
// --- RETRY HANDLER (UPDATED for Image Editor) ---
function handleRetry(now) {
if (now - lastGenerationTimestamp < GENERATION_COOLDOWN_MS) return;
lastRetryTimestamp = now;
const grokTA = document.querySelector(TARGET_TEXTAREA_SELECTOR) ||
document.querySelector(IMAGE_EDITOR_SELECTOR); // UPDATED
const btn = document.querySelector(RETRY_BUTTON_SELECTOR) ||
document.querySelector(IMAGE_EDITOR_BUTTON_SELECTOR); // UPDATED
if (grokTA && lastTypedPrompt) {
nativeValueSet(grokTA, lastTypedPrompt);
if (autoClickEnabled && currentRetryCount >= maxRetries) {
updateStatus("Limit Reached", "error");
limitReached = true;
return;
}
if (autoClickEnabled && btn && !btn.disabled) {
currentRetryCount++;
updateStatus(`Retrying (${currentRetryCount}/${maxRetries})`);
setTimeout(() => {
btn.click();
lastGenerationTimestamp = Date.now();
}, RETRY_DELAY_MS);
}
}
}
// --- KEYBOARD TOGGLE (UNCHANGED) ---
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 (UNCHANGED) ---
window.addEventListener('beforeunload', () => {
observer.disconnect();
});
})();