Grok DeMod

Hides mod results in Grok conversations, auto-recovers messages.

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
    // @name         Grok DeMod
    // @license      MIT
    // @namespace    http://tampermonkey.net/
    // @version      1.1
    // @description  Hides mod results in Grok conversations, auto-recovers messages.
    // @author       UniverseDev
    // @icon         https://www.google.com/s2/favicons?sz=64&domain=grok.com
    // @match        https://grok.com/*
    // @match        https://x.ai/*
    // @grant        unsafeWindow
    // ==/UserScript==

    (function() {
        'use strict';

        const 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,
            lsKeys: {
                enabled: 'GrokDeModEnabled',
                debug: 'GrokDeModDebug',
                flags: 'GrokDeModFlags',
            },
            styles: {
                uiContainer: `
                    position: fixed;
                    bottom: 10px;
                    right: 10px;
                    z-index: 10000;
                    background: #2d2d2d;
                    padding: 10px;
                    border-radius: 8px;
                    box-shadow: 0 4px 8px rgba(0,0,0,0.2);
                    display: flex;
                    flex-direction: column;
                    gap: 8px;
                    font-family: Arial, sans-serif;
                    color: #e0e0e0;
                    min-width: 170px;
                    max-width: 260px;
                `,
                button: `
                    padding: 6px 12px;
                    border-radius: 5px;
                    border: none;
                    cursor: pointer;
                    color: #fff;
                    font-size: 13px;
                    transition: background-color 0.2s ease;
                `,
                status: `
                    padding: 5px;
                    font-size: 12px;
                    color: #a0a0a0;
                    text-align: center;
                    border-top: 1px solid #444;
                    margin-top: 5px;
                    min-height: 16px;
                `,
                logContainer: `
                    max-height: 100px;
                    overflow-y: auto;
                    font-size: 11px;
                    color: #c0c0c0;
                    background-color: #333;
                    padding: 5px;
                    border-radius: 4px;
                    line-height: 1.4;
                    margin-top: 5px;
                `,
                logEntry: `
                    padding-bottom: 3px;
                    border-bottom: 1px dashed #555;
                    margin-bottom: 3px;
                    word-break: break-word;
                `,
                colors: {
                    enabled: '#388E3C',
                    disabled: '#D32F2F',
                    debugEnabled: '#1976D2',
                    debugDisabled: '#555555',
                    safe: '#66ff66',
                    flagged: '#ffa500',
                    blocked: '#ff6666',
                    recovering: '#ffcc00'
                }
            }
        };

        let demodEnabled = getState(CONFIG.lsKeys.enabled, true);
        let debug = getState(CONFIG.lsKeys.debug, false);
        let moderationFlags = getState(CONFIG.lsKeys.flags, CONFIG.defaultFlags);
        let initCache = null;
        let currentConversationId = null;
        let pendingModeratedMessageId = null;           // ← v1.1 improvement
        const encoder = new TextEncoder();
        const decoder = new TextDecoder();
        const uiLogBuffer = [];
        const MAX_LOG_ENTRIES = 50;

        const ModerationResult = Object.freeze({
            SAFE: 0,
            FLAGGED: 1,
            BLOCKED: 2,
        });

        function logDebug(...args) {
            if (debug) {
                console.log('[Grok DeMod]', ...args);
            }
        }

        function logError(...args) {
            console.error('[Grok DeMod]', ...args);
        }

        function getState(key, defaultValue) {
            try {
                const value = localStorage.getItem(key);
                if (value === null) return defaultValue;
                if (value === 'true') return true;
                if (value === 'false') return false;
                return JSON.parse(value);
            } catch (e) {
                logError(`Error reading ${key} from localStorage:`, e);
                return defaultValue;
            }
        }

        function setState(key, value) {
            try {
                const valueToStore = typeof value === 'boolean' ? value.toString() : JSON.stringify(value);
                localStorage.setItem(key, valueToStore);
            } catch (e) {
                logError(`Error writing ${key} to localStorage:`, e);
            }
        }

        function timeoutPromise(ms, promise, description = 'Promise') {
            return new Promise((resolve, reject) => {
                const timer = setTimeout(() => {
                    logDebug(`${description} timed out after ${ms}ms`);
                    reject(new Error(`Timeout (${description})`));
                }, ms);
                promise.then(
                    (value) => { clearTimeout(timer); resolve(value); },
                    (error) => { clearTimeout(timer); reject(error); }
                );
            });
        }

        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 currentPath = path ? `${path}.${key}` : key;
                const value = obj[key];

                if (key === 'isBlocked' && value === true) {
                    logDebug(`Blocked detected via flag '${currentPath}'`);
                    return ModerationResult.BLOCKED;
                }

                if (moderationFlags.includes(key) && value === true) {
                    logDebug(`Flagged detected via flag '${currentPath}'`);
                    result = Math.max(result, ModerationResult.FLAGGED);
                }

                if (CONFIG.messageKeys.includes(key) && typeof value === 'string') {
                    const content = value.toLowerCase();
                    for (const pattern of CONFIG.moderationMessagePatterns) {
                        if (pattern.test(content)) {
                            logDebug(`Moderation pattern matched in '${currentPath}': "${content.substring(0, 50)}..."`);

                            if (/blocked|moderated|restricted/i.test(pattern.source)) {
                                return ModerationResult.BLOCKED;
                            }
                            result = Math.max(result, ModerationResult.FLAGGED);
                            break;
                        }
                    }

                    if (result === ModerationResult.SAFE && content.length < 70 && /(sorry|apologies|unable|cannot)/i.test(content)) {
                        logDebug(`Heuristic moderation detected in '${currentPath}': "${content.substring(0, 50)}..."`);
                        result = Math.max(result, ModerationResult.FLAGGED);
                    }
                }

                if (typeof value === 'object') {
                    const childResult = getModerationResult(value, currentPath);
                    if (childResult === ModerationResult.BLOCKED) {
                        return ModerationResult.BLOCKED;
                    }
                    result = Math.max(result, childResult);
                }
            }
            return result;
        }

        function clearFlagging(obj) {
            if (typeof obj !== 'object' || obj === null) return obj;

            if (Array.isArray(obj)) {
                return obj.map(item => clearFlagging(item));
            }

            const newObj = {};
            for (const key in obj) {
                if (!obj.hasOwnProperty(key)) continue;

                const value = obj[key];

                if (moderationFlags.includes(key) && value === true) {
                    newObj[key] = false;
                    logDebug(`Cleared flag '${key}'`);
                }
                else if (CONFIG.messageKeys.includes(key) && typeof value === 'string') {
                    let replaced = false;
                    for (const pattern of CONFIG.moderationMessagePatterns) {
                        if (pattern.test(value)) {
                            newObj[key] = CONFIG.clearedMessageText;
                            logDebug(`Replaced moderated message in '${key}' using pattern`);
                            replaced = true;
                            break;
                        }
                    }

                    if (!replaced && value.length < 70 && /(sorry|apologies|unable|cannot)/i.test(value.toLowerCase())) {
                        if (getModerationResult({[key]: value}) === ModerationResult.FLAGGED) {
                            newObj[key] = CONFIG.clearedMessageText;
                            logDebug(`Replaced heuristic moderated message in '${key}'`);
                            replaced = true;
                        }
                    }

                    if (!replaced) {
                        newObj[key] = value;
                    }
                }
                else if (typeof value === 'object') {
                    newObj[key] = clearFlagging(value);
                }
                else {
                    newObj[key] = value;
                }
            }
            return newObj;
        }

        let uiContainer, toggleButton, debugButton, statusEl, logContainer;

        function addLog(message) {
            if (!logContainer) return;
            const timestamp = new Date().toLocaleTimeString();
            const logEntry = document.createElement('div');
            logEntry.textContent = `[${timestamp}] ${message}`;
            logEntry.style.cssText = CONFIG.styles.logEntry;

            uiLogBuffer.push(logEntry);
            if (uiLogBuffer.length > MAX_LOG_ENTRIES) {
                const removed = uiLogBuffer.shift();
                if (removed && removed.parentNode === logContainer) {
                    logContainer.removeChild(removed);
                }
            }

            logContainer.appendChild(logEntry);
            logContainer.scrollTop = logContainer.scrollHeight;
        }

        function updateStatus(modResult, isRecovering = false) {
            if (!statusEl) return;
            let text = 'Status: ';
            let color = CONFIG.styles.colors.safe;

            if (isRecovering) {
                text += 'Recovering...';
                color = CONFIG.styles.colors.recovering;
            } else if (modResult === ModerationResult.BLOCKED) {
                text += 'Blocked (Recovered/Cleared)';
                color = CONFIG.styles.colors.blocked;
            } else if (modResult === ModerationResult.FLAGGED) {
                text += 'Flagged (Cleared)';
                color = CONFIG.styles.colors.flagged;
            } else {
                text += 'Safe';
                color = CONFIG.styles.colors.safe;
            }
            statusEl.textContent = text;
            statusEl.style.color = color;
        }

        function setupUI() {
            uiContainer = document.createElement('div');
            uiContainer.id = 'grok-demod-ui';
            uiContainer.style.cssText = CONFIG.styles.uiContainer;

            toggleButton = document.createElement('button');
            debugButton = document.createElement('button');
            statusEl = document.createElement('div');
            logContainer = document.createElement('div');

            toggleButton.textContent = demodEnabled ? 'DeMod: ON' : 'DeMod: OFF';
            toggleButton.title = 'Toggle DeMod functionality (ON = intercepting)';
            toggleButton.style.cssText = CONFIG.styles.button;
            toggleButton.style.backgroundColor = demodEnabled ? CONFIG.styles.colors.enabled : CONFIG.styles.colors.disabled;
            toggleButton.onclick = () => {
                demodEnabled = !demodEnabled;
                setState(CONFIG.lsKeys.enabled, demodEnabled);
                toggleButton.textContent = demodEnabled ? 'DeMod: ON' : 'DeMod: OFF';
                toggleButton.style.backgroundColor = demodEnabled ? CONFIG.styles.colors.enabled : CONFIG.styles.colors.disabled;
                addLog(`DeMod ${demodEnabled ? 'Enabled' : 'Disabled'}.`);
                console.log('[Grok DeMod] Interception is now', demodEnabled ? 'ACTIVE' : 'INACTIVE');
            };

            debugButton.textContent = debug ? 'Debug: ON' : 'Debug: OFF';
            debugButton.title = 'Toggle debug mode (logs verbose details to console)';
            debugButton.style.cssText = CONFIG.styles.button;
            debugButton.style.backgroundColor = debug ? CONFIG.styles.colors.debugEnabled : CONFIG.styles.colors.debugDisabled;
            debugButton.onclick = () => {
                debug = !debug;
                setState(CONFIG.lsKeys.debug, debug);
                debugButton.textContent = debug ? 'Debug: ON' : 'Debug: OFF';
                debugButton.style.backgroundColor = debug ? CONFIG.styles.colors.debugEnabled : CONFIG.styles.colors.debugDisabled;
                addLog(`Debug Mode ${debug ? 'Enabled' : 'Disabled'}.`);
                logDebug(`Debug mode ${debug ? 'enabled' : 'disabled'}.`);
            };

            statusEl.id = 'grok-demod-status';
            statusEl.style.cssText = CONFIG.styles.status;
            updateStatus(ModerationResult.SAFE);

            logContainer.id = 'grok-demod-log';
            logContainer.style.cssText = CONFIG.styles.logContainer;

            uiLogBuffer.forEach(entry => logContainer.appendChild(entry));
            logContainer.scrollTop = logContainer.scrollHeight;

            uiContainer.appendChild(toggleButton);
            uiContainer.appendChild(debugButton);
            uiContainer.appendChild(statusEl);
            uiContainer.appendChild(logContainer);
            document.body.appendChild(uiContainer);

            addLog("Grok DeMod v1.1 Initialized.");
            if (debug) addLog("Debug mode is ON.");
        }

        // ──────────────────────────────────────────────────────────────
        // v1.1: Smarter recovery – tries to target the exact message ID
        // ──────────────────────────────────────────────────────────────
        async function recoverMessageContent(targetMessageId = null) {
            if (!currentConversationId) {
                logDebug('Recovery skipped: Missing conversationId');
                addLog('Recovery failed: No conversation ID.');
                return null;
            }

            // Ensure we have headers cached
            if (!initCache || !initCache.headers) {
                logDebug('Recovery cache missing, attempting fresh fetch for headers...');
                try {
                    const currentConvUrl = `/rest/app-chat/conversation/${currentConversationId}`;
                    const tempResp = await originalFetch(currentConvUrl, { method: 'GET', headers: {'Accept': 'application/json'} });
                    if (tempResp.ok) {
                        initCache = { headers: new Headers({'Accept': 'application/json'}), credentials: 'include' };
                        logDebug('Fresh header fetch successful.');
                    } else {
                        logDebug(`Fresh header fetch failed with status ${tempResp.status}.`);
                        addLog('Recovery failed: Cannot get request data.');
                        return null;
                    }
                } catch (e) {
                    logError('Error during fresh header fetch:', e);
                    addLog('Recovery failed: Error getting request data.');
                    return null;
                }
            }

            const url = `/rest/app-chat/conversation/${currentConversationId}`;
            logDebug(`Attempting recovery fetch${targetMessageId ? ` for message ID: ${targetMessageId}` : ''}`);
            addLog(targetMessageId ? 'Attempting targeted recovery...' : 'Attempting content recovery...');

            const headers = new Headers(initCache.headers);
            if (!headers.has('Accept')) headers.set('Accept', 'application/json, text/plain, */*');

            const requestOptions = {
                method: 'GET',
                headers: headers,
                credentials: initCache.credentials || 'include',
            };

            try {
                const response = await timeoutPromise(
                    CONFIG.recoveryTimeoutMs,
                    fetch(url, requestOptions),
                    'Recovery Fetch'
                );

                if (!response.ok) {
                    logError(`Recovery fetch failed with status ${response.status}`);
                    addLog(`Recovery failed: HTTP ${response.status}`);
                    return null;
                }

                const data = await response.json();
                const messages = data?.messages;

                if (!Array.isArray(messages) || messages.length === 0) {
                    logDebug('Recovery failed: No messages found');
                    addLog('Recovery failed: No messages found.');
                    return null;
                }

                let recoveredContent = null;

                // v1.1: Prefer exact message ID match if we have one
                if (targetMessageId) {
                    const targetMsg = messages.find(m =>
                        m.id === targetMessageId ||
                        m.message_id === targetMessageId ||
                        (m._id && m._id === targetMessageId)
                    );
                    if (targetMsg && typeof targetMsg.content === 'string' && targetMsg.content.trim() !== '') {
                        recoveredContent = targetMsg.content;
                        logDebug('Targeted message recovery successful');
                    }
                }

                // Fallback: latest message (original behavior)
                if (!recoveredContent) {
                    messages.sort((a, b) => {
                        const tsA = a.timestamp ? new Date(a.timestamp).getTime() : 0;
                        const tsB = b.timestamp ? new Date(b.timestamp).getTime() : 0;
                        return tsB - tsA;
                    });
                    const latestMessage = messages[0];
                    if (latestMessage && typeof latestMessage.content === 'string' && latestMessage.content.trim() !== '') {
                        recoveredContent = latestMessage.content;
                        logDebug('Fallback (latest message) recovery successful');
                    }
                }

                if (recoveredContent) {
                    addLog('Recovery successful.');
                    return { content: recoveredContent };
                }

                logDebug('Recovery failed: No valid content found');
                addLog('Recovery failed: Invalid message content.');
                return null;

            } catch (e) {
                logError('Recovery fetch/parse error:', e);
                addLog(`Recovery error: ${e.message}`);
                return null;
            }
        }

        function extractConversationIdFromUrl(url) {
            const match = 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 match ? match[1] : null;
        }

        async function processPotentialModeration(json, source) {
            const modResult = getModerationResult(json);
            let finalJson = json;

            // v1.1: Capture message ID when we detect a block
            if (modResult === ModerationResult.BLOCKED) {
                // Try common ID fields
                pendingModeratedMessageId =
                    json.id ||
                    json.message_id ||
                    json._id ||
                    (json.messages && json.messages[0] && (json.messages[0].id || json.messages[0].message_id)) ||
                    null;
                if (pendingModeratedMessageId) {
                    logDebug(`Captured moderated message ID for targeted recovery: ${pendingModeratedMessageId}`);
                }
            }

            if (modResult !== ModerationResult.SAFE) {
                if (modResult === ModerationResult.BLOCKED) {
                    logDebug(`Blocked content detected from ${source}`);
                    addLog(`Blocked content from ${source}.`);
                    updateStatus(modResult, true);

                    const recoveredData = await recoverMessageContent(pendingModeratedMessageId);

                    if (recoveredData && recoveredData.content) {
                        addLog(`Recovery successful (${source}).`);
                        logDebug(`Recovered content applied (${source})`);

                        let replaced = false;
                        const keysToTry = [...CONFIG.messageKeys, 'text', 'message'];
                        for (const key of keysToTry) {
                            if (typeof finalJson[key] === 'string') {
                                finalJson[key] = recoveredData.content;
                                logDebug(`Injected recovered content into key '${key}'`);
                                replaced = true;
                                break;
                            }
                        }
                        if (!replaced) {
                            finalJson.recovered_content = recoveredData.content;
                        }

                        finalJson = clearFlagging(finalJson);
                        updateStatus(modResult, false);
                    } else {
                        addLog(`Recovery failed (${source}). Content may be lost.`);
                        finalJson = clearFlagging(json);
                        updateStatus(modResult, false);
                    }

                    // Reset after attempt
                    pendingModeratedMessageId = null;

                } else {
                    logDebug(`Flagged content detected and cleared from ${source}.`);
                    addLog(`Flagged content cleared (${source}).`);
                    finalJson = clearFlagging(json);
                    updateStatus(modResult);
                }
            } else {
                if (statusEl && !statusEl.textContent.includes('Blocked') && !statusEl.textContent.includes('Flagged') && !statusEl.textContent.includes('Recovering')) {
                    updateStatus(modResult);
                } else if (statusEl && statusEl.textContent.includes('Recovering')) {
                    logDebug("Recovery attempt finished. Resetting status.");
                    updateStatus(ModerationResult.SAFE);
                }
            }
            return finalJson;
        }

        async function handleFetchResponse(original_response, url, requestArgs) {
            const response = original_response.clone();

            if (!response.ok) {
                logDebug(`Fetch response not OK (${response.status}) for ${url}, skipping processing.`);
                return original_response;
            }

            const contentType = response.headers.get('Content-Type')?.toLowerCase() || '';
            logDebug(`Intercepted fetch response for ${url}, Content-Type: ${contentType}`);

            // Cache conversation GET for recovery headers
            const conversationGetMatch = url.match(/\/rest\/app-chat\/conversation\/([a-f0-9-]+)/i);
            if (conversationGetMatch && requestArgs?.method === 'GET') {
                logDebug(`Caching GET request options for conversation ${conversationGetMatch[1]}`);
                initCache = {
                    headers: new Headers(requestArgs.headers),
                    credentials: requestArgs.credentials || 'include'
                };
                if (!currentConversationId) {
                    currentConversationId = conversationGetMatch[1];
                    logDebug(`Conversation ID set from GET URL: ${currentConversationId}`);
                }
            }

            if (!currentConversationId) {
                const idFromUrl = extractConversationIdFromUrl(url);
                if (idFromUrl) {
                    currentConversationId = idFromUrl;
                    logDebug(`Conversation ID set from other URL: ${currentConversationId}`);
                }
            }

            // ───── SSE (text/event-stream) ─────
            if (contentType.includes('text/event-stream')) {
                logDebug(`Processing SSE stream for ${url}`);
                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) {
                                    // final flush
                                    if (buffer.trim()) {
                                        if (buffer.startsWith('{') || buffer.startsWith('[')) {
                                            try {
                                                let json = JSON.parse(buffer);
                                                json = await processPotentialModeration(json, 'SSE-Final');
                                                controller.enqueue(encoder.encode(`data: ${JSON.stringify(json)}\n\n`));
                                            } catch(e) {
                                                controller.enqueue(encoder.encode(`data: ${buffer}\n\n`));
                                            }
                                        } else {
                                            controller.enqueue(encoder.encode(`data: ${buffer}\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 json = JSON.parse(currentEvent.data);
                                                    if (json.conversation_id && !currentConversationId) {
                                                        currentConversationId = json.conversation_id;
                                                    }
                                                    json = await processPotentialModeration(json, 'SSE');
                                                    controller.enqueue(encoder.encode(`data: ${JSON.stringify(json)}\n\n`));
                                                } catch(e) {
                                                    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) {
                            logError('Error reading SSE stream:', e);
                            controller.error(e);
                        } finally {
                            reader.releaseLock();
                        }
                    }
                });

                const newHeaders = new Headers(response.headers);
                return new Response(stream, {
                    status: response.status,
                    statusText: response.statusText,
                    headers: newHeaders
                });
            }

            // ───── Plain JSON ─────
            if (contentType.includes('application/json')) {
                logDebug(`Processing JSON response for ${url}`);
                try {
                    const text = await response.text();
                    let json = JSON.parse(text);

                    if (json.conversation_id && !currentConversationId) {
                        currentConversationId = json.conversation_id;
                        logDebug(`Conversation ID updated from JSON response: ${currentConversationId}`);
                    }

                    json = await processPotentialModeration(json, 'Fetch');

                    const newBody = JSON.stringify(json);
                    const newHeaders = new Headers(response.headers);
                    if (newHeaders.has('content-length')) {
                        newHeaders.set('content-length', encoder.encode(newBody).byteLength.toString());
                    }

                    return new Response(newBody, {
                        status: response.status,
                        statusText: response.statusText,
                        headers: newHeaders
                    });
                } catch (e) {
                    logError('Fetch JSON processing error:', e);
                    return original_response;
                }
            }

            logDebug(`Non-SSE/JSON response for ${url}, skipping processing.`);
            return original_response;
        }

        const originalFetch = unsafeWindow.fetch;

        unsafeWindow.fetch = async function(input, init) {
            if (!demodEnabled) {
                return originalFetch.apply(this, arguments);
            }

            let url;
            let requestArgs = init || {};

            try {
                url = (input instanceof Request) ? input.url : String(input);
            } catch (e) {
                return originalFetch.apply(this, arguments);
            }

            if (!url.includes('/rest/app-chat/')) {
                return originalFetch.apply(this, arguments);
            }

            if (requestArgs.method === 'POST') {
                logDebug(`Observing POST request: ${url}`);
                const idFromUrl = extractConversationIdFromUrl(url);
                if (idFromUrl) {
                    if (!currentConversationId) currentConversationId = idFromUrl;
                    if (!initCache && requestArgs.headers) {
                        initCache = {
                            headers: new Headers(requestArgs.headers),
                            credentials: requestArgs.credentials || 'include'
                        };
                    }
                }
                return originalFetch.apply(this, arguments);
            }

            logDebug(`Intercepting fetch: ${requestArgs.method || 'GET'} ${url}`);

            try {
                const original_response = await originalFetch.apply(this, arguments);
                return await handleFetchResponse(original_response, url, requestArgs);
            } catch (error) {
                logError(`Fetch interception failed for ${url}:`, error);
                throw error;
            }
        };

        // WebSocket interception (unchanged – still solid)
        const OriginalWebSocket = unsafeWindow.WebSocket;
        unsafeWindow.WebSocket = new Proxy(OriginalWebSocket, {
            construct(target, args) {
                const url = args[0];
                logDebug('WebSocket connection attempt:', url);

                const ws = new target(...args);

                let originalOnMessageHandler = null;

                Object.defineProperty(ws, 'onmessage', {
                    configurable: true,
                    enumerable: true,
                    get() { return originalOnMessageHandler; },
                    async set(handler) {
                        logDebug('WebSocket onmessage handler assigned');
                        originalOnMessageHandler = handler;

                        ws.onmessageinternal = async function(event) {
                            if (!demodEnabled || typeof event.data !== 'string' || !event.data.startsWith('{')) {
                                if (originalOnMessageHandler) originalOnMessageHandler.call(ws, event);
                                return;
                            }

                            logDebug('Intercepting WebSocket message');
                            try {
                                let json = JSON.parse(event.data);

                                if (json.conversation_id && json.conversation_id !== currentConversationId) {
                                    currentConversationId = json.conversation_id;
                                    logDebug(`Conversation ID updated from WebSocket: ${currentConversationId}`);
                                }

                                const processedJson = await processPotentialModeration(json, 'WebSocket');

                                const newEvent = new MessageEvent('message', {
                                    data: JSON.stringify(processedJson),
                                    origin: event.origin,
                                    lastEventId: event.lastEventId,
                                    source: event.source,
                                    ports: event.ports,
                                });

                                if (originalOnMessageHandler) {
                                    originalOnMessageHandler.call(ws, newEvent);
                                }
                            } catch (e) {
                                logError('WebSocket processing error:', e);
                                if (originalOnMessageHandler) originalOnMessageHandler.call(ws, event);
                            }
                        };

                        ws.addEventListener('message', ws.onmessageinternal);
                    }
                });

                const wrapHandler = (eventName) => {
                    let originalHandler = null;
                    Object.defineProperty(ws, `on${eventName}`, {
                        configurable: true,
                        enumerable: true,
                        get() { return originalHandler; },
                        set(handler) {
                            originalHandler = handler;
                            ws.addEventListener(eventName, (event) => {
                                if (eventName === 'message') return;
                                if (originalHandler) originalHandler.call(ws, event);
                            });
                        }
                    });
                };
                wrapHandler('close');
                wrapHandler('error');

                ws.addEventListener('open', () => logDebug('WebSocket opened:', url));

                return ws;
            }
        });

        // Final init
        if (!['grok.com', 'x.ai'].includes(window.location.hostname)) {
            console.log('[Grok DeMod] Script inactive: Intended for grok.com / x.ai only.');
            return;
        }

        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', setupUI);
        } else {
            setupUI();
        }

        console.log('[Grok DeMod] v1.1 loaded successfully. Interception is', demodEnabled ? 'ACTIVE' : 'INACTIVE', '. Debug is', debug ? 'ON' : 'OFF');
    })();