quick toggles for the main rblx page.
// ==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);
})();