quick toggles

quick toggles for the main rblx page.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         quick toggles
// @namespace    http://tampermonkey.net/
// @version      2.2
// @description  quick toggles for the main rblx page.
// @homepage     https://lachlanm05.com
// @author       lachlanm05
// @match        https://www.roblox.com/home*
// @grant        none
// @license      MIT
// ==/UserScript==

// works like you'd expect.
// confused on how it works? just chuck it into any ai chatbot
// and it'll explain it better than anything i could write.

(function() {
    'use strict';

    // grab the initial security token from the page's meta tags. 
    // roblox requires this token to prove the request is coming from a real browser.
    function getInitialCsrfToken() {
        const tokenMeta = document.querySelector('meta[name="csrf-token"]');
        return tokenMeta ? tokenMeta.getAttribute('data-token') : '';
    }

    // global variables to track the active token and prevent the UI from fighting the user
    let activeCsrfToken = getInitialCsrfToken();
    let isUpdating = false;

    // this reads
    async function syncSettingsUI() {
        // if the user just clicked a switch, pause syncing so the boxes don't rubber-band.
        if (isUpdating) return;

        try {
            // we append a timestamp to the URL to completely defeat the browser cache,
            // forcing it to fetch the freshest data every single time.
            const bypassCacheUrl = 'https://apis.roblox.com/user-settings-api/v1/user-settings/settings-and-options?_=' + Date.now();
            const response = await fetch(bypassCacheUrl, {
                method: 'GET',
                credentials: 'include',
                headers: {
                    'Cache-Control': 'no-store, no-cache, must-revalidate',
                    'Pragma': 'no-cache'
                }
            });

            if (response.ok) {
                const data = await response.json();
                
                // Read the hidden API format (data.setting.currentValue) and update UI
                const onlineToggle = document.getElementById('toggle-online');
                if (onlineToggle && data.whoCanSeeMyOnlineStatus) {
                    onlineToggle.checked = (data.whoCanSeeMyOnlineStatus.currentValue === 'AllUsers');
                }

                const expToggle = document.getElementById('toggle-experience');
                if (expToggle && data.whoCanJoinMeInExperiences) {
                    expToggle.checked = (data.whoCanJoinMeInExperiences.currentValue === 'Followers');
                }
            } else {
                console.warn('Roblox Privacy Toggles: failed to sync. sorry. status: ' + response.status);
            }
        } catch (error) {
            console.error('Roblox Privacy Toggles: error fetching settings. status: ', error);
        }
    }

    // we write.
    // sends the new stuff.
    async function updatePrivacySetting(settingKey, settingValue) {
        if (!activeCsrfToken) {
            console.error('Roblox Privacy Toggles: couldnt find initial CSRF token');
            return;
        }

        // lock the UI so the 5-second polling doesn't overwrite our visual changes
        isUpdating = true;
        const payload = {};
        payload[settingKey] = settingValue;

        // wrapper for the fetch request so we can easily retry it if the token is stale
        async function makeRequest() {
            return fetch('https://apis.roblox.com/user-settings-api/v1/user-settings', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json;charset=utf-8',
                    'X-CSRF-TOKEN': activeCsrfToken
                },
                credentials: 'include', // ensures your .ROBLOSECURITY cookie is sent
                body: JSON.stringify(payload)
            });
        }

        try {
            let response = await makeRequest();

            // we shake hands :3
            // thankfully in a 403, we do actually get a new one.
            // then we can go take it and use it.
            if (!response.ok && response.status === 403 && response.headers.has('x-csrf-token')) {
                activeCsrfToken = response.headers.get('x-csrf-token');
                response = await makeRequest();
            }

            if (response.ok) {
                // very cool.
                // then unlock the UI and force a fresh read to confirm everything is synced.
                setTimeout(() => {
                    isUpdating = false;
                    syncSettingsUI();
                }, 1500);
            } else {
                // unlock if the thing fails
                isUpdating = false;
            }
        } catch (error) {
            isUpdating = false;
        }
    }

    // build
    function injectUI() {
        // thanks ai for telling me how to write html in js in a userscript.
        // along with css. practically how html and css work in js in a userscript.
        // gemini said that just making a function with const container and const htmlElements
        // would work. wow, how cool! /s
        // eh, it's better than vibe coding this function. human powered slop ftw.
        // all terrible ui colors and design was chosen by me.
        // thanks gemini-sensei.
      
      
        // create main floating box
        const container = document.createElement('div');
        container.style.position = 'fixed';
        container.style.bottom = '20px';
        container.style.right = '20px';
        container.style.backgroundColor = '#232527';
        container.style.padding = '15px';
        container.style.borderRadius = '8px';
        container.style.boxShadow = '0 4px 6px rgba(0,0,0,0.5)';
        container.style.color = '#fff';
        container.style.zIndex = '9999';
        container.style.fontFamily = 'Gotham, "Helvetica Neue", Helvetica, Arial, sans-serif';

        // we make html elements.
        const htmlElements = [
            '<div style="font-weight: bold; margin-bottom: 12px; font-size: 14px; text-align: center; border-bottom: 1px solid #393b3d; padding-bottom: 5px;">quick</div>',
            '<div style="display: flex; align-items: center; margin-bottom: 10px;">',
                '<input type="checkbox" id="toggle-online" style="margin-right: 8px; cursor: pointer;">',
                '<label for="toggle-online" style="font-size: 13px; cursor: pointer; user-select: none;">Show Online Status</label>',
            '</div>',
            '<div style="display: flex; align-items: center;">',
                '<input type="checkbox" id="toggle-experience" style="margin-right: 8px; cursor: pointer;">',
                '<label for="toggle-experience" style="font-size: 13px; cursor: pointer; user-select: none;">Show Current Experience</label>',
            '</div>'
        ];
        
        container.innerHTML = htmlElements.join('');
        document.body.appendChild(container);

        // event
        // update on click
        document.getElementById('toggle-online').addEventListener('change', (e) => {
            const value = e.target.checked ? 'AllUsers' : 'NoOne';
            updatePrivacySetting('whoCanSeeMyOnlineStatus', value);
        });

        document.getElementById('toggle-experience').addEventListener('change', (e) => {
            const value = e.target.checked ? 'Followers' : 'NoOne';
            updatePrivacySetting('whoCanJoinMeInExperiences', value);
        });

        // run an init scan then every 10 seconds
        syncSettingsUI();
        setInterval(syncSettingsUI, 10000); // this is ms, future me.
    }

    // wait for dear react to load.
    setTimeout(injectUI, 1500);

})();