Grok Streamer Mode

Blurs sensitive information to protect your privacy on grok.com.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name         Grok Streamer Mode
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Blurs sensitive information to protect your privacy on grok.com.
// @author       Ported by Blanklspeaker, Adapted from original plugin by Prism (https://github.com/imjustprism/Grokness)
// @match        https://grok.com/*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const logger = {
        error: (...args) => console.error('[StreamerMode]', ...args)
    };

    let styleElement = null;

    const baseStyles = `
.streamer-mode-active img.aspect-square.h-full.w-full[alt="pfp"] {
    filter: blur(8px) !important;
}

.streamer-mode-active .sidebar-user-info .display-name,
.streamer-mode-active [data-sidebar="menu"] .group\\/conversation-item span.flex-1.select-none,
.streamer-mode-active span.flex-1.select-none.text-nowrap.max-w-full.overflow-hidden.inline-block {
    filter: blur(5px) !important;
    padding: 0 4px !important;
    margin: 0 -4px !important;
    display: inline-block !important;
    width: calc(100% + 8px) !important;
    position: relative !important;
}

.streamer-mode-active .p-1.min-w-0.text-sm .text-sm.font-medium,
.streamer-mode-active .p-1.min-w-0.text-sm .text-secondary.truncate {
    filter: blur(5px) !important;
    padding: 0 4px !important;
    margin: 0 -4px !important;
    display: inline-block !important;
    width: calc(100% + 8px) !important;
    position: relative !important;
}

.streamer-mode-active div.text-xs.flex.flex-row.gap-1.p-3.text-secondary.opacity-30 {
    filter: blur(5px) !important;
    padding: 0 4px !important;
    margin: 0 -4px !important;
    display: inline-block !important;
    width: calc(100% + 8px) !important;
    position: relative !important;
}

.streamer-mode-active #grok-feature-flags-menu-item {
    filter: blur(5px) !important;
}
    `;

    function getDynamicStyles(emailOnly) {
        if (emailOnly) {
            return `
html.streamer-mode-active .p-1.min-w-0.text-sm .text-secondary.truncate {
    filter: blur(5px) !important;
    padding: 0 4px !important;
    margin: 0 -4px !important;
    display: inline-block !important;
    width: calc(100% + 8px) !important;
    position: relative !important;
}

html.streamer-mode-active div.text-xs.flex.flex-row.gap-1.p-3.text-secondary.opacity-30 {
    filter: blur(5px) !important;
    padding: 0 4px !important;
    margin: 0 -4px !important;
    display: inline-block !important;
    width: calc(100% + 8px) !important;
    position: relative !important;
}
            `;
        }
        return baseStyles.replace(/\.streamer-mode-active/g, 'html.streamer-mode-active');
    }

    function updateStylesheetAndClass() {
        try {
            const isEnabled = GM_getValue('enabled', true);
            const emailOnly = GM_getValue('emailOnly', false);

            if (styleElement) {
                styleElement.remove();
                styleElement = null;
            }

            document.documentElement.classList.remove('streamer-mode-active');

            let dynamicStyles = '';

            if (emailOnly) {
                dynamicStyles = getDynamicStyles(true);
                document.documentElement.classList.add('streamer-mode-active');
            } else if (isEnabled) {
                dynamicStyles = getDynamicStyles(false);
                document.documentElement.classList.add('streamer-mode-active');
            }

            if (dynamicStyles) {
                styleElement = GM_addStyle(dynamicStyles);
            }
        } catch (err) {
            logger.error('Failed to update stylesheet or class:', err);
        }
    }

    function createToggle(initialChecked, labelId) {
        const button = document.createElement('button');
        button.type = 'button';
        button.role = 'switch';
        button.className = 'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-[1px] border-transparent transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background ring-card-border ring-1 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-dove dark:data-[state=checked]:bg-ivory dark:data-[state=unchecked]:bg-button-secondary-selected';
        button.value = 'on';
        if (labelId) {
            button.setAttribute('aria-labelledby', labelId);
        }

        const span = document.createElement('span');
        span.className = 'pointer-events-none block h-4 w-4 rounded-full bg-white dark:bg-background shadow-lg ring-0 transition-transform data-[state=unchecked]:translate-x-0 ms-0.5 data-[state=checked]:translate-x-5 rtl:data-[state=checked]:-translate-x-5 dark:data-[state=unchecked]:bg-overlay';

        button.appendChild(span);

        function setChecked(checked) {
            button.setAttribute('aria-checked', checked ? 'true' : 'false');
            button.dataset.state = checked ? 'checked' : 'unchecked';
            span.dataset.state = checked ? 'checked' : 'unchecked';
        }

        setChecked(initialChecked);

        button.addEventListener('click', () => {
            const current = button.getAttribute('aria-checked') === 'true';
            setChecked(!current);
        });

        return button;
    }

    function injectSettings() {
        const targetLabelText = 'Show Conversation Previews in History';
        const observer = new MutationObserver((mutations) => {
            const labels = document.querySelectorAll('div.text-sm.font-medium');
            for (let label of labels) {
                if (label.innerText.trim() === targetLabelText) {
                    const labelWrapper = label.parentElement; // max-w-sm min-w-0
                    const previewRow = labelWrapper.parentElement; // flex flex-row ...
                    if (previewRow && !document.getElementById('streamer-mode-row')) {
                        const container = previewRow.parentElement; // flex-col gap-6

                        // Create row for Streamer Mode
                        const streamerRow = document.createElement('div');
                        streamerRow.className = previewRow.className;
                        streamerRow.id = 'streamer-mode-row';

                        const streamerLabelWrapper = document.createElement('div');
                        streamerLabelWrapper.className = labelWrapper.className;
                        const streamerLabelId = 'streamer-mode-label';
                        streamerLabelWrapper.id = streamerLabelId;

                        const streamerLabel = document.createElement('div');
                        streamerLabel.className = 'text-sm font-medium';
                        streamerLabel.innerText = 'Streamer Mode';

                        streamerLabelWrapper.appendChild(streamerLabel);
                        streamerRow.appendChild(streamerLabelWrapper);

                        const streamerToggleWrapper = document.createElement('div');
                        streamerToggleWrapper.className = 'text-right min-w-24';

                        const streamerToggle = createToggle(GM_getValue('enabled', true), streamerLabelId);
                        streamerToggle.addEventListener('click', () => {
                            const checked = streamerToggle.getAttribute('aria-checked') === 'true';
                            GM_setValue('enabled', checked);
                            updateStylesheetAndClass();
                        });

                        streamerToggleWrapper.appendChild(streamerToggle);
                        streamerRow.appendChild(streamerToggleWrapper);

                        container.insertBefore(streamerRow, previewRow.nextSibling);

                        // Create row for Blur Email Only
                        const emailRow = document.createElement('div');
                        emailRow.className = previewRow.className;
                        emailRow.id = 'email-only-row';

                        const emailLabelWrapper = document.createElement('div');
                        emailLabelWrapper.className = labelWrapper.className;
                        const emailLabelId = 'email-only-label';
                        emailLabelWrapper.id = emailLabelId;

                        const emailLabel = document.createElement('div');
                        emailLabel.className = 'text-sm font-medium';
                        emailLabel.innerText = 'Blur Email Only';

                        emailLabelWrapper.appendChild(emailLabel);
                        emailRow.appendChild(emailLabelWrapper);

                        const emailToggleWrapper = document.createElement('div');
                        emailToggleWrapper.className = 'text-right min-w-24';

                        const emailToggle = createToggle(GM_getValue('emailOnly', false), emailLabelId);
                        emailToggle.addEventListener('click', () => {
                            const checked = emailToggle.getAttribute('aria-checked') === 'true';
                            GM_setValue('emailOnly', checked);
                            updateStylesheetAndClass();
                        });

                        emailToggleWrapper.appendChild(emailToggle);
                        emailRow.appendChild(emailToggleWrapper);

                        container.insertBefore(emailRow, streamerRow.nextSibling);
                    }
                }
            }
        });

        observer.observe(document.body, { childList: true, subtree: true });
    }

    // Run the update function and inject settings
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => {
            updateStylesheetAndClass();
            injectSettings();
        }, { once: true });
    } else {
        updateStylesheetAndClass();
        injectSettings();
    }

    // Expose functions to toggle and configure via console
    window.toggleStreamerMode = function() {
        const currentEnabled = GM_getValue('enabled', true);
        GM_setValue('enabled', !currentEnabled);
        updateStylesheetAndClass();
        console.log('[StreamerMode] Enabled:', !currentEnabled);
    };

    window.setStreamerModeEmailOnly = function(value) {
        GM_setValue('emailOnly', !!value);
        updateStylesheetAndClass();
        console.log('[StreamerMode] Email Only:', !!value);
    };

    console.log('[StreamerMode] Loaded. Use toggleStreamerMode() in console to toggle on/off. Use setStreamerModeEmailOnly(true/false) to toggle blurring only email.');
})();