quick toggles

quick toggles for the main rblx page.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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

})();