// ==UserScript== // @name Grok Enhancer // @namespace https://grok.com/ // @version 2.0 // @description All-in-one Grok enhancement // @author Angel // @homepageURL https://angelmakes.software/ // @source https://github.com/Angel2mp3 // @license MIT // @match https://grok.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=grok.com // @updateURL https://github.com/Angel2mp3/Grok-Enhancer/raw/main/Grok-Enhancer.user.js // @downloadURL https://github.com/Angel2mp3/Grok-Enhancer/raw/main/Grok-Enhancer.user.js // @grant unsafeWindow // @grant GM_xmlhttpRequest // @connect assets.grok.com // @connect imagine-public.x.ai // @run-at document-start // ==/UserScript== (function () { 'use strict'; if (window.location.hostname !== 'grok.com') return; // ══════════════════════════════════════════════════════════════ // Shared Utilities & Globals // ══════════════════════════════════════════════════════════════ const _win = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; const _originalFetch = _win.fetch.bind(_win); const _OriginalWebSocket = _win.WebSocket; const _encoder = new TextEncoder(); const _decoder = new TextDecoder(); // ── Media Database (populated from API interception for downloader) ── const _ge_mediaDatabase = new Map(); function ge_extractPostId(url) { if (!url) return null; const matches = [...url.matchAll(/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/g)]; return matches.length > 0 ? matches[matches.length - 1][0] : null; } function ge_sanitizeFilename(str) { return (str || '').replace(/[/\\?%*:|"<>]/g, '_').replace(/\s+/g, '_'); } function ge_processApiMedia(apiData) { if (!apiData?.posts) return; for (const post of apiData.posts) { if (!post.id) continue; let entry = _ge_mediaDatabase.get(post.id); if (!entry) entry = { id: post.id, items: [] }; function makeItem(src, fallback) { const isVideo = src.mediaType === 'MEDIA_POST_TYPE_VIDEO'; const url = isVideo && src.hdMediaUrl ? src.hdMediaUrl : src.mediaUrl; if (!url) return null; const time = (src.createTime || fallback?.createTime || '').slice(0, 19).replace(/:/g, '-'); const model = src.modelName || fallback?.modelName || ''; const prompt = (src.originalPrompt || src.prompt || fallback?.originalPrompt || fallback?.prompt || '').trim(); let ext = isVideo ? 'mp4' : 'jpg'; if (src.mimeType === 'video/mp4') ext = 'mp4'; else if (src.mimeType === 'image/png') ext = 'png'; else if (src.mimeType === 'image/jpeg') ext = 'jpg'; let slug = ge_sanitizeFilename(prompt); if (slug.length > 120) slug = slug.slice(0, 117) + '...'; return { id: src.id, url, type: isVideo ? 'video' : 'image', ext, name: `${time || 'unknown'}_${src.id}${model ? '_' + ge_sanitizeFilename(model) : ''}${slug ? '_' + slug : ''}.${ext}`, thumb: src.mediaUrl || '', createTime: src.createTime || fallback?.createTime || '', prompt }; } if (post.mediaUrl) { const item = makeItem(post, null); if (item) entry.items.push(item); } if (post.childPosts?.length) { for (const child of post.childPosts) { const item = makeItem(child, post); if (item) entry.items.push(item); } } // Deduplicate by ID const seen = new Set(); entry.items = entry.items.filter(i => { if (seen.has(i.id)) return false; seen.add(i.id); return true; }); if (entry.items.length > 0) { _ge_mediaDatabase.set(post.id, entry); for (const item of entry.items) { if (item.id !== post.id) _ge_mediaDatabase.set(item.id, entry); } } } // Cap the in-memory media database to prevent unbounded growth in long sessions if (_ge_mediaDatabase.size > 2000) { const trimTo = 1000; const keys = [..._ge_mediaDatabase.keys()]; for (let i = 0; i < keys.length - trimTo; i++) _ge_mediaDatabase.delete(keys[i]); logDebug('[Downloader] Media database trimmed to', _ge_mediaDatabase.size, 'entries'); } else { logDebug('[Downloader] Media database now has', _ge_mediaDatabase.size, 'entries'); } } function getState(key, def) { try { const v = localStorage.getItem(key); if (v === null) return def; if (v === 'true') return true; if (v === 'false') return false; return JSON.parse(v); } catch (_) { return def; } } function setState(key, val) { try { localStorage.setItem(key, typeof val === 'boolean' ? String(val) : JSON.stringify(val)); } catch (_) { /* ignore */ } } // ── Feature toggles ────────────────────────────────────────── let featureLogo = getState('GrokEnhancer_Logo', true); let featureLinks = getState('GrokEnhancer_Links', false); let featureDeMod = getState('GrokDeModEnabled', true); let featureRateLimit = getState('GrokEnhancer_RateLimit', true); let featureDebug = getState('GrokDeModDebug', false); let featureHideShare = getState('GrokEnhancer_HideShare', false); let featureHidePopups = getState('GrokEnhancer_HidePopups', false); let featureHidePremium = getState('GrokEnhancer_HidePremium', true); let featureHideHeavy = getState('GrokEnhancer_HideHeavy', false); let featureHideExpert = getState('GrokEnhancer_HideExpert', false); let featureHideAuto = getState('GrokEnhancer_HideAuto', false); let featureHideFollowups = getState('GrokEnhancer_HideFollowups', false); let featureHideBuildNav = getState('GrokEnhancer_HideBuildNav', false); let featureHideImagineNav = getState('GrokEnhancer_HideImagineNav', false); let featureHideSkillsNav = getState('GrokEnhancer_HideSkillsNav', false); let featureAutoPrivate = getState('GrokEnhancer_AutoPrivate', false); let featurePrivacyMode = getState('GrokEnhancer_Streamer', false); let featurePrivacyBlur = getState('GrokEnhancer_PrivacyBlur', false); let featureHideUsername = getState('GrokEnhancer_HideUsername', false); let featureHideEmail = getState('GrokEnhancer_HideEmail', false); let featureHideAvatar = getState('GrokEnhancer_HideAvatar', false); let featureAutoLock = getState('GrokEnhancer_AutoLock', false); let ge_autoLockMinutes = getState('GrokEnhancer_AutoLockMinutes', 5); let featurePinLock = getState('GrokEnhancer_PinLock', false); let ge_activeStyleId = getState('GrokEnhancer_ActiveStyleId', null); // ── Imagine Menu state ── let featureImagineMenu = getState('GrokEnhancer_ImagineMenu', false); let ge_imInterceptOn = getState('GrokEnhancer_IM_Intercept', true); let ge_imVideoLength = parseInt(getState('GrokEnhancer_IM_VideoLength', '30')) || 30; let ge_imAutoRetry = getState('GrokEnhancer_IM_AutoRetry', false); let ge_imMaxRetries = parseInt(getState('GrokEnhancer_IM_MaxRetries', '3')) || 3; let ge_imDisableLoop = getState('GrokEnhancer_IM_DisableLoop', false); let ge_imHideOverlay = getState('GrokEnhancer_IM_HideOverlay', false); let ge_imSmartRetry = getState('GrokEnhancer_IM_SmartRetry', false); let ge_imPersistentPrompt = getState('GrokEnhancer_IM_PersistentPrompt', false); let featureDisableAutoScroll = getState('GrokEnhancer_DisableAutoScroll', false); let ge_imInterceptCount = 0; let ge_imRetryCount = 0; let ge_imLastRetryTime = 0; let ge_imActivePromptId = getState('GrokEnhancer_ActivePromptId', null); // ── Prompt Manager helpers ── function ge_getPrompts() { try { return JSON.parse(localStorage.getItem('GrokEnhancer_Prompts') || '[]'); } catch (_) { return []; } } function ge_savePrompts(p) { localStorage.setItem('GrokEnhancer_Prompts', JSON.stringify(p)); } function logDebug(...a) { if (featureDebug) console.log('[GrokEnhancer]', ...a); } function logError(...a) { console.error('[GrokEnhancer]', ...a); } // ── FAB triple-click hide/show ────────────────────────────── // Hidden state only survives a refresh when featureFabStayHidden is on; otherwise // the FAB always starts visible, even if it was hidden last session. let featureFabStayHidden = getState('GrokEnhancer_FabStayHidden', false); let _ge_fabHidden = featureFabStayHidden && getState('GrokEnhancer_FabHidden', false); let _ge_fabClicks = []; const GE_TRIPLE_CLICK_MS = 500; // max time window for 3 clicks // ══════════════════════════════════════════════════════════════ // 1. SuperGrok Logo Replacement // ══════════════════════════════════════════════════════════════ const SUPERGROK_VIEWBOX = '0 0 149 33'; const SUPERGROK_INNER_HTML = ` `; let logoReplaced = false; let _logoSvgObserver = null; let lastUrl = location.href; const _ge_urlChangeObserver = new MutationObserver(() => { if (location.href !== lastUrl) { lastUrl = location.href; logoReplaced = false; if (featurePrivacyMode) { ge_maskPrivacyTitle(); // New route may have rendered fresh sidebar/command-menu items. ge_rescanPrivacyFull(); } } }); _ge_urlChangeObserver.observe(document, { subtree: true, childList: true }); function isGreetingLogo(svg) { let el = svg.parentElement; while (el && el !== document.body) { if (el.classList && el.classList.contains('max-w-breakout')) return true; el = el.parentElement; } return false; } function tryReplaceLogo() { if (!featureLogo || logoReplaced) return; const svgs = document.querySelectorAll('svg'); for (const svg of svgs) { const markPaths = svg.querySelectorAll('path[id="mark"]'); if (markPaths.length < 2) continue; if (!isGreetingLogo(svg)) continue; if (svg.getAttribute('viewBox') === SUPERGROK_VIEWBOX) { logoReplaced = true; return; } svg.setAttribute('viewBox', SUPERGROK_VIEWBOX); svg.setAttribute('fill-rule', 'evenodd'); svg.setAttribute('clip-rule', 'evenodd'); svg.innerHTML = SUPERGROK_INNER_HTML; logoReplaced = true; // Watch this SVG: if React re-renders and resets it, re-allow replacement if (_logoSvgObserver) _logoSvgObserver.disconnect(); _logoSvgObserver = new MutationObserver(() => { if (svg.getAttribute('viewBox') !== SUPERGROK_VIEWBOX) { logoReplaced = false; } }); _logoSvgObserver.observe(svg, { attributes: true, attributeFilter: ['viewBox'], childList: true }); break; } } // ══════════════════════════════════════════════════════════════ // 2. Clickable Links // ══════════════════════════════════════════════════════════════ const SCAN_RE = /(?"'`\])\}]+|\bwww\.[a-zA-Z0-9\-]+\.[^\s<>"'`\])\}]+|\b(?:[a-zA-Z0-9\-]+\.)+(?:com|org|net|io|dev|app|co|ai|gov|edu|me|info|xyz|biz|name|mobi|pro|tel|jobs|museum|coop|aero|int|travel|post|tech|software|online|site|website|store|shop|blog|cloud|digital|media|network|solutions|services|company|agency|studio|design|systems|consulting|management|marketing|finance|health|care|technology|tools|space|zone|world|life|live|social|community|group|team|global|business|professional|expert|plus|city|land|today|news|press|review|guide|support|help|training|education|academy|institute|center|foundation|ventures|capital|partners|holdings|works|build|engineering|energy|eco|farm|food|restaurant|bar|hotel|tours|rentals|properties|estate|homes|auto|cars|sports|fitness|art|gallery|photography|video|music|show|film|events|party|fun|games|game|play|dating|love|wedding|family|kids|pet|clinic|dental|doctor|pharmacy|insurance|loans|credit|bank|money|pay|law|attorney|legal|security|repair|cleaning|run|link|click|host|page|web|email|uk|ca|au|de|fr|jp|ru|br|in|it|es|nl|se|no|fi|dk|pl|pt|be|ch|at|nz|mx|ar|sg|hk|tw|kr|za|ie|cz|hu|ro|gr|th|vn|ph|id|my|ng|ke|gg|re|tv|cc|so|is|ee|lv|lt|sk|si|hr|rs|bg|mk|al|ba|md|ge|am|az|by|kz|ua|uz|mn|af|pk|bd|lk|np|mm|kh|la|bn|pg|fj|ws|to|vu|ki|fm|pw|mh|nr|sb|eu|us|gb|il|tr|sa|ae|eg|ma|li|lu|mo|mt|cy|gh|tz|sn|cm|ao|zw|mu|bw|na|ls|sz|rw|sd|ly|dz|cd|ga|gq|cv|sl|lr|gn|bf|ml|gm|mz|sc|sh|je|im|gi|gt|bz|sv|hn|ni|cr|pa|pe|cl|bo|uy|py|ec|tt|jm|cu|do|ht|dm|bb|lc|vc|gd|ag|kn|pr|vi|ky|bm|aw|gp|mq|nc|pf|as|gu|ck|nu|tk|nf|cx|sj|gl|pm|yt|ax|fo)(?:\/[^\s<>"'`\])\}]*)?\b/gi; // Platform context detection for @mention link routing const PLATFORM_PATTERNS = [ { re: /\b(instagram|insta)\b/i, url: u => `https://instagram.com/${u}` }, { re: /\b(tiktok|tik\s*tok|\bTT\b)\b/i, url: u => `https://tiktok.com/@${u}` }, { re: /\b(snapchat|snap)\b/i, url: u => `https://snapchat.com/add/${u}` }, { re: /\b(bluesky|bsky\.app)\b/i, url: u => `https://bsky.app/profile/${u}` }, { re: /\b(threads\.net|threads)\b/i, url: u => `https://threads.net/@${u}` }, { re: /\btwitch\b/i, url: u => `https://twitch.tv/${u}` }, { re: /\bkick\.com|\bkick\b/i, url: u => `https://kick.com/${u}` }, { re: /\byoutube\b/i, url: u => `https://youtube.com/@${u}` }, { re: /\b(facebook|fb\.com)\b/i, url: u => `https://facebook.com/${u}` }, { re: /\blinkedin\b/i, url: u => `https://linkedin.com/in/${u}` }, { re: /\b(github|gh\b)\b/i, url: u => `https://github.com/${u}` }, { re: /\b(telegram|t\.me)\b/i, url: u => `https://t.me/${u}` }, { re: /\bsoundcloud\b/i, url: u => `https://soundcloud.com/${u}` }, { re: /\bspotify\b/i, url: u => `https://open.spotify.com/user/${u}` }, { re: /\bmedium\b/i, url: u => `https://medium.com/@${u}` }, { re: /\bsubstack\b/i, url: u => `https://${u}.substack.com` }, { re: /\bpatreon\b/i, url: u => `https://patreon.com/${u}` }, { re: /\bko-?fi\b/i, url: u => `https://ko-fi.com/${u}` }, { re: /\bvsco\b/i, url: u => `https://vsco.co/${u}` }, { re: /\bpinterest\b/i, url: u => `https://pinterest.com/${u}` }, { re: /\btumblr\b/i, url: u => `https://tumblr.com/${u}` }, { re: /\breddit\b/i, url: u => `https://reddit.com/user/${u}` }, { re: /\bmastodon\b/i, url: u => `https://mastodon.social/@${u}` }, { re: /\bdiscord\b/i, url: u => `https://discord.com/users/${u}` }, { re: /\b(x\.com|twitter|tweet|retweet|x account|on x)\b/i, url: u => `https://x.com/${u}` }, ]; function getMentionHref(user, text, start, textNode) { const WIN = 150; const mentionStr = '@' + user; // Use the full block element text so that context in sibling nodes is included const ctxEl = textNode?.parentElement?.closest('p,li,div,article,section,blockquote,td') || textNode?.parentElement; const fullText = (ctxEl && ctxEl.textContent.length > text.length) ? ctxEl.textContent : text; // Find where this text node sits inside the block text, then add the local match offset let baseOffset = 0; if (fullText !== text) { const idx = fullText.indexOf(text); if (idx !== -1) baseOffset = idx; } const mentionIdx = baseOffset + start; const winStart = Math.max(0, mentionIdx - WIN); const winEnd = Math.min(fullText.length, mentionIdx + mentionStr.length + WIN); const win = fullText.slice(winStart, winEnd); const mentionPos = mentionIdx - winStart; const atEnd = mentionPos + mentionStr.length; let bestPlatform = null; let bestDist = Infinity; for (const p of PLATFORM_PATTERNS) { const re = new RegExp(p.re.source, 'gi'); let m; while ((m = re.exec(win)) !== null) { const kwEnd = m.index + m[0].length; const dist = kwEnd <= mentionPos ? mentionPos - kwEnd : m.index >= atEnd ? m.index - atEnd : 0; if (dist < bestDist) { bestDist = dist; bestPlatform = p; } } } return bestPlatform ? bestPlatform.url(user) : 'https://x.com/' + user; } const SKIP_TAGS = new Set([ 'A', 'SCRIPT', 'STYLE', 'CODE', 'PRE', 'TEXTAREA', 'INPUT', 'SELECT', 'BUTTON', 'NOSCRIPT', 'IFRAME', 'OBJECT', 'SVG', ]); const PROCESSED_ATTR = 'data-linkified'; function linkifyNode(root) { if (!featureLinks) return; const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, { acceptNode(node) { let el = node.parentElement; while (el) { if (SKIP_TAGS.has(el.tagName)) return NodeFilter.FILTER_REJECT; if (el.hasAttribute(PROCESSED_ATTR)) return NodeFilter.FILTER_REJECT; el = el.parentElement; } if (!node.nodeValue || node.nodeValue.trim().length < 4) return NodeFilter.FILTER_SKIP; return NodeFilter.FILTER_ACCEPT; } }); const textNodes = []; let n; while ((n = walker.nextNode())) textNodes.push(n); for (const textNode of textNodes) { const text = textNode.nodeValue; SCAN_RE.lastIndex = 0; if (!SCAN_RE.test(text)) continue; SCAN_RE.lastIndex = 0; const frag = document.createDocumentFragment(); let lastIndex = 0, match; while ((match = SCAN_RE.exec(text)) !== null) { const full = match[0], mentionUser = match[1], start = match.index; if (start > lastIndex) frag.appendChild(document.createTextNode(text.slice(lastIndex, start))); const a = document.createElement('a'); if (mentionUser) { a.href = getMentionHref(mentionUser, text, start, textNode); } else { a.href = /^https?:\/\//i.test(full) ? full : 'https://' + full; } a.textContent = full; a.target = '_blank'; a.rel = 'noopener noreferrer'; a.className = 'ge-link'; a.style.cssText = 'text-decoration:underline;cursor:pointer;'; frag.appendChild(a); lastIndex = start + full.length; } if (lastIndex < text.length) frag.appendChild(document.createTextNode(text.slice(lastIndex))); const parent = textNode.parentElement; if (parent) parent.setAttribute(PROCESSED_ATTR, '1'); textNode.parentNode.replaceChild(frag, textNode); } } // ══════════════════════════════════════════════════════════════ // 3. DeMod — Moderation Bypass // ══════════════════════════════════════════════════════════════ const DEMOD_CONFIG = { defaultFlags: ['isFlagged', 'isBlocked', 'moderationApplied', 'restricted'], messageKeys: ['message', 'content', 'text', 'error'], moderationMessagePatterns: [ /this content has been moderated/i, /sorry, i cannot assist/i, /policy violation/i, /blocked/i, /moderated/i, /restricted/i, /content restricted/i, /unable to process/i, /cannot help/i, /(sorry|apologies).*?(cannot|unable|help|assist)/i, ], clearedMessageText: '[Content cleared by Grok DeMod]', recoveryTimeoutMs: 5000, statusColors: { safe: '#66ff66', flagged: '#ffa500', blocked: '#ff6666', recovering: '#ffcc00', }, }; let moderationFlags = getState('GrokDeModFlags', DEMOD_CONFIG.defaultFlags); let demodInitCache = null; let currentConversationId = null; const ModerationResult = Object.freeze({ SAFE: 0, FLAGGED: 1, BLOCKED: 2 }); function timeoutPromise(ms, promise, desc = 'Promise') { return new Promise((resolve, reject) => { const timer = setTimeout(() => reject(new Error(`Timeout (${desc})`)), ms); promise.then(v => { clearTimeout(timer); resolve(v); }, e => { clearTimeout(timer); reject(e); }); }); } function getModerationResult(obj, path = '') { if (typeof obj !== 'object' || obj === null) return ModerationResult.SAFE; let result = ModerationResult.SAFE; for (const key in obj) { if (!obj.hasOwnProperty(key)) continue; const cp = path ? `${path}.${key}` : key; const value = obj[key]; if (key === 'isBlocked' && value === true) { logDebug(`Blocked: '${cp}'`); return ModerationResult.BLOCKED; } if (moderationFlags.includes(key) && value === true) { logDebug(`Flagged: '${cp}'`); result = Math.max(result, ModerationResult.FLAGGED); } if (DEMOD_CONFIG.messageKeys.includes(key) && typeof value === 'string') { const c = value.toLowerCase(); for (const p of DEMOD_CONFIG.moderationMessagePatterns) { if (p.test(c)) { if (/blocked|moderated|restricted/i.test(p.source)) return ModerationResult.BLOCKED; result = Math.max(result, ModerationResult.FLAGGED); break; } } if (result === ModerationResult.SAFE && c.length < 70 && /(sorry|apologies|unable|cannot)/i.test(c)) result = Math.max(result, ModerationResult.FLAGGED); } if (typeof value === 'object') { const cr = getModerationResult(value, cp); if (cr === ModerationResult.BLOCKED) return ModerationResult.BLOCKED; result = Math.max(result, cr); } } return result; } function clearFlagging(obj) { if (typeof obj !== 'object' || obj === null) return obj; if (Array.isArray(obj)) return obj.map(clearFlagging); const out = {}; for (const key in obj) { if (!obj.hasOwnProperty(key)) continue; const v = obj[key]; if (moderationFlags.includes(key) && v === true) { out[key] = false; } else if (DEMOD_CONFIG.messageKeys.includes(key) && typeof v === 'string') { let replaced = false; for (const p of DEMOD_CONFIG.moderationMessagePatterns) { if (p.test(v)) { out[key] = DEMOD_CONFIG.clearedMessageText; replaced = true; break; } } if (!replaced && v.length < 70 && /(sorry|apologies|unable|cannot)/i.test(v.toLowerCase())) { if (getModerationResult({ [key]: v }) === ModerationResult.FLAGGED) { out[key] = DEMOD_CONFIG.clearedMessageText; replaced = true; } } if (!replaced) out[key] = v; } else if (typeof v === 'object') out[key] = clearFlagging(v); else out[key] = v; } return out; } function extractConversationIdFromUrl(url) { const m = url.match(/\/conversation\/([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})/i); return m ? m[1] : null; } async function redownloadLatestMessage() { if (!currentConversationId) { panelAddLog('Recovery failed: No conversation ID.'); return null; } if (!demodInitCache || !demodInitCache.headers) { try { const r = await _originalFetch(`/rest/app-chat/conversation/${currentConversationId}`, { method: 'GET', headers: { 'Accept': 'application/json' } }); if (r.ok) demodInitCache = { headers: new Headers({ 'Accept': 'application/json' }), credentials: 'include' }; else { panelAddLog('Recovery failed: Cannot get request data.'); return null; } } catch (_) { panelAddLog('Recovery failed: Error getting request data.'); return null; } } panelAddLog('Attempting content recovery...'); const headers = new Headers(demodInitCache.headers); if (!headers.has('Accept')) headers.set('Accept', 'application/json, text/plain, */*'); try { const resp = await timeoutPromise(DEMOD_CONFIG.recoveryTimeoutMs, _originalFetch(`/rest/app-chat/conversation/${currentConversationId}`, { method: 'GET', headers, credentials: demodInitCache.credentials || 'include' }), 'Recovery Fetch'); if (!resp.ok) { panelAddLog(`Recovery failed: HTTP ${resp.status}`); return null; } const data = await resp.json(); const msgs = data?.messages; if (!Array.isArray(msgs) || msgs.length === 0) { panelAddLog('Recovery failed: No messages found.'); return null; } msgs.sort((a, b) => (b.timestamp ? new Date(b.timestamp).getTime() : 0) - (a.timestamp ? new Date(a.timestamp).getTime() : 0)); const latest = msgs[0]; if (!latest || typeof latest.content !== 'string' || !latest.content.trim()) { panelAddLog('Recovery failed: Invalid latest message.'); return null; } panelAddLog('Recovery seems successful.'); return { content: latest.content }; } catch (e) { panelAddLog(`Recovery error: ${e.message}`); return null; } } async function processPotentialModeration(json, source) { const mr = getModerationResult(json); let out = json; if (mr !== ModerationResult.SAFE) { if (mr === ModerationResult.BLOCKED) { panelAddLog(`Blocked content from ${source}.`); panelUpdateStatus(mr, true); const recovered = await redownloadLatestMessage(); if (recovered && recovered.content) { panelAddLog(`Recovery successful (${source}).`); let replaced = false; for (const k of [...DEMOD_CONFIG.messageKeys, 'text', 'message']) { if (typeof out[k] === 'string') { out[k] = recovered.content; replaced = true; break; } } if (!replaced) out.recovered_content = recovered.content; out = clearFlagging(out); panelUpdateStatus(mr, false); } else { panelAddLog(`Recovery failed (${source}).`); out = clearFlagging(json); panelUpdateStatus(mr, false); } } else { panelAddLog(`Flagged content cleared (${source}).`); out = clearFlagging(json); panelUpdateStatus(mr); } } else { if (panelStatusEl && !panelStatusEl.textContent.includes('Blocked') && !panelStatusEl.textContent.includes('Flagged') && !panelStatusEl.textContent.includes('Recovering')) panelUpdateStatus(mr); else if (panelStatusEl && panelStatusEl.textContent.includes('Recovering')) panelUpdateStatus(ModerationResult.SAFE); } return out; } // Tracks the active conversation ID from either the initial GET request // or the first response that reveals it, and caches the auth headers // needed later for the DeMod recovery re-fetch. function ge_trackConversationId(url, requestArgs) { const convGetMatch = url.match(/\/rest\/app-chat\/conversation\/([a-f0-9-]+)$/i); if (convGetMatch && requestArgs?.method === 'GET') { demodInitCache = { headers: new Headers(requestArgs.headers), credentials: requestArgs.credentials || 'include' }; if (!currentConversationId) currentConversationId = convGetMatch[1]; } if (!currentConversationId) { const id = extractConversationIdFromUrl(url); if (id) currentConversationId = id; } } function ge_handleSSEResponse(response) { const reader = response.body.getReader(); const stream = new ReadableStream({ async start(controller) { let buffer = ''; let currentEvent = { data: '', type: 'message', id: null }; try { while (true) { const { done, value } = await reader.read(); if (done) { if (buffer.trim() && (buffer.startsWith('{') || buffer.startsWith('['))) { try { let j = JSON.parse(buffer); j = await processPotentialModeration(j, 'SSE-Final'); controller.enqueue(_encoder.encode(`data: ${JSON.stringify(j)}\n\n`)); } catch (_) { controller.enqueue(_encoder.encode(`data: ${buffer}\n\n`)); } } else if (buffer.trim()) { controller.enqueue(_encoder.encode(`data: ${buffer}\n\n`)); } else if (currentEvent.data) { try { let j = JSON.parse(currentEvent.data); j = await processPotentialModeration(j, 'SSE-Event'); controller.enqueue(_encoder.encode(`data: ${JSON.stringify(j)}\n\n`)); } catch (_) { controller.enqueue(_encoder.encode(`data: ${currentEvent.data}\n\n`)); } } controller.close(); break; } buffer += _decoder.decode(value, { stream: true }); let lines = buffer.split('\n'); buffer = lines.pop() || ''; for (const line of lines) { if (line.trim() === '') { if (currentEvent.data) { if (currentEvent.data.startsWith('{') || currentEvent.data.startsWith('[')) { try { let j = JSON.parse(currentEvent.data); if (j.conversation_id && !currentConversationId) currentConversationId = j.conversation_id; j = await processPotentialModeration(j, 'SSE'); controller.enqueue(_encoder.encode(`data: ${JSON.stringify(j)}\n\n`)); } catch (_) { controller.enqueue(_encoder.encode(`data: ${currentEvent.data}\n\n`)); } } else { controller.enqueue(_encoder.encode(`data: ${currentEvent.data}\n\n`)); } } currentEvent = { data: '', type: 'message', id: null }; } else if (line.startsWith('data:')) { currentEvent.data += (currentEvent.data ? '\n' : '') + line.substring(5).trim(); } else if (line.startsWith('event:')) { currentEvent.type = line.substring(6).trim(); } else if (line.startsWith('id:')) { currentEvent.id = line.substring(3).trim(); } } } } catch (e) { controller.error(e); } finally { reader.releaseLock(); } } }); return new Response(stream, { status: response.status, statusText: response.statusText, headers: new Headers(response.headers) }); } async function ge_handleJSONResponse(response, original_response) { try { const text = await response.text(); let json = JSON.parse(text); if (json.conversation_id && !currentConversationId) currentConversationId = json.conversation_id; json = await processPotentialModeration(json, 'Fetch'); const body = JSON.stringify(json); const nh = new Headers(response.headers); if (nh.has('content-length')) nh.set('content-length', _encoder.encode(body).byteLength.toString()); return new Response(body, { status: response.status, statusText: response.statusText, headers: nh }); } catch (_) { return original_response; } } async function handleFetchResponse(original_response, url, requestArgs) { const response = original_response.clone(); if (!response.ok) return original_response; const ct = response.headers.get('Content-Type')?.toLowerCase() || ''; ge_trackConversationId(url, requestArgs); if (ct.includes('text/event-stream')) return ge_handleSSEResponse(response); if (ct.includes('application/json')) return ge_handleJSONResponse(response, original_response); return original_response; } // ── Install fetch interceptor ──────────────────────────────── _win.fetch = async function (input, init) { let url; let requestArgs = init || {}; const isReqObj = (input instanceof Request); try { url = isReqObj ? input.url : String(input); } catch (_) { return _originalFetch.apply(this, arguments); } // Resolve method: init overrides Request properties const method = requestArgs.method || (isReqObj ? input.method : undefined); // ── Custom Response Style: prepend instructions to the user message ── const isChatPost = method === 'POST' && url.includes('/rest/app-chat/conversation'); // ── Rate Limit: detect model from outgoing chat request for local usage tracking ── const isRateLimitTrackable = method === 'POST' && (url.includes('/rest/app-chat/conversations/new') || (url.includes('/responses') && url.includes('/rest/app-chat/conversations/'))); let rl_pendingModel = null; if (featureRateLimit && isRateLimitTrackable) { try { let rlBodyText = null; if (typeof requestArgs.body === 'string') rlBodyText = requestArgs.body; else if (isReqObj) { const rlClone = input.clone(); rlBodyText = await rlClone.text(); } if (rlBodyText) rl_pendingModel = rl_getModelFromBody(JSON.parse(rlBodyText)); } catch (_) {} } if (ge_activeStyleId && isChatPost) { try { const styles = ge_getCustomStyles(); const activeStyle = styles.find(s => s.id === ge_activeStyleId); if (activeStyle) { let bodyText = null; if (typeof requestArgs.body === 'string') { bodyText = requestArgs.body; } else if (isReqObj) { const cloned = input.clone(); bodyText = await cloned.text(); } if (bodyText) { const json = JSON.parse(bodyText); // Find the user message field and prepend style instructions const msgKey = ['message', 'content', 'text', 'prompt'].find(k => typeof json[k] === 'string'); if (msgKey) { json[msgKey] = '[Follow these response-style instructions for this and all subsequent replies in this conversation: ' + activeStyle.instructions + ']\n\n' + json[msgKey]; } const newBody = JSON.stringify(json); if (isReqObj) { input = new Request(input, { body: newBody }); requestArgs = init || {}; } else { requestArgs = { ...requestArgs, body: newBody }; } logDebug('[CustomStyle] Injected "' + activeStyle.name + '" into ' + url); } } } catch (err) { console.warn('[GrokEnhancer] CustomStyle inject error:', err); } } // ── Imagine Menu: Video length override ── if (featureImagineMenu && ge_imInterceptOn && isChatPost) { try { let bodyText2 = null; if (typeof requestArgs.body === 'string') { bodyText2 = requestArgs.body; } else if (isReqObj) { const cloned2 = input.clone(); bodyText2 = await cloned2.text(); } if (bodyText2) { const json2 = JSON.parse(bodyText2); const hasVideoGen = json2.toolOverrides?.videoGen !== undefined; const hasVideoConfig = json2.responseMetadata?.modelConfigOverride?.modelMap?.videoGenModelConfig; if (hasVideoGen || hasVideoConfig) { if (!json2.responseMetadata) json2.responseMetadata = {}; if (!json2.responseMetadata.modelConfigOverride) json2.responseMetadata.modelConfigOverride = {}; if (!json2.responseMetadata.modelConfigOverride.modelMap) json2.responseMetadata.modelConfigOverride.modelMap = {}; if (!json2.responseMetadata.modelConfigOverride.modelMap.videoGenModelConfig) json2.responseMetadata.modelConfigOverride.modelMap.videoGenModelConfig = {}; const cfg = json2.responseMetadata.modelConfigOverride.modelMap.videoGenModelConfig; const oldLen = cfg.videoLength; cfg.videoLength = ge_imVideoLength; ge_imInterceptCount++; logDebug(`[ImagineMenu] Video length ${oldLen || 'default'} → ${ge_imVideoLength} (#${ge_imInterceptCount})`); const newBody2 = JSON.stringify(json2); if (isReqObj) { input = new Request(input, { body: newBody2 }); requestArgs = init || {}; } else { requestArgs = { ...requestArgs, body: newBody2 }; } ge_updateImStatus(); } // ── Imagine Menu: Prompt injection ── if (ge_imActivePromptId) { const prompts = ge_getPrompts(); const ap = prompts.find(p => p.id === ge_imActivePromptId); if (ap && ap.text) { const msgK = ['message', 'content', 'text', 'prompt'].find(k => typeof json2[k] === 'string'); if (msgK && !json2[msgK].includes(ap.text)) { json2[msgK] = ap.text + '\n\n' + json2[msgK]; logDebug('[ImagineMenu] Injected prompt:', ap.name); } // Clear active prompt if not auto-retry if (!ge_imAutoRetry) { ge_imActivePromptId = null; setState('GrokEnhancer_ActivePromptId', null); ge_updateImActiveLabel(); } const nb = JSON.stringify(json2); if (isReqObj) { input = new Request(input, { body: nb }); requestArgs = init || {}; } else { requestArgs = { ...requestArgs, body: nb }; } } } } } catch (err2) { console.warn('[GrokEnhancer] ImagineMenu intercept error:', err2); } } // ── Media API intercept for downloader database ── if (url.includes('/rest/media/post/list')) { const resp = await _originalFetch.call(this, input, requestArgs); try { const clone = resp.clone(); const data = await clone.json(); ge_processApiMedia(data); } catch (e) { logError('[Downloader] API intercept error:', e); } return resp; } if (!featureDeMod) { const p0 = _originalFetch.call(this, input, requestArgs); if (rl_pendingModel) rl_trackUsageAndLimit(rl_pendingModel, p0); return p0; } if (!url.includes('/rest/app-chat/')) return _originalFetch.call(this, input, requestArgs); if (method === 'POST') { const id = extractConversationIdFromUrl(url); if (id) { if (!currentConversationId) currentConversationId = id; const hdrs = requestArgs.headers || (isReqObj ? input.headers : null); if (!demodInitCache && hdrs) demodInitCache = { headers: new Headers(hdrs), credentials: requestArgs.credentials || (isReqObj ? input.credentials : 'include') }; } const p1 = _originalFetch.call(this, input, requestArgs); if (rl_pendingModel) rl_trackUsageAndLimit(rl_pendingModel, p1); return p1; } try { const resp = await _originalFetch.call(this, input, requestArgs); return await handleFetchResponse(resp, url, requestArgs); } catch (e) { throw e; } }; // ── Install WebSocket interceptor ──────────────────────────── _win.WebSocket = new Proxy(_OriginalWebSocket, { construct(target, args) { const ws = new target(...args); let originalOnMessageHandler = null; Object.defineProperty(ws, 'onmessage', { configurable: true, enumerable: true, get() { return originalOnMessageHandler; }, async set(handler) { originalOnMessageHandler = handler; ws.onmessageinternal = async function (event) { if (!featureDeMod || typeof event.data !== 'string' || !event.data.startsWith('{')) { if (originalOnMessageHandler) try { originalOnMessageHandler.call(ws, event); } catch (_) { } return; } try { let json = JSON.parse(event.data); if (json.conversation_id && json.conversation_id !== currentConversationId) currentConversationId = json.conversation_id; const processed = await processPotentialModeration(json, 'WebSocket'); const ne = new MessageEvent('message', { data: JSON.stringify(processed), origin: event.origin, lastEventId: event.lastEventId, source: event.source, ports: event.ports }); if (originalOnMessageHandler) try { originalOnMessageHandler.call(ws, ne); } catch (_) { } } catch (_) { if (originalOnMessageHandler) try { originalOnMessageHandler.call(ws, event); } catch (_2) { } } }; ws.addEventListener('message', ws.onmessageinternal); } }); const wrapHandler = (eventName) => { let oh = null; Object.defineProperty(ws, `on${eventName}`, { configurable: true, enumerable: true, get() { return oh; }, set(handler) { oh = handler; ws.addEventListener(eventName, (e) => { if (eventName === 'message') return; if (oh) try { oh.call(ws, e); } catch (_) { } }); } }); }; wrapHandler('close'); wrapHandler('error'); return ws; } }); // Shared "get-or-create a