// ==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