Greasy Fork is available in English.

quick toggles

quick toggles for the main rblx page.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

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

})();