Greasy Fork

Greasy Fork is available in English.

Grok DeMod

Hides moderation results in Grok conversations, auto-recovers blocked messages.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Grok DeMod
// @license      GPL-3.0-or-later
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Hides moderation results in Grok conversations, auto-recovers blocked messages.
// @author       UniverseDev
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=grok.com
// @match        https://grok.com/*
// @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;

            `,
            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;
    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 = (e) => {

            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 = (e) => {

            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 Initialized.");
        if (debug) addLog("Debug mode is ON.");


    }



    async function redownloadLatestMessage() {
        if (!currentConversationId) {
            logDebug('Recovery skipped: Missing conversationId');
            addLog('Recovery failed: No conversation ID.');
            return null;
        }
         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) {

                    logDebug('Fresh header fetch successful (status OK).');

                    initCache = { headers: new Headers({'Accept': 'application/json'}), credentials: 'include' };
                } else {
                     logDebug(`Fresh header fetch failed with status ${tempResp.status}. Recovery may fail.`);
                     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 for conversation: ${currentConversationId}`);
        addLog('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}: ${response.statusText}`);
                addLog(`Recovery failed: HTTP ${response.status}`);

                try {
                    const errorBody = await response.text();
                    logDebug('Recovery error body:', errorBody.substring(0, 500));
                } catch (e) { }
                return null;
            }

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

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


             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() === '') {
                 logDebug('Recovery failed: Latest message or its content is invalid/empty', latestMessage);
                 addLog('Recovery failed: Invalid latest message.');
                 return null;
            }

            logDebug('Recovery successful, latest content:', latestMessage.content.substring(0, 100) + '...');
            addLog('Recovery seems successful.');
            return { content: latestMessage.content };

        } 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;

        if (modResult !== ModerationResult.SAFE) {
            if (modResult === ModerationResult.BLOCKED) {
                logDebug(`Blocked content detected from ${source}:`, JSON.stringify(json).substring(0, 200) + '...');
                addLog(`Blocked content from ${source}.`);
                updateStatus(modResult, true);

                const recoveredData = await redownloadLatestMessage();

                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) {
                        logDebug("Could not find standard key to inject recovered content, adding as 'recovered_content'");
                         finalJson.recovered_content = recoveredData.content;
                    }


                    finalJson = clearFlagging(finalJson);
                    updateStatus(modResult, false);

                } else {

                    addLog(`Recovery failed (${source}). Content may be lost.`);
                    logDebug(`Recovery failed (${source}), applying standard clearing.`);
                    finalJson = clearFlagging(json);
                    updateStatus(modResult, false);
                }
            } 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 (next message safe). 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}`);



        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}`);
             }
         }




        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) {

                                if (buffer.trim()) {
                                     logDebug("SSE stream ended, processing final buffer:", buffer);

                                     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) {
                                             logDebug("Error parsing final SSE buffer, sending as is:", e);
                                             controller.enqueue(encoder.encode(`data: ${buffer}\n\n`));
                                          }
                                      } else {
                                          controller.enqueue(encoder.encode(`data: ${buffer}\n\n`));
                                      }
                                } else if (currentEvent.data) {

                                    logDebug("SSE stream ended after data field, processing event:", currentEvent.data.substring(0,100)+"...");
                                    try {
                                        let json = JSON.parse(currentEvent.data);
                                        json = await processPotentialModeration(json, 'SSE-Event');
                                        controller.enqueue(encoder.encode(`data: ${JSON.stringify(json)}\n\n`));
                                    } catch (e) {
                                        logDebug("Error parsing trailing SSE data, sending as is:", e);
                                        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) {
                                        logDebug("Processing SSE event data:", currentEvent.data.substring(0, 100) + '...');
                                        if (currentEvent.data.startsWith('{') || currentEvent.data.startsWith('[')) {
                                             try {
                                                let json = JSON.parse(currentEvent.data);

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

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

                                                controller.enqueue(encoder.encode(`data: ${JSON.stringify(json)}\n\n`));
                                             } catch(e) {
                                                logError("SSE JSON parse error:", e, "Data:", currentEvent.data.substring(0,200)+"...");

                                                controller.enqueue(encoder.encode(`data: ${currentEvent.data}\n\n`));
                                             }
                                        } else {
                                             logDebug("SSE data is not JSON, forwarding as is.");
                                             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();
                                } else if (line.startsWith(':')) {

                                } else {
                                    logDebug("Unknown SSE line:", line);

                                }
                            }
                        }
                    } catch (e) {
                        logError('Error reading/processing 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
            });
        }


        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, 'URL:', url);

                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) {

            logDebug('Invalid fetch input, passing through:', input, 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;
                     logDebug(`Conversation ID set from POST URL: ${currentConversationId}`);
                 }

                 if (!initCache && requestArgs.headers) {
                      logDebug(`Caching headers from POST request to ${idFromUrl}`);
                      initCache = {
                          headers: new Headers(requestArgs.headers),
                          credentials: requestArgs.credentials || 'include'
                      };
                 }
             }

             return originalFetch.apply(this, arguments);
         }


        logDebug(`Intercepting fetch request: ${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;
        }
    };


    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) {
                                 try {
                                     originalOnMessageHandler.call(ws, event);
                                 } catch (e) {
                                     logError("Error in original WebSocket onmessage handler:", e);
                                 }
                            }
                            return;
                        }

                        logDebug('Intercepting WebSocket message:', event.data.substring(0, 200) + '...');
                        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) {
                                 try {
                                    originalOnMessageHandler.call(ws, newEvent);
                                 } catch (e) {
                                     logError("Error calling original WebSocket onmessage handler after modification:", e);

                                 }
                            } else {
                                logDebug("Original WebSocket onmessage handler not found when message received.");
                            }

                        } catch (e) {
                            logError('WebSocket processing error:', e, 'Data:', event.data.substring(0, 200) + '...');

                            if (originalOnMessageHandler) {
                                 try {
                                    originalOnMessageHandler.call(ws, event);
                                 } catch (eInner) {
                                     logError("Error in original WebSocket onmessage handler (fallback path):", eInner);
                                 }
                            }
                        }
                    };


                    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) {
                        logDebug(`WebSocket on${eventName} handler assigned`);
                        originalHandler = handler;
                        ws.addEventListener(eventName, (event) => {
                             if (eventName === 'message') return;
                             logDebug(`WebSocket event: ${eventName}`, event);
                             if (originalHandler) {
                                 try {
                                     originalHandler.call(ws, event);
                                 } catch (e) {
                                     logError(`Error in original WebSocket on${eventName} handler:`, e);
                                 }
                             }
                        });
                    }
                });
             };

             wrapHandler('close');
             wrapHandler('error');


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

            return ws;
        }
    });




    if (window.location.hostname !== 'grok.com') {
        console.log('[Grok DeMod] Script inactive: Intended for grok.com only. Current host:', window.location.hostname);
        return;
    }


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

        setupUI();
    }

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

})();