// ==UserScript==
// @name RoLocate
// @namespace https://oqarshi.github.io/
// @version 32.3
// @description Adds filter options to roblox server page. Alternative to paid extensions like RoPro, RoGold (Ultimate), RoQol, and RoKit.
// @author Oqarshi
// @match https://www.roblox.com/*
// @license CC-BY-4.0; https://creativecommons.org/licenses/by/4.0/
// @icon data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSgBBwcHCggKEwoKEygaFhooKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKP/AABEIAEAAQAMBEQACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AOE714B+/wDUO9AdQoABQCExxTF0FNIbDHSgAxzQHUTjPSmLQv6HYJqmr21i9zHa+e4QSyA7VJ6Zx6nj8acY8ztc58VX+r0pVVHmsr2W503jH4e3/hWK1uLy4hltJpPLeaJWIiPuPpn8q1qUJU7Ns8vLM+o5i5QpxakleztqdBB8GdRnhSWHV7B4pFDKyqxDA8gjitfqknrc82XF1CDcZUpJr0HH4Kart41SxJ91f/Cj6nLuT/rjhv8An2/wOb8TfDfxBoFu9zNbx3Vqgy8ts2/aPUggHHvjFZTw84anq4LiHBYyShF8sn0en/AOM444rE9vQOM9KA0uL3pD6m34P0N/EWrvp8LbJ2gkkiPYuoyAfrjH41pThzuyODMccsDS9tJaXSfo2e2+CNSh8ceDrzQ9cDfb7Zfs9yrff4+7J9QR+Y9676UlVg4S3Pgs0w8spxscXhvglqu3mvT9GeT+JdV17RI4PDlxdXMEmlySKskUrJ5kbbSvQ8jgkezY7VxzlOHuN7H2OBw2ExbljYxTVRLRpOzV7/8AB9DCXxFrS4K6vqII7i5f/Gs/aS7ne8BhWtaUfuR6r8HfHOpajq/9iazO12ssbNDLJy4KjJUnuCM9fSuvDVpSfLI+R4kyWhQo/WsPHls9UttevlqcN8V9Fh0LxpdQWiBLaZVuI0HRQ2cge2QawxEFCbSPf4fxs8ZgoznrJaP5f8A5DvWB7fUO9Aa3O8+CP/JQLX/rjL/6Ca6ML/ER87xT/wAi+XqvzN/x1qv/AAh3xbTUrGPak0KPdRr0lDEhvx4B+ozWlWXsq3Mjzspwv9qZQ6FR6pvlfa239djf+L3h6HxJ4cg8RaRiWaCISFkH+tgPP5r1/OtcRTU488TzuHMfPAYmWBr6Ju3pL/g7fceERwyyr+6jd+3yqTzXn2P0GU1Fas9Z+Cvg/UoteTW9QtpLW2gRhEJVKtIzDHAPOACefpXZhqUubmZ8fxPm1CWHeFpSUpNq9uiWv3nM/GLVotW8cXJtnDxWyLbBhyCVyW/UkfhWWJkpTdj1eG8LPDYGPPo5Nv79vwOJ71znvdQ70B1O8+CP/JQbX/rjL/6Ca6ML/ER87xT/AMi+XqvzO28T6fb6r8arSxvU328+nMjr7FJOR7jrW84qVdJ9jwsBXnh8jlWpuzU0/wAYlj4a30/hzXb3wVrT7grNJZSN0dTyVHsRzj13CqoycJOlL5GWd0IY7DwzXD9dJLs/+Bt9xHppPw88fNp8hK+HtZbdAT92GT09sE4+hU9qUf3NS3RlVl/beX+2X8alv5r+tfW5ofGvUdd0zQ4ZdImENjIfKuXjX94uenzdgeRxznHPNViZTjH3djm4Yw+ExFdxrq81qu3np3Pnk9eteafpdg70B1E4z2pi0ud78Ecf8LBtf+uMv/oJrfC/xEfPcUW/s+XqvzPQdR/5L5pf/Xkf/QJK6X/vC9D5uj/yT9T/ABfrE5L453Etn49sLm2kMc8VpG6OOqsJHINY4ptVE0ezwpThVy+cJq6cmn9yO8ItPih8O8jYl8o/783Cj+Rz+TetdGlen5nzq9pkGY94fnF/qvzQeA9TTxb4WvdA19CdQtFNrdRv95h0D/UY6+oz3opS9pFwluh5vhnluLhjMK/cl70e3p6foeDeJtGn8P65dabdj95C+A2OHXqrD6ivPnBwk4s/Q8Fi6eNoRrw2f4PqjL4z2qTq0uL3pD6mv4V1+58NazHqVlFFJMisoWUErgjB6EVpTm6b5kcWYYGGPouhUbSdtvI2ZviBqc3jCDxG1tZi8hi8lYwreWRhhyN2c/Me9W68ufn6nDDIqEcHLApvlbvfS/Ty8uxmeMPE934r1OO+v4YIpUiEIWEELgEnuTzyaipUdR3Z15Zl1PLqTo0m2m762/4HYm8GeMNS8JT3EunLFIk6BXimBKkjoeCORz+dOnVlTehnmeU0MyhGNW6a2atctHx5qS+Lh4it7a0t7xk2Sxxq3lzDGPmBbPp0PYVXt5c/OjH+xKDwf1Kbbje6btdemn9XKvjTxbdeLLi3nv7O0hnhUoHgVgWXrg5Y9OcfU1NWq6mrRtlmV08ti4UpNp9Hb9EjnO9ZHqdQ70BrcO9Aa3CgNQFAK4namLWwppDdwoDUO9AdT//Z
// @grant GM_xmlhttpRequest
// @require https://update.greasyfork.org/scripts/526611/1535754/Rolocate%20Base64%20Image%20Library.js
// ==/UserScript==
(function() {
'use strict';
function initializeLocalStorage() {
// Define default settings
const defaultSettings = {
enableLogs: false, // disabled by default
togglefilterserversbutton: true, // enable by default
AutoRunServerRegions: false, // disabled by default
};
// Loop through default settings and set them in localStorage if they don't exist
Object.entries(defaultSettings).forEach(([key, value]) => {
const storageKey = `ROLOCATE_${key}`;
if (localStorage.getItem(storageKey) === null) {
localStorage.setItem(storageKey, value);
}
});
}
function openSettingsMenu() {
if (document.getElementById("userscript-settings-menu")) return;
// Initialize localStorage with default values if they don't exist
initializeLocalStorage();
// Create overlay
const overlay = document.createElement("div");
overlay.id = "userscript-settings-menu";
overlay.innerHTML = `
<div class="settings-container">
<button id="close-settings" class="close-hover">✖</button>
<div class="settings-sidebar">
<h2>Settings</h2>
<ul>
<li class="active" data-section="home">🏠 Home</li>
<li data-section="general">⚙️ General</li>
<li data-section="appearance">🎨 Appearance</li>
<li data-section="advanced">🚀 Advanced</li>
<li data-section ="help">📙 Help</li>
<li data-section="about">ℹ️ About</li>
</ul>
</div>
<div class="settings-content">
<h2 id="settings-title">Home</h2>
<div id="settings-body">${getSettingsContent("home")}</div>
</div>
</div>
`;
document.body.appendChild(overlay);
// Inject styles
const style = document.createElement("style");
style.textContent = `
@keyframes fadeIn {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
@keyframes fadeOut {
from { opacity: 1; transform: scale(1); }
to { opacity: 0; transform: scale(0.95); }
}
@keyframes sectionFade {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
#userscript-settings-menu {
position: fixed;
top: 0; left: 0;
width: 100vw; height: 100vh;
background: rgba(0,0,0,0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
animation: fadeIn 0.3s ease-out;
}
.settings-container {
display: flex;
position: relative;
width: 520px; height: 380px;
background: #1e1e1e;
border-radius: 14px;
overflow: hidden;
box-shadow: 0 12px 24px rgba(0,0,0,0.5);
font-family: Arial, sans-serif;
}
#close-settings {
position: absolute;
top: 12px;
right: 12px;
background: transparent;
border: none;
color: white;
font-size: 20px;
cursor: pointer;
z-index: 10001;
}
.settings-sidebar {
width: 35%;
background: #272727;
padding: 15px;
color: white;
display: flex;
flex-direction: column;
align-items: center;
}
.settings-sidebar ul {
list-style: none;
padding: 0;
width: 100%;
}
.settings-sidebar li {
padding: 12px;
text-align: center;
cursor: pointer;
transition: 0.3s;
border-radius: 6px;
font-weight: bold;
}
.settings-sidebar li:hover, .settings-sidebar .active {
background: #444;
}
/* Custom Scrollbar */
.settings-content {
flex: 1;
padding: 20px;
color: white;
text-align: center;
max-height: 320px;
overflow-y: auto;
scrollbar-width: auto;
scrollbar-color: darkgreen black;
}
/* Webkit (Chrome, Safari) Scrollbar */
.settings-content::-webkit-scrollbar {
width: 14px; /* Increased thickness but it doesent work for some reason */
}
.settings-content::-webkit-scrollbar-track {
background: black;
border-radius: 7px;
}
.settings-content::-webkit-scrollbar-thumb {
background: darkgreen;
border-radius: 7px;
}
.settings-content::-webkit-scrollbar-thumb:hover {
background: #006400; /* Darker green on hover */
}
.settings-content h2,
.settings-content div {
animation: sectionFade 0.3s ease-in-out;
}
.close-hover {
position: relative;
color: black;
transition: color 0.3s ease;
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
}
.close-hover::after {
content: "" !important;
position: absolute !important;
left: 0 !important;
bottom: -2px !important;
width: 0% !important;
height: 2px !important;
background-color: red !important;
transition: width 0.3s ease !important;
}
.close-hover:hover {
color: red !important;
}
.close-hover:hover::after {
width: 100% !important;
}
/* Toggle Slider Styles */
.toggle-slider {
display: flex;
align-items: center;
margin: 10px 0;
cursor: pointer;
}
.toggle-slider input {
display: none;
}
.toggle-slider .slider {
position: relative;
display: inline-block;
width: 40px;
height: 20px;
background-color: #A9A9A9;
border-radius: 20px;
margin-right: 10px;
transition: background-color 0.3s;
}
.toggle-slider .slider::before {
content: "";
position: absolute;
height: 16px;
width: 16px;
left: 2px;
bottom: 2px;
background-color: white;
border-radius: 50%;
transition: transform 0.3s;
}
.toggle-slider input:checked + .slider {
background-color: #4CAF50;
}
.toggle-slider input:checked + .slider::before {
transform: translateX(20px);
}
.rolocate-logo {
width: 75px !important; /* Force width */
height: 75px !important; /* Ensure proper scaling */
object-fit: contain; /* Prevent distortion */
border-radius: 10px; /* Rounded corners */
display: block;
margin: 0 auto 10px auto; /* Center and add spacing */
}
.version {
font-size: 14px;
color: #aaa;
margin-bottom: 20px;
}
.settings-content ul {
text-align: left;
list-style-type: none;
padding: 0;
}
.settings-content ul li {
margin: 10px 0;
}
.settings-content ul li a {
color: #4CAF50;
text-decoration: none;
}
.settings-content ul li a:hover {
text-decoration: underline;
}
.warning_advanced {
font-size: 14px; /* Adjust size as needed */
color: red;
font-weight: bold;
}
.average_text {
font-size: 16px;
color: grey;
font-weight: bold;
}
h2 {
text-decoration: underline;
}
`;
document.head.appendChild(style);
// Sidebar logic with animation
document.querySelectorAll(".settings-sidebar li").forEach(li => {
li.addEventListener("click", function() {
const currentActive = document.querySelector(".settings-sidebar .active");
if (currentActive) currentActive.classList.remove("active");
this.classList.add("active");
const section = this.getAttribute("data-section");
const settingsBody = document.getElementById("settings-body");
const settingsTitle = document.getElementById("settings-title");
// Apply fade-out first
settingsBody.style.animation = "fadeOut 0.2s ease-in forwards";
settingsTitle.style.animation = "fadeOut 0.2s ease-in forwards";
setTimeout(() => {
// Update content
settingsTitle.textContent = section.charAt(0).toUpperCase() + section.slice(1);
settingsBody.innerHTML = getSettingsContent(section);
// Apply fade-in animation
settingsBody.style.animation = "sectionFade 0.3s ease-in-out forwards";
settingsTitle.style.animation = "sectionFade 0.3s ease-in-out forwards";
applyStoredSettings();
}, 200);
});
});
// Close button with fade-out animation
document.getElementById("close-settings").addEventListener("click", function() {
overlay.style.animation = "fadeOut 0.3s ease-in forwards";
setTimeout(() => overlay.remove(), 300);
});
// Apply stored settings on open
applyStoredSettings();
}
function getSettingsContent(section) {
if (section === "home") {
return `
<img class="rolocate-logo" src="${window.Base64Images.logo}" alt="ROLOCATE Logo">
<span class="average_text">Rolocate Settings Menu.</span>
`;
}
if (section === "appearance") {
return `
<span class="average_text">Nothing to see here! Come back later for more awesome features! 😊</span>
`;
}
if (section === "advanced") {
return `
<span class="warning_advanced">⚠️ Warning: Do not edit unless you know what you're doing! ⚠️</span>
<label class="toggle-slider">
<input type="checkbox" id="enableLogs">
<span class="slider"></span>
Enable Console Logs
</label>
<label class="toggle-slider">
<input type="checkbox" id="togglefilterserversbutton">
<span class="slider"></span>
Enable Filter & Server Hop
</label>
`;
}
if (section === "about") {
return `
<div class="version">Rolocate: Version 32.3</div>
<h2>Credits</h2>
<p>This project was created by:</p>
<ul>
<li>Developer: <a href="https://www.roblox.com/users/545334824/profile" target="_blank">Oqarshi</a></li>
<li>Special Thanks: <a href="https://chromewebstore.google.com/detail/btroblox-making-roblox-be/hbkpclpemjeibhioopcebchdmohaieln" target="_blank">Btroblox Team</a></li>
<li>Roblox Locate: <a href="https://greasyfork.org/en/scripts/523727-rolocate" target="_blank">GreasyFork</a></li>
<li>Invite & FAQ Source Code: <a href="https://github.com/Oqarshi/Invite" target="_blank">GitHub</a></li>
<li>FAQ Website: <a href="https://oqarshi.github.io/Invite/rolocate/index.html" target="_blank">RoLocate FAQ</a></li>
</ul>
`;
} // the help
if (section === "help") {
return `
<h2>General Tab:</h2>
<ul>
<li>Auto Run Server Regions: <a>Replaces Roblox's 8 default servers with at least 8 servers, providing detailed info such as location and ping.</a></li>
</ul>
<h2>Appearance Tab:</h2>
<ul>
<li>Nothing yet!</a></li>
</ul>
<h2>Advanced Tab:</h2>
<ul>
<li>Enable Console Logs: <a>Enables console.log messages from the script.</a></li>
<li>Enable Filter & Server Hop: <a>Enables filter and server hop features on the game page.</a></li>
</ul>
`;
} // the general
return `
<label class="toggle-slider">
<input type="checkbox" id="AutoRunServerRegions">
<span class="slider"></span>
Auto Run Server Regions
</label>
`;
}
function applyStoredSettings() {
document.querySelectorAll("input[type='checkbox']").forEach(checkbox => {
const storageKey = `ROLOCATE_${checkbox.id}`;
checkbox.checked = localStorage.getItem(storageKey) === "true";
checkbox.addEventListener("change", () => {
localStorage.setItem(storageKey, checkbox.checked);
});
});
}
function AddSettingsButton() {
const base64Logo = window.Base64Images.logo;
const navbarGroup = document.querySelector('.nav.navbar-right.rbx-navbar-icon-group');
if (!navbarGroup || document.getElementById('custom-logo')) return;
const li = document.createElement('li');
li.id = 'custom-logo-container';
li.style.position = 'relative';
li.innerHTML = `
<img id="custom-logo"
style="
margin-top: 6px;
margin-left: 6px;
width: 26px;
cursor: pointer;
border-radius: 4px;
transition: all 0.2s ease-in-out;
"
src="${base64Logo}">
<span id="custom-tooltip"
style="
visibility: hidden;
background-color: black;
color: white;
text-align: center;
padding: 5px;
border-radius: 5px;
position: absolute;
top: 35px;
left: 50%;
transform: translateX(-50%);
white-space: nowrap;
font-size: 12px;
opacity: 0;
transition: opacity 0.2s ease-in-out;
">
Settings
</span>
`;
const logo = li.querySelector('#custom-logo');
const tooltip = li.querySelector('#custom-tooltip');
logo.addEventListener('click', () => openSettingsMenu());
logo.addEventListener('mouseover', () => {
logo.style.width = '30px';
logo.style.border = '2px solid white';
tooltip.style.visibility = 'visible';
tooltip.style.opacity = '1';
});
logo.addEventListener('mouseout', () => {
logo.style.width = '26px';
logo.style.border = 'none';
tooltip.style.visibility = 'hidden';
tooltip.style.opacity = '0';
});
navbarGroup.appendChild(li);
}
/*************************************************************************
notification function
*************************************************************************/
function notifications(message, type = 'info', emoji = '', duration = 3000) {
// Helper function to darken (or lighten) a hex color.
// Pass a negative percent to darken, a positive percent to lighten.
function shadeColor(color, percent) {
let num = parseInt(color.slice(1), 16),
amt = Math.round(2.55 * percent),
R = (num >> 16) + amt,
G = ((num >> 8) & 0xFF) + amt,
B = (num & 0xFF) + amt;
R = Math.max(Math.min(255, R), 0);
G = Math.max(Math.min(255, G), 0);
B = Math.max(Math.min(255, B), 0);
return "#" + ((1 << 24) + (R << 16) + (G << 8) + B).toString(16).slice(1);
}
// Inject CSS styles for the toast and close button once
if (!document.getElementById('toast-styles')) {
const style = document.createElement('style');
style.id = 'toast-styles';
style.innerHTML = `
#toast-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
display: flex;
flex-direction: column;
gap: 10px;
}
.toast {
position: relative;
min-width: 300px;
max-width: 400px;
padding: 15px 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.15);
opacity: 0;
transform: translateX(50px);
transition: opacity 0.5s ease, transform 0.5s ease;
font-family: Arial, sans-serif;
word-wrap: break-word;
}
.toast .toast-content {
display: flex;
align-items: center;
}
.toast .toast-close-btn {
position: absolute;
top: 8px;
right: 12px;
cursor: pointer;
font-weight: bold;
font-size: 18px;
line-height: 18px;
color: #fff;
display: inline-block;
}
/* Underline animation for close button */
.toast .toast-close-btn::after {
content: '';
position: absolute;
left: 0;
bottom: -2px;
width: 100%;
height: 2px;
background: currentColor;
transform: scaleX(0);
transform-origin: left;
transition: transform 0.3s ease;
}
.toast .toast-close-btn:hover::after {
transform: scaleX(1);
}
.toast .progress-bar {
position: absolute;
bottom: 0;
left: 0;
height: 4px;
background-color: rgba(255,255,255,0.7);
width: 100%;
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
}
`;
document.head.appendChild(style);
}
// Create or get the container
let container = document.getElementById('toast-container');
if (!container) {
container = document.createElement('div');
container.id = 'toast-container';
document.body.appendChild(container);
}
// Create toast element
const toast = document.createElement('div');
toast.className = 'toast';
// Determine the base color based on type
let baseColor;
switch (type.toLowerCase()) {
case 'success':
baseColor = '#4CAF50';
break;
case 'error':
baseColor = '#F44336';
break;
case 'info':
default:
baseColor = '#2196F3';
break;
}
// Create a dark version of the base color (darkened by 20%)
let darkColor = shadeColor(baseColor, -20);
// Set a gradient background from the dark variant to the base color
toast.style.background = `linear-gradient(90deg, ${darkColor}, ${baseColor})`;
// Create content wrapper with optional emoji
const content = document.createElement('div');
content.className = 'toast-content';
content.innerHTML = `${emoji ? `<span style="margin-right:8px;">${emoji}</span>` : ''}<span>${message}</span>`;
toast.appendChild(content);
// Create the close (×) button with underline animation on hover
const closeBtn = document.createElement('span');
closeBtn.className = 'toast-close-btn';
closeBtn.innerHTML = '×';
closeBtn.addEventListener('click', () => removeToast(toast));
toast.appendChild(closeBtn);
// Create progress bar
const progressBar = document.createElement('div');
progressBar.className = 'progress-bar';
// Set the progress bar's transition to match the duration
progressBar.style.transition = `width ${duration}ms linear`;
toast.appendChild(progressBar);
// Append toast to container and animate in
container.appendChild(toast);
setTimeout(() => {
toast.style.opacity = '1';
toast.style.transform = 'translateX(0)';
// Start progress bar animation
setTimeout(() => {
progressBar.style.width = '0%';
}, 50);
}, 50);
// Auto-remove toast after the specified duration
const removeTimeout = setTimeout(() => removeToast(toast), duration);
// Function to fade out and remove toast
function removeToast(toastEl) {
clearTimeout(removeTimeout);
toastEl.style.opacity = '0';
toastEl.style.transform = 'translateX(50px)';
setTimeout(() => toastEl.remove(), 500);
}
}
function Update_Popup() {
const VERSION = "V32.3";
const PREV_VERSION = "V31.3";
if (localStorage.getItem(PREV_VERSION)) {
localStorage.removeItem(PREV_VERSION);
}
if (localStorage.getItem(VERSION)) return;
localStorage.setItem(VERSION, "true");
const css = `
.first-time-popup {
display: flex;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.7);
justify-content: center;
align-items: center;
z-index: 1000;
opacity: 0;
animation: fadeIn 0.4s ease-in-out forwards;
}
.first-time-popup-content {
background: rgba(25, 25, 25, 0.95);
border-radius: 18px;
padding: 30px;
width: 420px;
max-width: 90%;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
text-align: center;
color: #fff;
transform: scale(0.85);
animation: scaleUp 0.5s ease-out forwards;
}
.popup-header {
font-size: 22px;
font-weight: bold;
color: #4da6ff;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 5px;
}
.popup-version {
font-size: 18px;
font-weight: bold;
color: #ffcc00;
margin-bottom: 15px;
}
.popup-info {
font-size: 15px;
color: #ccc;
margin-bottom: 20px;
line-height: 1.6;
padding: 10px;
border-radius: 10px;
background: rgba(255, 255, 255, 0.05);
}
.popup-info a {
color: #4da6ff;
text-decoration: none;
font-weight: bold;
transition: color 0.3s ease;
}
.popup-info a:hover {
color: #80bfff;
text-decoration: underline;
}
.popup-footer {
font-size: 14px;
color: #aaa;
font-weight: bold;
margin-top: 10px;
transition: opacity 0.3s ease-out;
}
.popup-footer.hidden {
opacity: 0;
visibility: hidden;
}
.popup-note {
font-size: 13px;
font-weight: bold;
color: #ff6666;
margin-top: 8px;
}
.popup-logo {
display: block;
margin: 0 auto 15px;
width: 80px; /* Adjust based on your preference */
height: auto;
border-radius: 10px; /* Optional: Adds rounded corners */
}
.first-time-popup-close {
position: absolute;
top: 10px;
right: 15px;
font-size: 24px;
font-weight: bold;
cursor: pointer;
color: #fff;
opacity: 0.4;
transition: opacity 0.3s ease;
pointer-events: none;
}
.first-time-popup-close.active {
opacity: 1;
pointer-events: auto;
}
.first-time-popup-close:hover {
color: #ff4d4d;
}
.first-time-popup-close::after {
content: "";
display: block;
width: 0%;
height: 2px;
background: #ff4d4d;
transition: width 0.3s ease-out;
}
.first-time-popup-close:hover::after {
width: 100%;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
@keyframes scaleUp {
0% { transform: scale(0.85); }
60% { transform: scale(1.05); }
100% { transform: scale(1); }
}
@keyframes scaleDown {
from { transform: scale(1); }
to { transform: scale(0.85); }
}
`;
const style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = css;
document.head.appendChild(style);
const popupHTML = `
<div class="first-time-popup">
<div class="first-time-popup-content">
<span class="first-time-popup-close">×</span>
<img class="popup-logo" src="${window.Base64Images.logo}" alt="Rolocate Logo">
<div class="popup-header"><b>Rolocate Update</b></div>
<div class="popup-version"><b>Version: ${VERSION}</b></div>
<div class="popup-info">
<p>A lot of bug fixes and errors. Check out the <a href="https://oqarshi.github.io/Invite/rolocate/index.html" target="_blank">FAQ page</a>!</p> <div class="popup-note">This won't show again until the next update.</div>
<div class="popup-footer">Closing enabled in <span id="countdown-timer"><strong>5</strong></span> seconds...</div>
</div>
</div>
`;
const popupContainer = document.createElement('div');
popupContainer.innerHTML = popupHTML;
document.body.appendChild(popupContainer);
const closeButton = document.querySelector('.first-time-popup-close');
const popup = document.querySelector('.first-time-popup');
const countdownTimer = document.getElementById('countdown-timer');
const footer = document.querySelector('.popup-footer');
let countdown = 5;
const countdownInterval = setInterval(() => {
countdown--;
countdownTimer.innerHTML = `<strong>${countdown}</strong>`;
if (countdown <= 0) {
clearInterval(countdownInterval);
closeButton.classList.add('active');
footer.classList.add('hidden'); // Hides the countdown text
}
}, 1000);
closeButton.addEventListener('click', () => {
popup.style.animation = 'fadeOut 0.4s ease-in-out forwards';
document.querySelector('.first-time-popup-content').style.animation = 'scaleDown 0.4s ease-in-out forwards';
setTimeout(() => {
popup.remove();
}, 400);
});
}
function ConsoleLogEnabled(...args) {
if (localStorage.getItem("ROLOCATE_enableLogs") === "true") {
console.log(...args);
}
}
// Load all required stuff hehehe
window.addEventListener("load", () => {
loadBase64Library(() => {
ConsoleLogEnabled("Loaded Base64Images. It is ready to use!");
});
AddSettingsButton(() => {
ConsoleLogEnabled("Loaded Settings button!");
});
Update_Popup();
initializeLocalStorage();
});
function loadBase64Library(callback, timeout = 5000) {
let elapsed = 0;
(function waitForLibrary() {
if (typeof window.Base64Images === "undefined") {
if (elapsed < timeout) {
elapsed += 50;
setTimeout(waitForLibrary, 50);
} else {
ConsoleLogEnabled("Base64Images did not load within the timeout.");
notifications('An error occured! No icons will show. Please refresh the page.', 'error', '⚠️', '8000')
}
} else {
if (callback) callback();
}
})();
}
/*******************************************************
The code for the random hop button and the filter button on roblox.com/games/*
*******************************************************/
if (window.location.href.startsWith("https://www.roblox.com/games/") && localStorage.getItem("ROLOCATE_togglefilterserversbutton") === "true") {
let Isongamespage = false; // Initially false
/*********************************************************************************************************************************************************************************************************************************************
This is all of the functions for the filter button and the popup for the 7 buttons does not include the functions for the 8 buttons
*********************************************************************************************************************************************************************************************************************************************/
/*******************************************************
name of function: createPopup
description: Creates a popup with server filtering options and interactive buttons.
*******************************************************/
function createPopup() {
const popup = document.createElement('div');
popup.className = 'server-filters-dropdown-box'; // Unique class name
popup.style.cssText = `
position: absolute;
width: 210px;
height: 382px;
right: 0px;
top: 30px;
z-index: 1000;
border-radius: 5px;
background-color: rgb(30, 32, 34);
display: flex;
flex-direction: column;
padding: 5px;
`;
// Create the header section
const header = document.createElement('div');
header.style.cssText = `
display: flex;
align-items: center;
padding: 10px;
border-bottom: 1px solid #444;
margin-bottom: 5px;
`;
// Add the logo (base64 image)
const logo = document.createElement('img');
logo.src = window.Base64Images.logo;
logo.style.cssText = `
width: 24px;
height: 24px;
margin-right: 10px;
`;
// Add the title
const title = document.createElement('span');
title.textContent = 'RoLocate';
title.style.cssText = `
color: white;
font-size: 18px;
font-weight: bold;
`;
// Append logo and title to the header
header.appendChild(logo);
header.appendChild(title);
// Append the header to the popup
popup.appendChild(header);
// Define unique names, tooltips, experimental status, and explanations for each button
const buttonData = [{
name: "Smallest Servers",
tooltip: "**Reverses the order of the server list.** The emptiest servers will be displayed first.",
experimental: false
},
{
name: "Available Space",
tooltip: "**Filters out servers which are full.** Servers with space will only be shown.",
experimental: false
},
{
name: "Player Count",
tooltip: "**Rolocate will find servers with your specified player count or fewer.** Searching for up to 3 minutes. If no exact match is found, it shows servers closest to the target.",
experimental: false
},
{
name: "Random Shuffle",
tooltip: "**Display servers in a completely random order.** Shows servers with space and servers with low player counts in a randomized order.",
experimental: false
},
{
name: "Server Region",
tooltip: "**Filters servers by region.** Offering more accuracy than 'Best Connection' in areas with fewer Roblox servers, like India, or in games with high player counts.",
experimental: true,
experimentalExplanation: "**Experimental**: Still in development and testing. Ping may be inaccurate sometimes because of the Roblox API."
},
{
name: "Best Connection",
tooltip: "**Automatically joins the fastest servers for you.** However, it may be less accurate in regions with fewer Roblox servers, like India, or in games with large player counts.",
experimental: true,
experimentalExplanation: "**Experimental**: Still in development and testing. it may be less accurate in regions with fewer Roblox servers"
},
{
name: "Join Small Server",
tooltip: "**Automatically tries to join a server with a very low population.** On popular games servers may fill up very fast so you might not always get in alone.",
experimental: false
},
{
name: "Locate Player",
tooltip: "**Finds and joins the server a user is playing on if they are playing this particular game.** Note: May take a while for very popular games.",
experimental: true,
experimentalExplanation: "**Experimental**: Still in development and testing. It may not be accurate with popular avatars, such as the default Roblox avatars."
}
];
// Create buttons with unique names, tooltips, experimental status, and explanations
buttonData.forEach((data, index) => {
const buttonContainer = document.createElement('div');
buttonContainer.className = 'server-filter-option';
buttonContainer.style.cssText = `
width: 190px;
height: 30px;
background-color: #393B3D;
margin: 5px;
border-radius: 5px;
padding: 3.5px;
position: relative;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.3s ease;
`;
const tooltip = document.createElement('div');
tooltip.className = 'filter-tooltip';
tooltip.style.cssText = `
display: none;
position: absolute;
top: -10px;
left: 200px;
width: auto;
inline-size: 200px;
height: auto;
background-color: #191B1D;
color: white;
padding: 5px;
border-radius: 5px;
white-space: pre-wrap;
font-size: 14px;
`;
// Parse tooltip text and replace **...** with bold HTML tags
tooltip.innerHTML = data.tooltip.replace(/\*\*(.*?)\*\*/g, "<b style='color: #068f00;'>$1</b>");
const buttonText = document.createElement('p');
buttonText.style.cssText = `
margin: 0;
color: white;
font-size: 16px;
`;
buttonText.textContent = data.name;
// Add "EXP" label if the button is experimental
if (data.experimental) {
const expLabel = document.createElement('span');
expLabel.textContent = 'EXP';
expLabel.style.cssText = `
margin-left: 8px;
color: gold;
font-size: 12px;
font-weight: bold;
background-color: rgba(255, 215, 0, 0.1);
padding: 2px 6px;
border-radius: 3px;
`;
buttonText.appendChild(expLabel);
}
// Add experimental explanation tooltip (left side)
let experimentalTooltip = null;
if (data.experimental) {
experimentalTooltip = document.createElement('div');
experimentalTooltip.className = 'experimental-tooltip';
experimentalTooltip.style.cssText = `
display: none;
position: absolute;
top: 0;
right: 200px;
width: 200px;
background-color: #191B1D;
color: white;
padding: 5px;
border-radius: 5px;
font-size: 14px;
white-space: pre-wrap;
z-index: 1001;
`;
// Function to replace **text** with bold and gold styled text
const formatText = (text) => {
return text.replace(/\*\*(.*?)\*\*/g, '<span style="font-weight: bold; color: gold;">$1</span>');
};
// Apply the formatting to the experimental explanation
experimentalTooltip.innerHTML = formatText(data.experimentalExplanation);
buttonContainer.appendChild(experimentalTooltip);
}
buttonContainer.appendChild(tooltip);
buttonContainer.appendChild(buttonText);
buttonContainer.addEventListener('mouseover', () => {
tooltip.style.display = 'block';
if (data.experimental) {
experimentalTooltip.style.display = 'block';
}
buttonContainer.style.backgroundColor = '#4A4C4E'; // Hover effect
});
buttonContainer.addEventListener('mouseout', () => {
tooltip.style.display = 'none';
if (data.experimental) {
experimentalTooltip.style.display = 'none';
}
buttonContainer.style.backgroundColor = '#393B3D'; // Revert to original color
});
buttonContainer.addEventListener('click', () => {
switch (index) {
case 0:
smallest_servers();
break;
case 1:
available_space_servers();
break;
case 2:
player_count_tab();
break;
case 3:
random_servers();
break;
case 4:
createServerCountPopup((totalLimit) => {
rebuildServerList(gameId, totalLimit);
});
break;
case 5:
rebuildServerList(gameId, 50, true);
break;
case 6:
auto_join_small_server();
break;
case 7:
find_user_server_tab();
break;
}
});
popup.appendChild(buttonContainer);
});
return popup;
}
/*******************************************************
name of function: ServerHop
description: Handles server hopping by fetching and joining a random server, excluding recently joined servers.
*******************************************************/
// Main function to handle the server hopping
function ServerHop() {
ConsoleLogEnabled("Starting server hop...");
showLoadingOverlay();
// Extract the game ID from the URL
const url = window.location.href;
const gameId = url.split("/")[4]; // Extracts the game ID, assuming URL is in the format: /games/{gameId}/Title
ConsoleLogEnabled(`Game ID: ${gameId}`);
// Array to store server IDs
let serverIds = [];
let nextPageCursor = null;
let pagesRequested = 0;
// Get the list of all recently joined servers in localStorage
const allStoredServers = Object.keys(localStorage)
.filter(key => key.startsWith("recentServers_"))
.map(key => JSON.parse(localStorage.getItem(key)));
// Remove any expired servers for all games (older than 15 minutes)
const currentTime = new Date().getTime();
allStoredServers.forEach(storedServers => {
const validServers = storedServers.filter(server => {
const lastJoinedTime = new Date(server.timestamp).getTime();
return (currentTime - lastJoinedTime) <= 15 * 60 * 1000; // 15 minutes
});
// Update localStorage with the valid (non-expired) servers
localStorage.setItem(`recentServers_${gameId}`, JSON.stringify(validServers));
});
// Get the list of recently joined servers for the current game
const storedServers = JSON.parse(localStorage.getItem(`recentServers_${gameId}`)) || [];
// Check if there are any recently joined servers and exclude them from selection
const validServers = storedServers.filter(server => {
const lastJoinedTime = new Date(server.timestamp).getTime();
return (currentTime - lastJoinedTime) <= 15 * 60 * 1000; // 15 minutes
});
if (validServers.length > 0) {
ConsoleLogEnabled(`Excluding servers joined in the last 15 minutes: ${validServers.map(s => s.serverId).join(', ')}`);
} else {
ConsoleLogEnabled("No recently joined servers within the last 15 minutes. Proceeding to pick a new server.");
}
// Function to fetch servers
function fetchServers(cursor) {
const url = `https://games.roblox.com/v1/games/${gameId}/servers/0?sortOrder=2&excludeFullGames=true&limit=100${cursor ? `&cursor=${cursor}` : ""}`;
GM_xmlhttpRequest({
method: "GET",
url: url,
onload: function(response) {
ConsoleLogEnabled("API Response:", response.responseText);
try {
const data = JSON.parse(response.responseText);
// If there's an error, log it and return without processing
if (data.errors) {
ConsoleLogEnabled("Skipping unreadable response:", data.errors[0].message);
return;
}
// After a successful request, wait 0.15 seconds before proceeding
setTimeout(() => {
if (!data || !data.data) {
ConsoleLogEnabled("Invalid response structure: 'data' is missing or undefined", data);
return;
}
data.data.forEach(server => {
if (validServers.some(vs => vs.serverId === server.id)) {
ConsoleLogEnabled(`Skipping previously joined server ${server.id}.`);
} else {
serverIds.push(server.id);
}
});
// Fetch next page if available and within limit
if (data.nextPageCursor && pagesRequested < 4) {
pagesRequested++;
ConsoleLogEnabled(`Fetching page ${pagesRequested}...`);
fetchServers(data.nextPageCursor);
} else {
pickRandomServer();
}
}, 150);
} catch (error) {
ConsoleLogEnabled("Error parsing response:", error);
}
},
onerror: function(error) {
ConsoleLogEnabled("Error fetching server data:", error);
}
});
}
// Function to pick a random server and join it
function pickRandomServer() {
if (serverIds.length > 0) {
const randomServerId = serverIds[Math.floor(Math.random() * serverIds.length)];
ConsoleLogEnabled(`Joining server: ${randomServerId}`);
// Join the game instance with the selected server ID
Roblox.GameLauncher.joinGameInstance(gameId, randomServerId);
// Store the selected server ID with the time and date in localStorage
const timestamp = new Date().toISOString();
const newServer = {
serverId: randomServerId,
timestamp
};
validServers.push(newServer);
// Save the updated list of recently joined servers to localStorage
localStorage.setItem(`recentServers_${gameId}`, JSON.stringify(validServers));
ConsoleLogEnabled(`Server ${randomServerId} stored with timestamp ${timestamp}`);
} else {
ConsoleLogEnabled("No servers found to join.");
notifications("You have joined all the servers recently. No servers found to join.", "error", "⚠️", "5000");
}
}
// Start the fetching process
fetchServers();
}
if (window.location.href.startsWith("https://www.roblox.com/games/")) {
window.addEventListener("load", () => {
// Extract game ID from URL
function findGameId() {
const match = window.location.href.match(/games\/(\d+)/);
return match ? match[1] : null;
}
// Auto-click "Servers" tab if enabled in localStorage
if (localStorage.ROLOCATE_AutoRunServerRegions === "true") {
setTimeout(() => {
const serversTab = document.querySelector("#tab-game-instances a");
if (serversTab) {
serversTab.click();
}
}, 1000);
}
// Auto-run server regions if enabled in localStorage
if (localStorage.ROLOCATE_AutoRunServerRegions === "true") {
setTimeout(() => {
const gameId = findGameId();
if (gameId) {
Loadingbar(true);
disableFilterButton(true);
disableLoadMoreButton();
rebuildServerList(gameId, 16);
}
}, 2000);
}
});
Isongamespage = true;
const observer = new MutationObserver((mutations, obs) => {
const serverListOptions = document.querySelector('.server-list-options');
const playButton = document.querySelector('.btn-common-play-game-lg.btn-primary-md');
if (serverListOptions && !document.querySelector('.RL-filter-button')) {
const filterButton = document.createElement('a');
filterButton.className = 'RL-filter-button';
filterButton.style.cssText = `
color: white;
font-weight: bold;
text-decoration: none;
cursor: pointer;
margin-left: 10px;
padding: 5px 10px;
display: flex;
align-items: center;
gap: 5px;
position: relative;
margin-top: 4px;
`;
filterButton.addEventListener('mouseover', () => {
filterButton.style.textDecoration = 'underline';
});
filterButton.addEventListener('mouseout', () => {
filterButton.style.textDecoration = 'none';
});
const buttonText = document.createElement('span');
buttonText.className = 'RL-filter-text';
buttonText.textContent = 'Filters';
filterButton.appendChild(buttonText);
const icon = document.createElement('span');
icon.className = 'RL-filter-icon';
icon.textContent = '≡';
icon.style.cssText = `font-size: 18px;`;
filterButton.appendChild(icon);
serverListOptions.appendChild(filterButton);
let popup = null;
filterButton.addEventListener('click', (event) => {
event.stopPropagation();
if (popup) {
popup.remove();
popup = null;
} else {
popup = createPopup();
popup.style.top = `${filterButton.offsetHeight}px`;
popup.style.left = '0';
filterButton.appendChild(popup);
}
});
document.addEventListener('click', (event) => {
if (popup && !filterButton.contains(event.target)) {
popup.remove();
popup = null;
}
});
}
if (playButton && !document.querySelector('.custom-play-button')) {
const buttonContainer = document.createElement('div');
buttonContainer.style.cssText = `
display: flex;
gap: 10px;
align-items: center;
width: 100%;
`;
playButton.style.cssText += `
flex: 3;
padding: 10px 12px;
text-align: center;
`;
const serverHopButton = document.createElement('button');
serverHopButton.className = 'custom-play-button';
serverHopButton.style.cssText = `
background-color: #335fff;
color: white;
border: none;
padding: 7.5px 12px;
cursor: pointer;
font-weight: bold;
border-radius: 8px;
flex: 1;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
position: relative;
`;
const tooltip = document.createElement('div');
tooltip.textContent = 'Join Random Server / Server Hop';
tooltip.style.cssText = `
position: absolute;
background-color: rgba(51, 95, 255, 0.8);
color: white;
padding: 5px 10px;
border-radius: 5px;
font-size: 12px;
visibility: hidden;
opacity: 0;
transition: opacity 0.2s ease-in-out;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
white-space: nowrap;
`;
serverHopButton.appendChild(tooltip);
serverHopButton.addEventListener('mouseover', () => {
tooltip.style.visibility = 'visible';
tooltip.style.opacity = '1';
});
serverHopButton.addEventListener('mouseout', () => {
tooltip.style.visibility = 'hidden';
tooltip.style.opacity = '0';
});
const logo = document.createElement('img');
logo.src = window.Base64Images.icon_serverhop;
logo.style.cssText = `
width: 45px;
height: 45px;
`;
serverHopButton.appendChild(logo);
playButton.parentNode.insertBefore(buttonContainer, playButton);
buttonContainer.appendChild(playButton);
buttonContainer.appendChild(serverHopButton);
serverHopButton.addEventListener('click', () => {
ServerHop();
});
}
if (document.querySelector('.RL-filter-button') && document.querySelector('.custom-play-button')) {
obs.disconnect();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
/*********************************************************************************************************************************************************************************************************************************************
The End of: This is all of the functions for the filter button and the popup for the 8 buttons does not include the functions for the 8 buttons
*********************************************************************************************************************************************************************************************************************************************/
/*********************************************************************************************************************************************************************************************************************************************
Functions for the 1st button
*********************************************************************************************************************************************************************************************************************************************/
/*******************************************************
name of function: smallest_servers
description: Fetches the smallest servers, disables the "Load More" button, shows a loading bar, and recreates the server cards.
*******************************************************/
async function smallest_servers() {
// Disable the "Load More" button and show the loading bar
Loadingbar(true);
disableFilterButton(true);
disableLoadMoreButton();
notifications("Finding small servers...", "success", "🧐");
// Get the game ID from the URL
const gameId = window.location.pathname.split('/')[2];
// Retry mechanism
let retries = 3;
let success = false;
while (retries > 0 && !success) {
try {
// Use GM_xmlhttpRequest to fetch server data from the Roblox API
const response = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: `https://games.roblox.com/v1/games/${gameId}/servers/0?sortOrder=1&excludeFullGames=true&limit=100`,
onload: function(response) {
if (response.status === 429) {
reject(new Error('429: Too Many Requests'));
} else if (response.status >= 200 && response.status < 300) {
resolve(response);
} else {
reject(new Error(`HTTP error! status: ${response.status}`));
}
},
onerror: function(error) {
reject(error);
}
});
});
const data = JSON.parse(response.responseText);
// Process each server
for (const server of data.data) {
const {
id: serverId,
playerTokens,
maxPlayers,
playing
} = server;
// Pass the server data to the card creation function
await rbx_card(serverId, playerTokens, maxPlayers, playing, gameId);
}
success = true; // Mark as successful if no errors occurred
} catch (error) {
retries--; // Decrement the retry count
if (error.message === '429: Too Many Requests' && retries > 0) {
ConsoleLogEnabled('Encountered a 429 error. Retrying in 5 seconds...');
await new Promise(resolve => setTimeout(resolve, 5000)); // Wait for 5 seconds
} else {
ConsoleLogEnabled('Error fetching server data:', error);
break; // Exit the loop if it's not a 429 error or no retries left
}
} finally {
if (success || retries === 0) {
// Hide the loading bar and enable the filter button
Loadingbar(false);
disableFilterButton(false);
}
}
}
}
/*********************************************************************************************************************************************************************************************************************************************
Functions for the 2nd button
*********************************************************************************************************************************************************************************************************************************************/
/*******************************************************
name of function: available_space_servers
description: Fetches servers with available space, disables the "Load More" button, shows a loading bar, and recreates the server cards.
*******************************************************/
async function available_space_servers() {
// Disable the "Load More" button and show the loading bar
Loadingbar(true);
disableLoadMoreButton();
disableFilterButton(true);
notifications("Finding servers with space...", "success", "🧐");
// Get the game ID from the URL
const gameId = window.location.pathname.split('/')[2];
// Retry mechanism
let retries = 3;
let success = false;
while (retries > 0 && !success) {
try {
// Use GM_xmlhttpRequest to fetch server data from the Roblox API
const response = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: `https://games.roblox.com/v1/games/${gameId}/servers/0?sortOrder=2&excludeFullGames=true&limit=100`,
onload: function(response) {
if (response.status === 429) {
reject(new Error('429: Too Many Requests'));
} else if (response.status >= 200 && response.status < 300) {
resolve(response);
} else {
reject(new Error(`HTTP error! status: ${response.status}`));
}
},
onerror: function(error) {
reject(error);
}
});
});
const data = JSON.parse(response.responseText);
// Process each server
for (const server of data.data) {
const {
id: serverId,
playerTokens,
maxPlayers,
playing
} = server;
// Pass the server data to the card creation function
await rbx_card(serverId, playerTokens, maxPlayers, playing, gameId);
}
success = true; // Mark as successful if no errors occurred
} catch (error) {
retries--; // Decrement the retry count
if (error.message === '429: Too Many Requests' && retries > 0) {
ConsoleLogEnabled('Encountered a 429 error. Retrying in 10 seconds...');
await new Promise(resolve => setTimeout(resolve, 10000)); // Wait for 10 seconds
} else {
ConsoleLogEnabled('Error fetching server data:', error);
break; // Exit the loop if it's not a 429 error or no retries left
}
} finally {
if (success || retries === 0) {
// Hide the loading bar and enable the filter button
Loadingbar(false);
disableFilterButton(false);
}
}
}
}
/*********************************************************************************************************************************************************************************************************************************************
Functions for the 3rd button
*********************************************************************************************************************************************************************************************************************************************/
/*******************************************************
name of function: player_count_tab
description: Opens a popup for the user to select the max player count using a slider and filters servers accordingly.
*******************************************************/
function player_count_tab() {
// Check if the max player count has already been determined
if (!player_count_tab.maxPlayers) {
// Try to find the element containing the player count information
const playerCountElement = document.querySelector('.text-info.rbx-game-status.rbx-game-server-status.text-overflow');
if (playerCountElement) {
const playerCountText = playerCountElement.textContent.trim();
const match = playerCountText.match(/(\d+) of (\d+) people max/);
if (match) {
const maxPlayers = parseInt(match[2], 10);
if (!isNaN(maxPlayers) && maxPlayers > 1) {
player_count_tab.maxPlayers = maxPlayers;
ConsoleLogEnabled("Found text element with max playercount");
}
}
} else {
// If the element is not found, extract the gameId from the URL
const gameIdMatch = window.location.href.match(/games\/(\d+)/);
if (gameIdMatch && gameIdMatch[1]) {
const gameId = gameIdMatch[1];
// Send a request to the Roblox API to get server information
GM_xmlhttpRequest({
method: 'GET',
url: `https://games.roblox.com/v1/games/${gameId}/servers/public?sortOrder=1&excludeFullGames=true&limit=100`,
onload: function(response) {
try {
if (response.status === 429) {
// Rate limit error, default to 100
ConsoleLogEnabled("Rate limited defaulting to 100.");
player_count_tab.maxPlayers = 100;
} else {
ConsoleLogEnabled("Valid api response");
const data = JSON.parse(response.responseText);
if (data.data && data.data.length > 0) {
const maxPlayers = data.data[0].maxPlayers;
if (!isNaN(maxPlayers) && maxPlayers > 1) {
player_count_tab.maxPlayers = maxPlayers;
}
}
}
// Update the slider range if the popup is already created
const slider = document.querySelector('.player-count-popup input[type="range"]');
if (slider) {
slider.max = player_count_tab.maxPlayers ? (player_count_tab.maxPlayers - 1).toString() : '100';
slider.style.background = `
linear-gradient(
to right,
#00A2FF 0%,
#00A2FF ${slider.value}%,
#444 ${slider.value}%,
#444 100%
);
`;
}
} catch (error) {
ConsoleLogEnabled('Failed to parse API response:', error);
// Default to 100 if parsing fails
player_count_tab.maxPlayers = 100;
const slider = document.querySelector('.player-count-popup input[type="range"]');
if (slider) {
slider.max = '100';
slider.style.background = `
linear-gradient(
to right,
#00A2FF 0%,
#00A2FF ${slider.value}%,
#444 ${slider.value}%,
#444 100%
);
`;
}
}
},
onerror: function(error) {
ConsoleLogEnabled('Failed to fetch server information:', error);
ConsoleLogEnabled('Fallback to 100 players.');
// Default to 100 if the request fails
player_count_tab.maxPlayers = 100;
const slider = document.querySelector('.player-count-popup input[type="range"]');
if (slider) {
slider.max = '100';
slider.style.background = `
linear-gradient(
to right,
#00A2FF 0%,
#00A2FF ${slider.value}%,
#444 ${slider.value}%,
#444 100%
);
`;
}
}
});
}
}
}
// Create the overlay (backdrop)
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9999;
opacity: 0;
transition: opacity 0.3s ease;
`;
document.body.appendChild(overlay);
// Create the popup container
const popup = document.createElement('div');
popup.className = 'player-count-popup';
popup.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgb(30, 32, 34);
padding: 20px;
border-radius: 10px;
z-index: 10000;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.7);
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
width: 300px;
opacity: 0;
transition: opacity 0.3s ease, transform 0.3s ease;
`;
// Add a close button in the top-right corner (bigger size)
const closeButton = document.createElement('button');
closeButton.innerHTML = '×'; // Using '×' for the close icon
closeButton.style.cssText = `
position: absolute;
top: 10px;
right: 10px;
background: transparent;
border: none;
color: #ffffff;
font-size: 24px; /* Increased font size */
cursor: pointer;
width: 36px; /* Increased size */
height: 36px; /* Increased size */
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.3s ease, color 0.3s ease;
`;
closeButton.addEventListener('mouseenter', () => {
closeButton.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
closeButton.style.color = '#ff4444';
});
closeButton.addEventListener('mouseleave', () => {
closeButton.style.backgroundColor = 'transparent';
closeButton.style.color = '#ffffff';
});
// Add a title
const title = document.createElement('h3');
title.textContent = 'Select Max Player Count';
title.style.cssText = `
color: white;
margin: 0;
font-size: 18px;
font-weight: 500;
`;
popup.appendChild(title);
// Add a slider with improved functionality and styling
const slider = document.createElement('input');
slider.type = 'range';
slider.min = '1';
slider.max = player_count_tab.maxPlayers ? (player_count_tab.maxPlayers - 1).toString() : '100';
slider.value = '1'; // Default value
slider.step = '1'; // Step for better accuracy
slider.style.cssText = `
width: 80%;
cursor: pointer;
margin: 10px 0;
-webkit-appearance: none; /* Remove default styling */
background: transparent;
`;
// Custom slider track
slider.style.background = `
linear-gradient(
to right,
#00A2FF 0%,
#00A2FF ${slider.value}%,
#444 ${slider.value}%,
#444 100%
);
border-radius: 5px;
height: 6px;
`;
// Custom slider thumb
slider.style.setProperty('--thumb-size', '20px'); /* Larger thumb */
slider.style.setProperty('--thumb-color', '#00A2FF');
slider.style.setProperty('--thumb-hover-color', '#0088cc');
slider.style.setProperty('--thumb-border', '2px solid #fff');
slider.style.setProperty('--thumb-shadow', '0 0 5px rgba(0, 0, 0, 0.5)');
slider.addEventListener('input', () => {
slider.style.background = `
linear-gradient(
to right,
#00A2FF 0%,
#00A2FF ${slider.value}%,
#444 ${slider.value}%,
#444 100%
);
`;
sliderValue.textContent = slider.value; // Update the displayed value
});
// Keyboard support for better accuracy (fixed to increment/decrement by 1)
slider.addEventListener('keydown', (e) => {
e.preventDefault(); // Prevent default behavior (which might cause jumps)
let newValue = parseInt(slider.value, 10);
if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {
newValue = Math.max(1, newValue - 1); // Decrease by 1
} else if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {
newValue = Math.min(100, newValue + 1); // Increase by 1
}
slider.value = newValue;
slider.dispatchEvent(new Event('input')); // Trigger input event to update UI
});
popup.appendChild(slider);
// Add a display for the slider value
const sliderValue = document.createElement('span');
sliderValue.textContent = slider.value;
sliderValue.style.cssText = `
color: white;
font-size: 16px;
font-weight: bold;
`;
popup.appendChild(sliderValue);
// Add a submit button with dark, blackish style
const submitButton = document.createElement('button');
submitButton.textContent = 'Search';
submitButton.style.cssText = `
padding: 8px 20px;
font-size: 16px;
background-color: #1a1a1a; /* Dark blackish color */
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s ease, transform 0.2s ease;
`;
submitButton.addEventListener('mouseenter', () => {
submitButton.style.backgroundColor = '#333'; /* Slightly lighter on hover */
submitButton.style.transform = 'scale(1.05)';
});
submitButton.addEventListener('mouseleave', () => {
submitButton.style.backgroundColor = '#1a1a1a';
submitButton.style.transform = 'scale(1)';
});
// Add a yellow box with a tip under the submit button
const tipBox = document.createElement('div');
tipBox.style.cssText = `
width: 100%;
padding: 10px;
background-color: rgba(255, 204, 0, 0.15);
border-radius: 5px;
text-align: center;
font-size: 14px;
color: #ffcc00;
transition: background-color 0.3s ease;
`;
tipBox.textContent = 'Tip: Click the slider and use the arrow keys for more accuracy.';
tipBox.addEventListener('mouseenter', () => {
tipBox.style.backgroundColor = 'rgba(255, 204, 0, 0.25)';
});
tipBox.addEventListener('mouseleave', () => {
tipBox.style.backgroundColor = 'rgba(255, 204, 0, 0.15)';
});
popup.appendChild(tipBox);
// Append the popup to the body
document.body.appendChild(popup);
// Fade in the overlay and popup
setTimeout(() => {
overlay.style.opacity = '1';
popup.style.opacity = '1';
popup.style.transform = 'translate(-50%, -50%) scale(1)';
}, 10);
/*******************************************************
name of function: fadeOutAndRemove
description: Fades out and removes the popup and overlay.
*******************************************************/
function fadeOutAndRemove(popup, overlay) {
popup.style.opacity = '0';
popup.style.transform = 'translate(-50%, -50%) scale(0.9)';
overlay.style.opacity = '0';
setTimeout(() => {
popup.remove();
overlay.remove();
}, 300); // Match the duration of the transition
}
// Close the popup when clicking outside
overlay.addEventListener('click', () => {
fadeOutAndRemove(popup, overlay);
});
// Close the popup when the close button is clicked
closeButton.addEventListener('click', () => {
fadeOutAndRemove(popup, overlay);
});
// Handle submit button click
submitButton.addEventListener('click', () => {
const maxPlayers = parseInt(slider.value, 10);
if (!isNaN(maxPlayers) && maxPlayers > 0) {
filterServersByPlayerCount(maxPlayers);
fadeOutAndRemove(popup, overlay);
} else {
notifications('Error: Please enter a number greater than 0', 'error', '⚠️', '5000');
}
});
popup.appendChild(submitButton);
popup.appendChild(closeButton);
}
/*******************************************************
name of function: fetchServersWithRetry
description: Fetches server data with retry logic and a delay between requests to avoid rate-limiting.
Uses GM_xmlhttpRequest instead of fetch.
*******************************************************/
async function fetchServersWithRetry(url, retries = 15, currentDelay = 750) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: url,
onload: function(response) {
// Check for 429 Rate Limit error
if (response.status === 429) {
if (retries > 0) {
const newDelay = currentDelay * 1; // Exponential backoff
ConsoleLogEnabled(`[DEBUG] Rate limited. Waiting ${newDelay / 1000} seconds before retrying...`);
setTimeout(() => {
resolve(fetchServersWithRetry(url, retries - 1, newDelay)); // Retry with increased delay
}, newDelay);
} else {
ConsoleLogEnabled('[DEBUG] Rate limit retries exhausted.');
notifications('Error: Rate limited please try again later.', 'error', '⚠️', '5000')
reject(new Error('RateLimit'));
}
return;
}
// Handle other HTTP errors
if (response.status < 200 || response.status >= 300) {
ConsoleLogEnabled('[DEBUG] HTTP error:', response.status, response.statusText);
reject(new Error(`HTTP error: ${response.status}`));
return;
}
// Parse and return the JSON data
try {
const data = JSON.parse(response.responseText);
ConsoleLogEnabled('[DEBUG] Fetched data successfully:', data);
resolve(data);
} catch (error) {
ConsoleLogEnabled('[DEBUG] Error parsing JSON:', error);
reject(error);
}
},
onerror: function(error) {
ConsoleLogEnabled('[DEBUG] Error in GM_xmlhttpRequest:', error);
reject(error);
}
});
});
}
/*******************************************************
name of function: filterServersByPlayerCount
description: Filters servers to show only those with a player count equal to or below the specified max.
If no exact matches are found, prioritizes servers with player counts lower than the input.
Keeps fetching until at least 8 servers are found, with a dynamic delay between requests.
*******************************************************/
async function filterServersByPlayerCount(maxPlayers) {
// Validate maxPlayers before proceeding
if (isNaN(maxPlayers) || maxPlayers < 1 || !Number.isInteger(maxPlayers)) {
ConsoleLogEnabled('[DEBUG] Invalid input for maxPlayers.');
notifications('Error: Please input a valid whole number greater than or equal to 1.', 'error', '⚠️', '5000');
return;
}
// Disable UI elements and clear the server list
Loadingbar(true);
disableLoadMoreButton();
disableFilterButton(true);
const serverList = document.querySelector('#rbx-game-server-item-container');
serverList.innerHTML = '';
const gameId = window.location.pathname.split('/')[2];
let cursor = null;
let serversFound = 0;
let serverMaxPlayers = null;
let isCloserToOne = null;
let topDownServers = []; // Servers collected during top-down search
let bottomUpServers = []; // Servers collected during bottom-up search
let currentDelay = 500; // Initial delay of 0.5 seconds
const timeLimit = 3 * 60 * 1000; // 3 minutes in milliseconds
const startTime = Date.now(); // Record the start time
notifications('Will search for a maximum of 3 minutes to find a server.', 'success', '🔎', '5000');
try {
while (serversFound < 16) {
// Check if the time limit has been exceeded
if (Date.now() - startTime > timeLimit) {
ConsoleLogEnabled('[DEBUG] Time limit reached. Proceeding to fallback servers.');
notifications('Warning: Time limit reached. Proceeding to fallback servers.', 'warning', '❗', '5000');
break;
}
// Fetch initial data to determine serverMaxPlayers and isCloserToOne
if (!serverMaxPlayers) {
const initialUrl = cursor ?
`https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=100&cursor=${cursor}` :
`https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=100`;
const initialData = await fetchServersWithRetry(initialUrl);
if (initialData.data.length > 0) {
serverMaxPlayers = initialData.data[0].maxPlayers;
isCloserToOne = maxPlayers <= (serverMaxPlayers / 2);
} else {
notifications("No servers found in initial fetch.", "error", "⚠️", "5000")
ConsoleLogEnabled('[DEBUG] No servers found in initial fetch.', 'warning', '❗');
break;
}
}
// Validate maxPlayers against serverMaxPlayers
if (maxPlayers >= serverMaxPlayers) {
ConsoleLogEnabled('[DEBUG] Invalid input: maxPlayers is greater than or equal to serverMaxPlayers.');
notifications(`Error: Please input a number between 1 through ${serverMaxPlayers - 1}`, 'error', '⚠️', '5000');
return;
}
// Adjust the URL based on isCloserToOne
const baseUrl = isCloserToOne ?
`https://games.roblox.com/v1/games/${gameId}/servers/public?sortOrder=1&excludeFullGames=true&limit=100` :
`https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=100`; // why does this work lmao
const url = cursor ? `${baseUrl}&cursor=${cursor}` : baseUrl;
const data = await fetchServersWithRetry(url);
// Safety check: Ensure the server list is valid and iterable
if (!Array.isArray(data.data)) {
ConsoleLogEnabled('[DEBUG] Invalid server list received. Waiting 1 second before retrying...');
await delay(1000); // Wait 1 second before retrying
continue; // Skip the rest of the loop and retry
}
// Filter and process servers
for (const server of data.data) {
if (server.playing === maxPlayers) {
await rbx_card(server.id, server.playerTokens, server.maxPlayers, server.playing, gameId);
serversFound++;
if (serversFound >= 16) {
break;
}
} else if (!isCloserToOne && server.playing > maxPlayers) {
topDownServers.push(server); // Add to top-down fallback list
} else if (isCloserToOne && server.playing < maxPlayers) {
bottomUpServers.push(server); // Add to bottom-up fallback list
}
}
// Exit if no more servers are available
if (!data.nextPageCursor) {
break;
}
cursor = data.nextPageCursor;
// Adjust delay dynamically
if (currentDelay > 150) {
currentDelay = Math.max(150, currentDelay / 2); // Gradually reduce delay
}
ConsoleLogEnabled(`[DEBUG] Waiting ${currentDelay / 1000} seconds before next request...`);
await delay(currentDelay);
}
// If no exact matches were found or time limit reached, use fallback servers
if (serversFound === 0 && (topDownServers.length > 0 || bottomUpServers.length > 0)) {
notifications(`There are no servers with ${maxPlayers} players. Showing servers closest to ${maxPlayers} players.`, 'warning', '😔', '8000');
// Sort top-down servers by player count (ascending)
topDownServers.sort((a, b) => a.playing - b.playing);
// Sort bottom-up servers by player count (descending)
bottomUpServers.sort((a, b) => b.playing - a.playing);
// Combine both fallback lists (prioritize top-down servers first)
const combinedFallback = [...topDownServers, ...bottomUpServers];
for (const server of combinedFallback) {
await rbx_card(server.id, server.playerTokens, server.maxPlayers, server.playing, gameId);
serversFound++;
if (serversFound >= 16) {
break;
}
}
}
if (serversFound <= 0) {
notifications('No Servers Found Within The Provided Criteria', 'info', '🔎', '5000');
}
} catch (error) {
ConsoleLogEnabled('[DEBUG] Error in filterServersByPlayerCount:', error);
} finally {
Loadingbar(false);
disableFilterButton(false);
}
}
/*********************************************************************************************************************************************************************************************************************************************
Functions for the 4th button
*********************************************************************************************************************************************************************************************************************************************/
/*******************************************************
name of function: random_servers
description: Fetches servers from two different URLs, combines the results, ensures no duplicates, shuffles the list, and passes the server information to the rbx_card function in a random order. Handles 429 errors with retries.
*******************************************************/
async function random_servers() {
notifications('Finding Random Servers. Please wait 2-5 seconds', 'success', '🔎', '5000');
// Disable the "Load More" button and show the loading bar
Loadingbar(true);
disableFilterButton(true);
disableLoadMoreButton();
// Get the game ID from the URL
const gameId = window.location.pathname.split('/')[2];
try {
// Fetch servers from the first URL with retry logic
const firstUrl = `https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=10`;
const firstData = await fetchWithRetry(firstUrl, 10); // Retry up to 3 times
// Wait for 5 seconds
await delay(1500);
// Fetch servers from the second URL with retry logic
const secondUrl = `https://games.roblox.com/v1/games/${gameId}/servers/public?sortOrder=1&excludeFullGames=true&limit=10`;
const secondData = await fetchWithRetry(secondUrl, 10); // Retry up to 3 times
// Combine the servers from both URLs
const combinedServers = [...firstData.data, ...secondData.data];
// Remove duplicates by server ID
const uniqueServers = [];
const seenServerIds = new Set();
for (const server of combinedServers) {
if (!seenServerIds.has(server.id)) {
seenServerIds.add(server.id);
uniqueServers.push(server);
}
}
// Shuffle the unique servers array
const shuffledServers = shuffleArray(uniqueServers);
// Get the first 16 shuffled servers
const selectedServers = shuffledServers.slice(0, 16);
// Process each server in random order
for (const server of selectedServers) {
const {
id: serverId,
playerTokens,
maxPlayers,
playing
} = server;
// Pass the server data to the card creation function
await rbx_card(serverId, playerTokens, maxPlayers, playing, gameId);
}
} catch (error) {
ConsoleLogEnabled('Error fetching server data:', error);
notifications('Error: Failed to fetch server data. Please try again later.', 'error', '⚠️', '5000');
} finally {
// Hide the loading bar and enable the filter button
Loadingbar(false);
disableFilterButton(false);
}
}
/*******************************************************
name of function: fetchWithRetry
description: Fetches data from a URL with retry logic for 429 errors using GM_xmlhttpRequest.
*******************************************************/
function fetchWithRetry(url, retries) {
return new Promise((resolve, reject) => {
const attemptFetch = (attempt = 0) => {
GM_xmlhttpRequest({
method: "GET",
url: url,
onload: function(response) {
if (response.status === 429) {
if (attempt < retries) {
ConsoleLogEnabled(`Rate limited. Retrying in 2.5 seconds... (Attempt ${attempt + 1}/${retries})`);
setTimeout(() => attemptFetch(attempt + 1), 1500); // Wait 1.5 seconds and retry
} else {
reject(new Error('Rate limit exceeded after retries'));
}
} else if (response.status >= 200 && response.status < 300) {
try {
const data = JSON.parse(response.responseText);
resolve(data);
} catch (error) {
reject(new Error('Failed to parse JSON response'));
}
} else {
reject(new Error(`HTTP error: ${response.status}`));
}
},
onerror: function(error) {
if (attempt < retries) {
ConsoleLogEnabled(`Error occurred. Retrying in 10 seconds... (Attempt ${attempt + 1}/${retries})`);
setTimeout(() => attemptFetch(attempt + 1), 10000); // Wait 10 seconds and retry
} else {
reject(error);
}
}
});
};
attemptFetch();
});
}
/*******************************************************
name of function: shuffleArray
description: Shuffles an array using the Fisher-Yates algorithm.
*******************************************************/
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1)); // Random index from 0 to i
[array[i], array[j]] = [array[j], array[i]]; // Swap elements
}
return array;
}
/*********************************************************************************************************************************************************************************************************************************************
Functions for the 5th button. taken from my other project
*********************************************************************************************************************************************************************************************************************************************/
if (Isongamespage) {
// Create a <style> element
const style = document.createElement('style');
style.textContent = `
/* Overlay for the modal background */
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.85); /* Solid black overlay */
z-index: 1000; /* Ensure overlay is below the popup */
opacity: 0; /* Start invisible */
animation: fadeIn 0.3s ease forwards; /* Fade-in animation */
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/* Popup Container for the server region */
.filter-popup {
background-color: #1e1e1e; /* Darker background */
color: #ffffff; /* White text */
padding: 25px;
border-radius: 12px;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.5);
width: 320px;
max-width: 90%;
position: fixed; /* Fixed positioning */
top: 50%; /* Center vertically */
left: 50%; /* Center horizontally */
transform: translate(-50%, -50%); /* Offset to truly center */
text-align: center;
z-index: 1001; /* Ensure popup is above the overlay */
border: 1px solid #444; /* Subtle border */
opacity: 0; /* Start invisible */
animation: fadeInPopup 0.3s ease 0.1s forwards; /* Fade-in animation with delay */
}
@keyframes fadeInPopup {
from {
opacity: 0;
transform: translate(-50%, -55%); /* Slight upward offset */
}
to {
opacity: 1;
transform: translate(-50%, -50%); /* Center position */
}
}
/* Fade-out animation for overlay and popup */
.overlay.fade-out {
animation: fadeOut 0.3s ease forwards;
}
.filter-popup.fade-out {
animation: fadeOutPopup 0.3s ease forwards;
}
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes fadeOutPopup {
from {
opacity: 1;
transform: translate(-50%, -50%); /* Center position */
}
to {
opacity: 0;
transform: translate(-50%, -55%); /* Slight upward offset */
}
}
/* Close Button for the server selector */
#closePopup {
position: absolute;
top: 5px; /* Reduced from 12px to 5px */
right: 1px; /* Reduced from 12px to 5px */
background: transparent; /* Transparent background */
border: none;
color: #ffffff; /* White color */
font-size: 20px;
cursor: pointer;
width: 28px;
height: 28px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.3s ease, color 0.3s ease;
}
#closePopup:hover {
background-color: rgba(255, 255, 255, 0.1); /* Light hover effect */
color: #ff4444; /* Red color on hover */
}
/* Label */
.filter-popup label {
display: block;
margin-bottom: 12px;
font-size: 16px;
color: #ffffff;
font-weight: 500; /* Slightly bolder text */
}
/* Dropdown */
.filter-popup select {
background-color: #333; /* Darker gray background */
color: #ffffff; /* White text */
padding: 10px;
border-radius: 6px;
border: 1px solid #555; /* Darker border */
width: 100%;
margin-bottom: 12px;
font-size: 14px;
transition: border-color 0.3s ease;
}
.filter-popup select:focus {
border-color: #888; /* Lighter border on focus */
outline: none;
}
/* Custom Input */
.filter-popup input[type="number"] {
background-color: #333; /* Darker gray background */
color: #ffffff; /* White text */
padding: 10px;
border-radius: 6px;
border: 1px solid #555; /* Darker border */
width: 100%;
margin-bottom: 12px;
font-size: 14px;
transition: border-color 0.3s ease;
}
.filter-popup input[type="number"]:focus {
border-color: #888; /* Lighter border on focus */
outline: none;
}
/* Confirm Button */
#confirmServerCount {
background-color: #444; /* Dark gray background */
color: #ffffff; /* White text */
padding: 10px 20px;
border: 1px solid #666; /* Gray border */
border-radius: 6px;
cursor: pointer;
font-size: 14px;
width: 100%;
transition: background-color 0.3s ease, transform 0.2s ease;
}
#confirmServerCount:hover {
background-color: #555; /* Lighter gray on hover */
transform: translateY(-1px); /* Slight lift effect */
}
#confirmServerCount:active {
transform: translateY(0); /* Reset lift effect on click */
}
/* Highlighted server item */
.rbx-game-server-item.highlighted {
border: 2px solid #4caf50; /* Green border */
border-radius: 8px;
background-color: rgba(76, 175, 80, 0.1); /* Subtle green background */
}
/* Disabled fetch button */
.fetch-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Popup Header */
.popup-header {
margin-bottom: 24px;
text-align: left;
padding: 16px;
background-color: rgba(255, 255, 255, 0.05); /* Subtle background for contrast */
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.1); /* Subtle border */
transition: background-color 0.3s ease, border-color 0.3s ease;
}
.popup-header:hover {
background-color: rgba(255, 255, 255, 0.08); /* Slightly brighter on hover */
border-color: rgba(255, 255, 255, 0.2);
}
.popup-header h3 {
margin: 0 0 12px 0;
font-size: 22px;
color: #ffffff;
font-weight: 700; /* Bolder for emphasis */
letter-spacing: -0.5px; /* Tighter letter spacing for modern look */
}
.popup-header p {
margin: 0;
font-size: 14px;
color: #cccccc;
line-height: 1.6; /* Improved line height for readability */
opacity: 0.9; /* Slightly transparent for a softer look */
}
/* Popup Footer */
.popup-footer {
margin-top: 20px;
text-align: left;
font-size: 14px;
color: #ffcc00; /* Yellow color for warnings */
background-color: rgba(255, 204, 0, 0.15); /* Lighter yellow background */
padding: 12px;
border-radius: 8px;
border: 1px solid rgba(255, 204, 0, 0.15); /* Subtle border */
transition: background-color 0.3s ease, border-color 0.3s ease;
}
.popup-footer:hover {
background-color: rgba(255, 204, 0, 0.25); /* Slightly brighter on hover */
border-color: rgba(255, 204, 0, 0.25);
}
.popup-footer p {
margin: 0;
line-height: 1.5;
font-weight: 500; /* Slightly bolder for emphasis */
}
/* Label */
.filter-popup label {
display: block;
margin-bottom: 12px;
font-size: 15px;
color: #ffffff;
font-weight: 500;
text-align: left;
opacity: 0.9; /* Slightly transparent for a softer look */
transition: opacity 0.3s ease;
}
.filter-popup label:hover {
opacity: 1; /* Fully opaque on hover */
}
select:hover, select:focus {
border-color: #ffffff;
outline: none;
}
`;
// Append the <style> element to the document head
document.head.appendChild(style);
}
// Function to show the message under the "Load More" button
function showMessage(message) {
const loadMoreButtonContainer = document.querySelector('.rbx-running-games-footer');
if (!loadMoreButtonContainer) {
ConsoleLogEnabled("Load More button container not found!");
return;
}
// Create the message element
const messageElement = document.createElement('div');
messageElement.className = 'filter-message';
messageElement.textContent = message;
// Clear any existing message and append the new one
const existingMessage = loadMoreButtonContainer.querySelector('.filter-message');
if (existingMessage) {
existingMessage.remove(); // Remove the existing message if it exists
}
loadMoreButtonContainer.appendChild(messageElement);
return messageElement;
}
// Function to hide the message of the showmessage functioon
function hideMessage() {
const messageElement = document.querySelector('.filter-message');
if (messageElement) messageElement.remove();
}
// Function to show the popup for random stuff
function showPopup() {
const overlay = document.createElement('div');
overlay.className = 'overlay';
const popup = document.createElement('div');
popup.className = 'filter-popup';
popup.textContent = 'Uh somethings wrong if you see this message. Please report to the greasyfork issues page!';
document.body.appendChild(overlay);
document.body.appendChild(popup);
return popup;
}
// Function to hide the popup for the stuff
function hidePopup() {
const popup = document.querySelector('.filter-popup');
const overlay = document.querySelector('.overlay');
if (popup) popup.remove();
if (overlay) overlay.remove();
}
// Function to fetch server details so game id and job id. yea!
async function fetchServerDetails(gameId, jobId) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "POST",
url: "https://gamejoin.roblox.com/v1/join-game-instance", // url for game id
headers: { // doesent need cookie cuase of magic
"Content-Type": "application/json",
"User-Agent": "Roblox/WinInet",
},
data: JSON.stringify({
placeId: gameId,
gameId: jobId
}),
onload: function(response) {
const json = JSON.parse(response.responseText);
ConsoleLogEnabled("API Response:", json); // This prints the full response
// Check if the response indicates that the user needs to purchase the game
if (json.status === 12 && json.message === 'You need to purchase access to this game before you can play.') { // yea error message!
reject('purchase_required'); // Special error code for this case yea!
return;
}
const address = json?.joinScript?.UdmuxEndpoints?.[0]?.Address ?? json?.joinScript?.MachineAddress;
if (!address) {
ConsoleLogEnabled("API Response (Unknown Location) Which means Full Server!:", json); // Log the API response for debug
reject(`Unable to fetch server location: Status ${json.status}`); // debug
return;
}
const location = serverRegionsByIp[address.replace(/^(128\.116\.\d+)\.\d+$/, "$1.0")]; // lmao all servers atart with this so yea dont argue with me
if (!location) {
ConsoleLogEnabled("API Response (Unknown Location):", json); // Log the API response into the chat. might remove it from production but idc rn
reject(`Unknown server address ${address}`);
return;
}
resolve(location);
},
onerror: function(error) {
ConsoleLogEnabled("API Request Failed:", error); // damn if this happpens idk what to tell u
reject(`Failed to fetch server details: ${error}`);
},
});
});
}
// cusomt delay also known as sleep fucntion in js cause this language sucks and doesent have a default function
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Function to create a popup for selecting the number of servers
// basically yea thats what it doesent
function createServerCountPopup(callback) {
const overlay = document.createElement('div');
overlay.className = 'overlay';
const popup = document.createElement('div');
popup.className = 'filter-popup'; // reason 100 is selected because thjats how many the api will show per request
popup.innerHTML = `
<button id="closePopup">X</button>
<div class="popup-header">
<h3>Select Number of Servers</h3>
<p>Choose how many servers you want to search. Higher values will provide more location variety but may take longer to process.</p>
<div class="popup-footer">
<p><strong>Note:</strong> Searching over 100 servers may take longer and could result in rate limiting.</p> <!-- For everyone's sake dont ask me about the chain.-->
</div>
</div>
<label for="serverCount">Select Number of Servers:</label>
<select id="serverCount">
<option value="10">10 Servers</option>
<option value="25">25 Servers</option>
<option value="50">50 Servers</option>
<option value="100" selected>100 Servers</option>
<option value="200">200 Servers</option>
<option value="500">500 Servers</option>
<option value="1000">1000 Servers</option>
<option value="custom">Custom</option>
</select>
<input id="customServerCount" type="number" min="1" max="1000" placeholder="Enter a number (1-1000)" style="display: none;">
<button id="confirmServerCount">Confirm</button>
`;
document.body.appendChild(overlay);
document.body.appendChild(popup);
const serverCountDropdown = popup.querySelector('#serverCount');
const customServerCountInput = popup.querySelector('#customServerCount');
const confirmButton = popup.querySelector('#confirmServerCount');
const closeButton = popup.querySelector('#closePopup');
// Show/hide custom input based on dropdown selection
serverCountDropdown.addEventListener('change', () => {
if (serverCountDropdown.value === 'custom') {
customServerCountInput.style.display = 'block';
} else {
customServerCountInput.style.display = 'none';
}
});
// button click on start or what ever
confirmButton.addEventListener('click', () => {
let serverCount;
if (serverCountDropdown.value === 'custom') {
serverCount = parseInt(customServerCountInput.value);
// Validate custom input
if (isNaN(serverCount) || serverCount < 1 || serverCount > 1000) {
notifications('Error: Please enter a valid number between 1 and 1000.', 'error', '⚠️', '5000')
return;
}
} else {
serverCount = parseInt(serverCountDropdown.value);
}
// Show an alert if the user selects a number above 100
if (serverCount > 100) { // error cause people dont know about this maybe. idk yea so here. also if u think this is a stupid way i should have done it before the button press idc so yea
notifications('Warning: Searching over 100 servers may take some time and you might get rate limited!', 'warning', '❗', '8000');
}
// Pass the selected server count to the callback
callback(serverCount);
disableFilterButton(true); // disbale filter button
disableLoadMoreButton(true); // disable load more button
hidePopup();
Loadingbar(true); // enable loading bar
});
// Close button logic :))
closeButton.addEventListener('click', () => {
hidePopup();
});
// Function to hide the popup
// yea im dumb and used the same function name but it works and im too lazy to change it
function hidePopup() {
const overlay = document.querySelector('.overlay');
const popup = document.querySelector('.filter-popup');
// Add fade-out classes
overlay.classList.add('fade-out');
popup.classList.add('fade-out');
// Remove elements after animation completes
setTimeout(() => {
overlay.remove();
popup.remove();
}, 300); // Match the duration of the fade-out animation
}
}
// Function to fetch public servers
// totallimit is amount of sevrers to fetch
async function fetchPublicServers(gameId, totalLimit) {
let servers = [];
let cursor = null;
while (servers.length < totalLimit) { // too lazy to comment any of this. hopefully i remember what this does in the future
const url = `https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=100${cursor ? `&cursor=${cursor}` : ''}`;
const response = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: url,
onload: function(response) {
resolve(JSON.parse(response.responseText));
},
onerror: function(error) {
reject(`Failed to fetch public servers: ${error}`);
},
});
});
servers = servers.concat(response.data);
if (!response.nextPageCursor || servers.length >= totalLimit) {
break;
}
cursor = response.nextPageCursor;
await delay(3000); // wait 3 seconds before each page request. if u think this is slow i tried 1 second i got rate limited :|
}
return servers.slice(0, totalLimit);
}
function createFilterDropdowns(servers) {
// Create the main filter container
const filterContainer = document.createElement('div');
Object.assign(filterContainer.style, {
display: 'flex',
gap: '24px',
alignItems: 'center',
padding: '28px',
background: 'linear-gradient(145deg, rgba(25,25,25,0.98) 0%, rgba(15,15,15,0.98) 100%)',
borderRadius: '20px',
boxShadow: '0 16px 32px rgba(0,0,0,0.4)',
backdropFilter: 'blur(20px)',
opacity: '0',
transform: 'translateY(-40px) scale(0.95)',
transition: 'all 0.8s cubic-bezier(0.23, 1, 0.32, 1)',
position: 'relative',
border: '1px solid rgba(255,255,255,0.1)',
margin: '24px',
fontFamily: "'Inter', sans-serif",
fontSize: '16px' // Larger font size
});
// Add animated border glow with red accents
const borderGlow = document.createElement('div');
Object.assign(borderGlow.style, {
position: 'absolute',
inset: '0',
borderRadius: '20px',
pointerEvents: 'none',
background: 'linear-gradient(45deg, rgba(255,50,50,0.2), rgba(255,50,50,0.1))',
zIndex: '-1',
animation: 'gradientShift 12s ease infinite'
});
filterContainer.appendChild(borderGlow);
// Add dynamic CSS for animations
const style = document.createElement('style');
style.textContent = `
@keyframes gradientShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
select::-webkit-scrollbar {
width: '10px';
}
select::-webkit-scrollbar-track {
background: rgba(30,30,30,0.5);
}
select::-webkit-scrollbar-thumb {
background: rgba(255,50,50,0.4);
border-radius: '6px';
}
select::-webkit-scrollbar-thumb:hover {
background: rgba(255,50,50,0.6);
}
`;
document.head.appendChild(style);
// Add logo with hover effects
const logo = document.createElement('img');
logo.src = window.Base64Images.logo;
Object.assign(logo.style, {
width: '56px',
height: '56px',
borderRadius: '14px',
marginRight: '20px',
transition: 'transform 0.4s ease, filter 0.4s ease',
filter: 'drop-shadow(0 6px 12px rgba(255,50,50,0.25))'
});
logo.addEventListener('mouseover', () => {
logo.style.transform = 'rotate(-10deg) scale(1.2)';
logo.style.filter = 'drop-shadow(0 8px 16px rgba(255,50,50,0.4))';
});
logo.addEventListener('mouseout', () => {
logo.style.transform = 'rotate(0) scale(1)';
logo.style.filter = 'drop-shadow(0 6px 12px rgba(255,50,50,0.25))';
});
filterContainer.appendChild(logo);
// Function to create a premium dropdown
const createDropdown = (id, placeholder) => {
const wrapper = document.createElement('div');
Object.assign(wrapper.style, {
position: 'relative',
minWidth: '220px' // Wider dropdown for a modern look
});
const dropdown = document.createElement('select');
dropdown.id = id;
dropdown.innerHTML = `<option value="">${placeholder}</option>`;
Object.assign(dropdown.style, {
width: '100%',
padding: '16px 56px 16px 24px', // More padding for a spacious feel
fontSize: '16px', // Larger font size
fontWeight: '500',
background: 'linear-gradient(145deg, rgba(40,40,40,0.9), rgba(25,25,25,0.9))',
color: '#FF1A1A',
border: '1px solid rgba(255,255,255,0.15)',
borderRadius: '12px',
boxShadow: '0 6px 12px rgba(0,0,0,0.2)',
appearance: 'none',
cursor: 'pointer',
transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
opacity: '0',
transform: 'translateY(-20px)'
});
// Custom chevron icon
const chevron = document.createElement('div');
chevron.innerHTML = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 9l6 6 6-6"/></svg>`;
Object.assign(chevron.style, {
position: 'absolute',
right: '20px',
top: '50%',
transform: 'translateY(-50%)',
pointerEvents: 'none',
transition: 'transform 0.4s ease',
color: 'rgba(255,50,50,0.9)' // Red chevron for a premium touch
});
// Dropdown interactions
dropdown.addEventListener('mouseover', () => {
dropdown.style.background = 'linear-gradient(145deg, rgba(60,60,60,0.9), rgba(40,40,40,0.9))';
dropdown.style.boxShadow = '0 8px 16px rgba(0,0,0,0.3)';
chevron.style.transform = 'translateY(-50%) rotate(180deg)';
});
dropdown.addEventListener('mouseout', () => {
dropdown.style.background = 'linear-gradient(145deg, rgba(40,40,40,0.9), rgba(25,25,25,0.9))';
dropdown.style.boxShadow = '0 6px 12px rgba(0,0,0,0.2)';
chevron.style.transform = 'translateY(-50%)';
});
dropdown.addEventListener('focus', () => {
dropdown.style.outline = '2px solid rgba(255,50,50,0.5)';
dropdown.style.outlineOffset = '2px';
});
dropdown.addEventListener('change', () => {
dropdown.style.transform = 'scale(0.98)';
setTimeout(() => dropdown.style.transform = 'scale(1)', 150);
});
// Fade-in animation
setTimeout(() => {
dropdown.style.opacity = '1';
dropdown.style.transform = 'translateY(0)';
}, 400);
wrapper.appendChild(dropdown);
wrapper.appendChild(chevron);
return wrapper;
};
// Create dropdowns
const countryDropdown = createDropdown('countryFilter', 'All Countries');
const cityDropdown = createDropdown('cityFilter', 'All Cities');
// Populate dropdowns with server data
const countryCounts = {};
servers.forEach(server => {
const country = server.location.country.name;
countryCounts[country] = (countryCounts[country] || 0) + 1;
});
Object.keys(countryCounts).forEach(country => {
const option = document.createElement('option');
option.value = country;
option.textContent = `${country} (${countryCounts[country]})`;
countryDropdown.querySelector('select').appendChild(option);
});
countryDropdown.querySelector('select').addEventListener('change', () => {
const selectedCountry = countryDropdown.querySelector('select').value;
cityDropdown.querySelector('select').innerHTML = '<option value="">All Cities</option>';
if (selectedCountry) {
const cityCounts = {};
servers
.filter(server => server.location.country.name === selectedCountry)
.forEach(server => {
const city = server.location.city;
const region = server.location.region?.name;
const cityKey = region ? `${city}, ${region}` : city;
cityCounts[cityKey] = (cityCounts[cityKey] || 0) + 1;
});
Object.keys(cityCounts).forEach(city => {
const option = document.createElement('option');
option.value = city;
option.textContent = `${city} (${cityCounts[city]})`;
cityDropdown.querySelector('select').appendChild(option);
});
// Animate city dropdown update
cityDropdown.querySelector('select').style.opacity = '0';
cityDropdown.querySelector('select').style.transform = 'translateY(-10px)';
setTimeout(() => {
cityDropdown.querySelector('select').style.opacity = '1';
cityDropdown.querySelector('select').style.transform = 'translateY(0)';
}, 150);
}
});
// Append dropdowns to container
filterContainer.appendChild(countryDropdown);
filterContainer.appendChild(cityDropdown);
// Fade-in container
setTimeout(() => {
filterContainer.style.opacity = '1';
filterContainer.style.transform = 'translateY(0) scale(1)';
}, 200);
return filterContainer;
}
// Function to filter servers based on selected country and city cause im lazy
function filterServers(servers, country, city) {
return servers.filter(server => {
const matchesCountry = !country || server.location.country.name === country;
const matchesCity = !city || `${server.location.city}${server.location.region?.name ? `, ${server.location.region.name}` : ''}` === city;
return matchesCountry && matchesCity;
});
}
// Function to sort servers by ping. maybe inaccurate but thats roblox's problem not mine
function sortServersByPing(servers) {
return servers.sort((a, b) => a.server.ping - b.server.ping);
}
async function fetchPlayerThumbnails_servers(playerTokens) {
const body = playerTokens.map(token => ({
requestId: `0:${token}:AvatarHeadshot:150x150:png:regular`,
type: "AvatarHeadShot",
targetId: 0,
token,
format: "png",
size: "150x150",
}));
const response = await fetch("https://thumbnails.roblox.com/v1/batch", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(body),
});
const data = await response.json();
return data.data || [];
}
async function rebuildServerList(gameId, totalLimit, best_connection) {
const serverListContainer = document.getElementById("rbx-game-server-item-container");
// If "Best Connection" is enabled
// FUNCTION FOR THE 6TH BUTTON!
if (best_connection === true) {
disableLoadMoreButton(true);
disableFilterButton(true);
notifications("Retrieving Location...", "success", "🌎", '5000')
// Ask for the user's location
const userLocation = await getUserLocation();
if (!userLocation) {
notifications('Error: Unable to fetch your location. Please enable location access.', 'error', '⚠️', '5000');
disableFilterButton(false);
return;
}
// Fetch 50 servers
const servers = await fetchPublicServers(gameId, 50);
if (servers.length === 0) {
notifications('Error: No servers found. Please try again later.', 'error', '⚠️', '5000');
disableFilterButton(false);
return;
}
// Calculate distances and find the closest server
let closestServer = null;
let minDistance = Infinity;
let closestServerLocation = null;
for (const server of servers) {
const {
id: serverId,
maxPlayers,
playing
} = server;
// Skip full servers
if (playing >= maxPlayers) {
continue;
}
try {
// Fetch server location
const location = await fetchServerDetails(gameId, serverId);
// Calculate distance
const distance = calculateDistance(
userLocation.latitude,
userLocation.longitude,
location.latitude,
location.longitude
);
// Update closest server
if (distance < minDistance) {
minDistance = distance;
closestServer = server;
closestServerLocation = location;
}
} catch (error) {
ConsoleLogEnabled(`Error fetching details for server ${serverId}:`, error);
// Skip this server and continue with the next one
continue;
}
}
if (closestServer) {
// Automatically join the closest server
showLoadingOverlay();
Roblox.GameLauncher.joinGameInstance(gameId, closestServer.id);
notifications(`Joining nearest server!
Server ID: ${closestServer.id}
Distance: ${(minDistance / 1.609).toFixed(2)} miles | ${minDistance.toFixed(2)} km
Location (Country): ${closestServerLocation.country.name}.`, 'success', '🚀', '5000');
disableFilterButton(false);
Loadingbar(false);
} else {
notifications('No valid servers found. Please try again later after refreshing the webpage. Filter button disabled.', 'error', '⚠️', '8000');
Loadingbar(false);
}
return; // Exit the function after joining the best server
}
// Rest of the original function (for non-"Best Connection" mode)
if (!serverListContainer) {
ConsoleLogEnabled("Server list container not found!");
notifications('Error: No Servers found. There is nobody playing this game. Please refresh the page.', 'error', '⚠️', '8000');
return;
}
const messageElement = showMessage("Just a moment, calculating the distance from servers..");
try {
// Retrieve user's location for distance calculations
const userLocation = await getUserLocation();
if (!userLocation) {
notifications('Error: Unable to fetch your location. Please enable location access.', 'error', '⚠️', '5000');
disableFilterButton(false);
return;
}
const servers = await fetchPublicServers(gameId, totalLimit);
const totalServers = servers.length;
let skippedServers = 0;
messageElement.textContent = `Filtering servers, please do not leave this page as it slows down the search...\n${totalServers} servers found, 0 servers loaded.`;
notifications(`Please do not leave this page as it slows down the search. \nFound a total of ${totalServers} servers.`, 'success', '👍', '8000');
const serverDetails = [];
for (let i = 0; i < servers.length; i++) {
const server = servers[i];
const {
id: serverId,
maxPlayers,
playing,
ping,
fps,
playerTokens
} = server;
let location;
try {
location = await fetchServerDetails(gameId, serverId);
} catch (error) {
if (error === 'purchase_required') {
messageElement.textContent = "Cannot access server data because you haven't purchased the game.";
notifications('Error: Cannot access server data because you haven\'t purchased the game.', 'error', '⚠️', '5000');
Loadingbar(false); // disable loading bar
return;
} else {
ConsoleLogEnabled(error);
location = {
city: "Unknown",
country: {
name: "Unknown",
code: "??"
}
};
}
}
if (location.city === "Unknown" || playing >= maxPlayers) {
ConsoleLogEnabled(`Skipping server ${serverId} because it is full or location is unknown.`);
skippedServers++;
continue;
}
// Fetch player thumbnails
const playerThumbnails = playerTokens && playerTokens.length > 0 ? await fetchPlayerThumbnails_servers(playerTokens) : [];
serverDetails.push({
server,
location,
playerThumbnails
});
messageElement.textContent = `Filtering servers, please do not leave this page...\n${totalServers} servers found, ${i + 1} server locations found`;
}
if (serverDetails.length === 0) {
messageElement.textContent = "No servers found. Please try again with an increase in the number of servers to search for.";
notifications('Error: No servers found. Please try again with an increase in the number of servers to search for.', 'error', '⚠️', '5000');
Loadingbar(false); // disable loading bar
return;
}
const loadedServers = totalServers - skippedServers;
notifications(`Filtering complete!\n${totalServers} servers found, ${loadedServers} servers loaded, ${skippedServers} servers skipped (full).`, 'success', '👍', '5000');
messageElement.textContent = `Filtering complete!\n${totalServers} servers found, ${loadedServers} servers loaded, ${skippedServers} servers skipped (full).`;
Loadingbar(false); // disable loading bar
// Add filter dropdowns
const filterContainer = createFilterDropdowns(serverDetails);
serverListContainer.parentNode.insertBefore(filterContainer, serverListContainer);
// Style the server list container to use a grid layout
serverListContainer.style.display = "grid";
serverListContainer.style.gridTemplateColumns = "repeat(4, 1fr)"; // 4 columns
serverListContainer.style.gap = "16px"; // Gap between cards
const displayFilteredServers = (country, city) => {
serverListContainer.innerHTML = "";
const filteredServers = filterServers(serverDetails, country, city);
// Sort servers by distance from the user (fastest first)
const sortedServers = filteredServers.sort((a, b) => {
const distanceA = calculateDistance(userLocation.latitude, userLocation.longitude, a.location.latitude, a.location.longitude);
const distanceB = calculateDistance(userLocation.latitude, userLocation.longitude, b.location.latitude, b.location.longitude);
return distanceA - distanceB;
});
sortedServers.forEach(({
server,
location,
playerThumbnails
}) => {
const serverCard = document.createElement("li");
serverCard.className = "rbx-game-server-item col-md-3 col-sm-4 col-xs-6";
serverCard.style.width = "100%";
serverCard.style.minHeight = "400px";
serverCard.style.display = "flex";
serverCard.style.flexDirection = "column";
serverCard.style.justifyContent = "space-between";
serverCard.style.boxSizing = "border-box";
serverCard.style.outline = 'none';
serverCard.style.padding = '6px';
serverCard.style.borderRadius = '8px';
// Create label (now based on distance)
const pingLabel = document.createElement("div");
pingLabel.style.marginBottom = "5px"; // Adds spacing above label
pingLabel.style.padding = "5px 10px";
pingLabel.style.borderRadius = "8px";
pingLabel.style.fontWeight = "bold";
pingLabel.style.textAlign = "center"; // Centers the label
// Calculate distance from user to server
const distance = calculateDistance(
userLocation.latitude,
userLocation.longitude,
location.latitude,
location.longitude
);
// Calculate ping using the advanced formula
const calculatedPing = 2 * (distance / 180000) * 1000 * 1.8 + (20 + 0.04 * distance);
if (distance < 1250) {
pingLabel.textContent = "⚡ Fast";
pingLabel.style.backgroundColor = "#014737";
pingLabel.style.color = "#73e1bc";
} else if (distance < 5000) {
pingLabel.textContent = "⏳ OK";
pingLabel.style.backgroundColor = "#c75a00";
pingLabel.style.color = "#ffe8c2";
} else {
pingLabel.textContent = "🐌 Slow";
pingLabel.style.backgroundColor = "#771d1d";
pingLabel.style.color = "#fcc468";
}
// Create a container for player thumbnails
const thumbnailsContainer = document.createElement("div");
thumbnailsContainer.className = "player-thumbnails-container";
thumbnailsContainer.style.display = "grid";
thumbnailsContainer.style.gridTemplateColumns = "repeat(3, 60px)";
thumbnailsContainer.style.gridTemplateRows = "repeat(2, 60px)";
thumbnailsContainer.style.gap = "5px";
thumbnailsContainer.style.marginBottom = "10px";
// Add player thumbnails to the container (max 5)
const maxThumbnails = 5;
const displayedThumbnails = playerThumbnails.slice(0, maxThumbnails);
displayedThumbnails.forEach(thumb => {
if (thumb && thumb.imageUrl) {
const img = document.createElement("img");
img.src = thumb.imageUrl;
img.className = "avatar-card-image";
img.style.width = "60px";
img.style.height = "60px";
img.style.borderRadius = "50%";
thumbnailsContainer.appendChild(img);
}
});
// Add a placeholder for hidden players
const hiddenPlayers = server.playing - displayedThumbnails.length;
if (hiddenPlayers > 0) {
const placeholder = document.createElement("div");
placeholder.className = "avatar-card-image";
placeholder.style.width = "60px";
placeholder.style.height = "60px";
placeholder.style.borderRadius = "50%";
placeholder.style.backgroundColor = "#6a6f81";
placeholder.style.display = "flex";
placeholder.style.alignItems = "center";
placeholder.style.justifyContent = "center";
placeholder.style.color = "#fff";
placeholder.style.fontSize = "14px";
placeholder.textContent = `+${hiddenPlayers}`;
thumbnailsContainer.appendChild(placeholder);
}
// Server card content with both distance and calculated ping, with line spacers between each info
const cardItem = document.createElement("div");
cardItem.className = "card-item";
cardItem.style.display = "flex";
cardItem.style.flexDirection = "column";
cardItem.style.justifyContent = "space-between";
cardItem.style.height = "100%";
cardItem.innerHTML = `
${thumbnailsContainer.outerHTML}
<div class="rbx-game-server-details game-server-details">
<div class="text-info rbx-game-status rbx-game-server-status text-overflow">
${server.playing} of ${server.maxPlayers} people max
</div>
<div class="server-player-count-gauge border">
<div class="gauge-inner-bar border" style="width: ${(server.playing / server.maxPlayers) * 100}%;"></div>
</div>
<span data-placeid="${gameId}">
<button type="button" class="btn-full-width btn-control-xs rbx-game-server-join game-server-join-btn btn-primary-md btn-min-width">Join</button>
</span>
</div>
<div style="margin-top: 10px; text-align: center;">
${pingLabel.outerHTML}
<div class="info-lines" style="margin-top: 8px;">
<div class="ping-info">Ping: ${calculatedPing.toFixed(2)} ms</div>
<hr style="margin: 6px 0;">
<div class="ping-info">Distance: ${distance.toFixed(2)} km</div>
<hr style="margin: 6px 0;">
<div class="location-info">${location.city}, ${location.country.name}</div>
<hr style="margin: 6px 0;">
<div class="fps-info">FPS: ${Math.round(server.fps)}</div>
</div>
</div>
`;
const joinButton = cardItem.querySelector(".rbx-game-server-join");
joinButton.addEventListener("click", () => {
ConsoleLogEnabled(`Roblox.GameLauncher.joinGameInstance(${gameId}, "${server.id}")`);
showLoadingOverlay();
Roblox.GameLauncher.joinGameInstance(gameId, server.id);
});
const container = adjustJoinButtonContainer(joinButton);
const inviteButton = createInviteButton(gameId, server.id);
container.appendChild(inviteButton);
serverCard.appendChild(cardItem);
serverListContainer.appendChild(serverCard);
});
};
// Add event listeners to dropdowns
const countryFilter = document.getElementById('countryFilter');
const cityFilter = document.getElementById('cityFilter');
countryFilter.addEventListener('change', () => {
displayFilteredServers(countryFilter.value, cityFilter.value);
});
cityFilter.addEventListener('change', () => {
displayFilteredServers(countryFilter.value, cityFilter.value);
});
// Display all servers initially
displayFilteredServers("", "");
setTimeout(() => {
hideMessage();
}, 3000);
} catch (error) {
ConsoleLogEnabled("Error rebuilding server list:", error);
notifications('Filtering error. Try again or refresh to enable the filter button.', 'error', '⚠️ ', '8000');
messageElement.textContent = "An error occurred while filtering servers. Please try again.";
Loadingbar(false); // enable loading bar
} finally {
Loadingbar(false); // omg bruh i just realzed i could put this here but now im too lazy to thorugh the code to remove all of the loading bar disabl functions
disableFilterButton(false); // beta test filter button
//notifications('Note: The filter button is disabled while using server regions. Refresh to enable it.', 'info', '🔄', '15000')
}
}
// Function to extract the game ID from the URL
function extractGameId() {
const url = window.location.href;
const match = url.match(/roblox\.com\/games\/(\d+)/);
if (match && match[1]) {
return match[1]; // Return the game ID
}
return null; // Return null if no game ID is found
}
// Log the game ID to the console
const gameId = extractGameId();
// Function to create and append the Invite button
function createInviteButton(placeId, serverId) { // too lazy to comment this function tbh just ready the name
const inviteButton = document.createElement('button');
inviteButton.textContent = 'Invite';
inviteButton.className = 'btn-control-xs btn-primary-md btn-min-width btn-full-width';
inviteButton.style.width = '25%';
inviteButton.style.marginLeft = '5px';
inviteButton.style.padding = '4px 8px';
inviteButton.style.fontSize = '12px';
inviteButton.style.borderRadius = '8px';
inviteButton.style.backgroundColor = '#3b3e49';
inviteButton.style.borderColor = '#3b3e49';
inviteButton.style.color = '#ffffff';
inviteButton.style.cursor = 'pointer';
inviteButton.style.fontWeight = '500';
inviteButton.style.textAlign = 'center';
inviteButton.style.whiteSpace = 'nowrap';
inviteButton.style.verticalAlign = 'middle';
inviteButton.style.lineHeight = '100%';
inviteButton.style.fontFamily = 'Builder Sans, Helvetica Neue, Helvetica, Arial, Lucida Grande, sans-serif';
inviteButton.style.textRendering = 'auto';
inviteButton.style.webkitFontSmoothing = 'antialiased';
inviteButton.style.mozOsxFontSmoothing = 'grayscale';
inviteButton.addEventListener('click', () => {
const inviteLink = `https://oqarshi.github.io/Invite/?placeid=${placeId}&serverid=${serverId}`;
navigator.clipboard.writeText(inviteLink).then(() => {
ConsoleLogEnabled(`Invite link copied to clipboard: ${inviteLink}`);
notifications('Success! Invite link copied to clipboard!', 'success', '🎉', ' 2000');
}).catch(() => {
ConsoleLogEnabled('Failed to copy invite link.');
notifications('Error: Failed to copy invite link', 'error', '😔', '2000');
});
});
return inviteButton;
}
// Function to adjust the Join button and its container
function adjustJoinButtonContainer(joinButton) {
const container = document.createElement('div');
container.style.display = 'flex';
container.style.width = '100%';
joinButton.style.width = '75%';
joinButton.parentNode.insertBefore(container, joinButton);
container.appendChild(joinButton);
return container;
}
/*********************************************************************************************************************************************************************************************************************************************
Functions for the 6th button.
*********************************************************************************************************************************************************************************************************************************************/
function calculateDistance(lat1, lon1, lat2, lon2) {
const R = 6371; // Radius of the Earth in kilometers
const dLat = (lat2 - lat1) * (Math.PI / 180);
const dLon = (lon2 - lon1) * (Math.PI / 180);
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(lat1 * (Math.PI / 180)) * Math.cos(lat2 * (Math.PI / 180)) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c; // Distance in kilometers
}
function getUserLocation() {
return new Promise((resolve, reject) => {
if (!navigator.geolocation) {
ConsoleLogEnabled("Geolocation is not supported by this browser.");
return fetchFallbackLocation(resolve);
}
// Step 1: Try Standard Geolocation
navigator.geolocation.getCurrentPosition(
(position) => resolveSuccess(position, resolve),
async (error) => {
ConsoleLogEnabled("Geolocation error:", error);
// Step 2: Check Permission Status (if supported)
if (navigator.permissions) {
try {
const permissionStatus = await navigator.permissions.query({ name: "geolocation" });
ConsoleLogEnabled("Geolocation permission status:", permissionStatus.state);
if (permissionStatus.state === "denied") {
ConsoleLogEnabled("User denied location access.");
return fetchFallbackLocation(resolve);
}
} catch (permError) {
ConsoleLogEnabled("Error checking permission status:", permError);
}
}
// Step 3: Try Alternative Geolocation API (If Available)
try {
navigator.geolocation.getCurrentPosition(
(position) => resolveSuccess(position, resolve), // ✅ Corrected to use resolveSuccess
() => fetchFallbackLocation(resolve),
{ maximumAge: 5000 }
);
return;
} catch (altError) {
ConsoleLogEnabled("Alternative Geolocation API failed:", altError);
}
// Step 4: If all else fails, fallback to server
fetchFallbackLocation(resolve);
},
{
timeout: 10000,
maximumAge: 0,
}
);
});
}
// 🎯 Success Handler
function resolveSuccess(position, resolve) {
notifications("We successfully detected your location.", "success", "🌎", "5000");
disableLoadMoreButton(true);
disableFilterButton(true);
Loadingbar(true);
resolve({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
});
}
// 🌍 Fetches fallback location from a callback server
function fetchFallbackLocation(resolve) {
ConsoleLogEnabled("Fetching location from the fallback server...");
fetch("https://ipapi.co/json/") // Example fallback API
.then((response) => response.json())
.then((data) => {
ConsoleLogEnabled("Fallback location received:", data);
notifications("Using approximate location from IP address.", "info", "📍", "5000");
resolve({
latitude: data.latitude || 40.7128, // Default to New York if API fails
longitude: data.longitude || -74.0060,
});
})
.catch((error) => {
ConsoleLogEnabled("Error fetching fallback location:", error);
notifications("Could not determine location. Please report and issue on Greasyfork. Assuming New York.", "error", "⚠️", "15000");
resolve({
latitude: 40.7128,
longitude: -74.0060,
});
});
}
/*********************************************************************************************************************************************************************************************************************************************
Functions for the 7th button.
*********************************************************************************************************************************************************************************************************************************************/
async function auto_join_small_server() {
// Disable the "Load More" button and show the loading bar
Loadingbar(true);
disableFilterButton(true);
disableLoadMoreButton();
// Get the game ID from the URL
const gameId = window.location.pathname.split('/')[2];
// Retry mechanism for 429 errors
let retries = 3; // Number of retries
let success = false;
while (retries > 0 && !success) {
try {
// Fetch server data using GM_xmlhttpRequest
const data = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: `https://games.roblox.com/v1/games/${gameId}/servers/public?sortOrder=1&excludeFullGames=true&limit=100`,
onload: function(response) {
if (response.status === 429) {
reject('429: Too Many Requests');
} else if (response.status >= 200 && response.status < 300) {
resolve(JSON.parse(response.responseText));
} else {
reject(`HTTP error: ${response.status}`);
}
},
onerror: function(error) {
reject(error);
},
});
});
// Find the server with the lowest player count
let minPlayers = Infinity;
let targetServer = null;
for (const server of data.data) {
if (server.playing < minPlayers) {
minPlayers = server.playing;
targetServer = server;
}
}
if (targetServer) {
// Join the server with the lowest player count
showLoadingOverlay();
Roblox.GameLauncher.joinGameInstance(gameId, targetServer.id);
notifications(`Joining a server with ${targetServer.playing} player(s).`, 'success', '🚀');
success = true; // Mark as successful
} else {
notifications('No available servers found.', 'error', '⚠️');
break; // Exit the loop if no servers are found
}
} catch (error) {
if (error === '429: Too Many Requests' && retries > 0) {
ConsoleLogEnabled('Rate limited. Retrying in 10 seconds...');
notifications('Rate limited. Retrying in 10 seconds...', 'warning', '⏳', '10000');
await delay(10000); // Wait 10 seconds before retrying
retries--;
} else {
ConsoleLogEnabled('Error fetching server data:', error);
notifications('Error: Failed to fetch server data. Please try again later.', 'error', '⚠️', '5000');
break; // Exit the loop if it's not a 429 error or no retries left
}
}
}
// Hide the loading bar and enable the filter button
Loadingbar(false);
disableFilterButton(false);
}
/*********************************************************************************************************************************************************************************************************************************************
Functions for the 8th button.
*********************************************************************************************************************************************************************************************************************************************/
function find_user_server_tab() {
// Create the overlay (backdrop)
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9999;
opacity: 0;
transition: opacity 0.3s ease;
`;
document.body.appendChild(overlay);
// Create the popup container
const popup = document.createElement('div');
popup.className = 'player-count-popup';
popup.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgb(30, 32, 34);
padding: 20px;
border-radius: 10px;
z-index: 10000;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.7);
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
width: 300px;
opacity: 0;
transition: opacity 0.3s ease, transform 0.3s ease;
`;
// Add a close button in the top-right corner (bigger size)
const closeButton = document.createElement('button');
closeButton.innerHTML = '×'; // Using '×' for the close icon
closeButton.style.cssText = `
position: absolute;
top: 10px;
right: 10px;
background: transparent;
border: none;
color: #ffffff;
font-size: 24px; /* Increased font size */
cursor: pointer;
width: 36px; /* Increased size */
height: 36px; /* Increased size */
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.3s ease, color 0.3s ease;
`;
closeButton.addEventListener('mouseenter', () => {
closeButton.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
closeButton.style.color = '#ff4444';
});
closeButton.addEventListener('mouseleave', () => {
closeButton.style.backgroundColor = 'transparent';
closeButton.style.color = '#ffffff';
});
// Add a title
const title = document.createElement('h3');
title.textContent = 'Enter Username';
title.style.cssText = `
color: white;
margin: 0;
font-size: 18px;
font-weight: 500;
`;
popup.appendChild(title);
// Add an input box for the username
const usernameInput = document.createElement('input');
usernameInput.type = 'text';
usernameInput.placeholder = 'Username, not display';
usernameInput.style.cssText = `
width: 80%;
padding: 8px;
font-size: 16px;
border: 1px solid #444;
border-radius: 5px;
background-color: #1a1a1a;
color: white;
outline: none;
`;
popup.appendChild(usernameInput);
// Add a confirm button with dark, blackish style
const confirmButton = document.createElement('button');
confirmButton.textContent = 'Confirm';
confirmButton.style.cssText = `
padding: 8px 20px;
font-size: 16px;
background-color: #1a1a1a; /* Dark blackish color */
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s ease, transform 0.2s ease;
`;
confirmButton.addEventListener('mouseenter', () => {
confirmButton.style.backgroundColor = '#333'; /* Slightly lighter on hover */
confirmButton.style.transform = 'scale(1.05)';
});
confirmButton.addEventListener('mouseleave', () => {
confirmButton.style.backgroundColor = '#1a1a1a';
confirmButton.style.transform = 'scale(1)';
});
// Handle confirm button click
confirmButton.addEventListener('click', () => {
const username = usernameInput.value.trim();
if (username) {
// Check if the username is between 3 and 20 characters
if (username.length >= 3 && username.length <= 20) {
// Call the function with the username
FindPlayerGameServer(username);
notifications("Searching for the user's server...", "info", "🧐", "5000");
fadeOutAndRemove_7th(popup, overlay);
} else {
// Show an error notification if the username is not within the required length
notifications('Error: Username must be between 3 and 20 characters', 'error', '⚠️');
}
} else {
notifications('Error: Please enter a username', 'error', '⚠️', '2500');
}
});
// Append the popup to the body
document.body.appendChild(popup);
// Fade in the overlay and popup
setTimeout(() => {
overlay.style.opacity = '1';
popup.style.opacity = '1';
popup.style.transform = 'translate(-50%, -50%) scale(1)';
}, 10);
/*******************************************************
name of function: fadeOutAndRemove_7th
description: Fades out and removes the popup and overlay.
*******************************************************/
function fadeOutAndRemove_7th(popup, overlay) {
popup.style.opacity = '0';
popup.style.transform = 'translate(-50%, -50%) scale(0.9)';
overlay.style.opacity = '0';
setTimeout(() => {
popup.remove();
overlay.remove();
}, 300); // Match the duration of the transition
}
// Close the popup when clicking outside
overlay.addEventListener('click', () => {
fadeOutAndRemove_7th(popup, overlay);
});
// Close the popup when the close button is clicked
closeButton.addEventListener('click', () => {
fadeOutAndRemove_7th(popup, overlay);
});
popup.appendChild(confirmButton);
popup.appendChild(closeButton);
}
async function FindPlayerGameServer(playerName) {
disableLoadMoreButton();
Loadingbar(true);
disableFilterButton(true);
const wait = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds));
const fetchData = async (url, options = {}) => {
if (!options.headers) options.headers = {};
return fetch(url, options)
.then(response => response.json())
.catch(error => ConsoleLogEnabled("Fetch error:", error));
};
const fetchDataGM = (url, options = {}) => {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: options.method || 'GET',
url: url,
headers: options.headers || {},
anonymous: true, // Prevents sending cookies
nocache: true, // Prevents caching
onload: function(response) {
try {
const parsedData = JSON.parse(response.responseText);
resolve(parsedData);
} catch (error) {
ConsoleLogEnabled("JSON parsing error:", error);
reject(error);
}
},
onerror: function(error) {
ConsoleLogEnabled("Request error:", error);
reject(error);
}
});
});
};
ConsoleLogEnabled(`Initiating search for player: ${playerName}`);
const gameId = window.location.href.split("/")[4];
ConsoleLogEnabled(`Game ID identified: ${gameId}`);
let userId;
try {
ConsoleLogEnabled(`Retrieving user ID for player: ${playerName}`);
const userProfile = await fetch(`https://www.roblox.com/users/profile?username=${playerName}`);
if (!userProfile.ok) {
notifications("Error: User does not exist on Roblox!", "error", "⚠️", "2500");
Loadingbar(false);
disableFilterButton(false);
throw `Player "${playerName}" not found`;
}
userId = userProfile.url.match(/\d+/)[0];
ConsoleLogEnabled(`User ID retrieved: ${userId}`);
} catch (error) {
ConsoleLogEnabled("Error:", error);
return `Error: ${error}`;
}
ConsoleLogEnabled(`Fetching avatar thumbnail for user ID: ${userId}`);
const avatarThumbnail = (await fetchData(`https://thumbnails.roblox.com/v1/users/avatar-headshot?userIds=${userId}&format=Png&size=150x150`)).data[0].imageUrl;
ConsoleLogEnabled(`Avatar thumbnail URL: ${avatarThumbnail}`);
let pageCursor = null;
let playerFound = false;
let totalServersChecked = 0;
let startTime = Date.now();
// Show the search progress popup with the player's thumbnail
const progressPopup = showSearchProgressPopup(avatarThumbnail);
while (true) {
let apiUrl = `https://games.roblox.com/v1/games/${gameId}/servers/0?limit=100`;
if (pageCursor) apiUrl += "&cursor=" + pageCursor;
ConsoleLogEnabled(`Accessing servers with URL: ${apiUrl}`);
const serverList = await fetchDataGM(apiUrl, {
credentials: "omit"
}).catch(() => null);
if (serverList && serverList.data) {
ConsoleLogEnabled(`Discovered ${serverList.data.length} servers in this set.`);
for (let index = 0; index < serverList.data.length; index++) {
const server = serverList.data[index];
if (!playerFound) {
totalServersChecked++;
// Calculate time elapsed
const timeElapsed = Math.floor((Date.now() - startTime) / 1000);
// Update the progress popup
updateSearchProgressPopup(
progressPopup,
totalServersChecked,
timeElapsed
);
if (server.playerTokens.length === 0) {
ConsoleLogEnabled(`Server ${index + 1} is empty. Proceeding to next server.`);
continue;
}
ConsoleLogEnabled(`Inspecting server ${index + 1} hosting ${server.playing} players...`);
let thumbnailData;
while (true) {
let requestBody = [];
const generateRequestEntry = (playerToken) => ({
requestId: `0:${playerToken}:AvatarHeadshot:150x150:png:regular`,
type: "AvatarHeadShot",
targetId: 0,
token: playerToken,
format: "png",
size: "150x150"
});
server.playerTokens.forEach(token => {
requestBody.push(generateRequestEntry(token));
});
try {
ConsoleLogEnabled(`Fetching thumbnails for ${server.playerTokens.length} player(s)...`);
thumbnailData = await fetchData("https://thumbnails.roblox.com/v1/batch", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json"
},
body: JSON.stringify(requestBody)
});
ConsoleLogEnabled("Thumbnail Data Response:", thumbnailData);
break;
} catch (error) {
ConsoleLogEnabled("Thumbnail retrieval error:", error);
await wait(1000);
}
}
if (!thumbnailData.data) {
ConsoleLogEnabled("No thumbnail data available. Moving to next server.");
continue;
}
for (let thumbIndex = 0; thumbIndex < thumbnailData.data.length; thumbIndex++) {
const thumbnail = thumbnailData.data[thumbIndex];
if (thumbnail && thumbnail.imageUrl === avatarThumbnail) {
playerFound = true;
ConsoleLogEnabled(`Player located in server ${index + 1}!`);
notifications("Found User's Server! Joining Server...", "success", "🚀", "5000");
showLoadingOverlay();
Roblox.GameLauncher.joinGameInstance(gameId, server.id);
fadeOutAndRemove_7th_progress(progressPopup.popup, progressPopup.overlay);
Loadingbar(false);
disableFilterButton(false);
return {
gameId,
serverId: server.id,
currentPlayers: server.playing,
maximumPlayers: server.maxPlayers,
};
}
}
} else {
break;
}
}
pageCursor = serverList.nextPageCursor;
if (!pageCursor || playerFound) break;
else {
ConsoleLogEnabled("Pausing for 0.5 seconds before next server batch...");
await wait(500);
}
} else {
ConsoleLogEnabled("Server fetch failed. Retrying in 5 seconds...");
notifications("Got rate limited. Waiting 8 seconds...", "info", "❗", "8000")
await wait(8000);
}
}
if (!playerFound) {
// Wait for 2 seconds before calling another function
setTimeout(() => {
fadeOutAndRemove_7th_progress(progressPopup.popup, progressPopup.overlay);
notifications("User not found playing this game!", "error", "⚠️", "5000");
}, 2000); // 2000 milliseconds = 2 seconds
// Existing logic (unchanged)
Loadingbar(false);
disableFilterButton(false);
ConsoleLogEnabled(`Player not found in the searched servers (${totalServersChecked} servers checked)`);
// Update the progress popup with the final count
updateSearchProgressPopup(
progressPopup,
totalServersChecked,
Math.floor((Date.now() - startTime) / 1000),
true
);
return `Player not found in the searched servers (${totalServersChecked} servers checked)`;
}
}
/*******************************************************
name of function: showSearchProgressPopup
description: Creates and displays a popup showing the search progress.
*******************************************************/
function showSearchProgressPopup(avatarThumbnail) {
// Create the overlay (backdrop)
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9999;
opacity: 0;
transition: opacity 0.3s ease;
`;
document.body.appendChild(overlay);
// Create the popup container
const popup = document.createElement('div');
popup.className = 'search-progress-popup';
popup.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgb(30, 32, 34);
padding: 20px;
border-radius: 10px;
z-index: 10000; /* Higher than overlay */
box-shadow: 0 0 15px rgba(0, 0, 0, 0.7);
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
width: 300px;
opacity: 0; /* Start invisible */
transition: opacity 0.3s ease, transform 0.3s ease;
`;
// Add a title
const title = document.createElement('h3');
title.textContent = 'Searching for Player...';
title.style.cssText = `
color: white;
margin: 0;
font-size: 18px;
font-weight: 500;
`;
popup.appendChild(title);
// Add the player's thumbnail
const thumbnail = document.createElement('img');
thumbnail.src = avatarThumbnail;
thumbnail.style.cssText = `
width: 100px;
height: 100px;
border-radius: 50%;
object-fit: cover;
`;
popup.appendChild(thumbnail);
// Add a progress text for servers searched
const serversSearchedText = document.createElement('p');
serversSearchedText.textContent = 'Servers searched: 0';
serversSearchedText.style.cssText = `
color: white;
margin: 0;
font-size: 16px;
`;
popup.appendChild(serversSearchedText);
// Add a text for time elapsed
const timeElapsedText = document.createElement('p');
timeElapsedText.textContent = 'Time elapsed: 0s';
timeElapsedText.style.cssText = `
color: white;
margin: 0;
font-size: 16px;
`;
popup.appendChild(timeElapsedText);
// Append the popup to the body
document.body.appendChild(popup);
// Fade in the overlay and popup
setTimeout(() => {
overlay.style.opacity = '1';
popup.style.opacity = '1';
popup.style.transform = 'translate(-50%, -50%) scale(1)';
}, 10);
return {
popup,
overlay,
serversSearchedText,
timeElapsedText,
};
}
/*******************************************************
name of function: updateSearchProgressPopup
description: Updates the search progress popup with the current count.
*******************************************************/
function updateSearchProgressPopup(
progressPopup,
totalServersChecked,
timeElapsed,
isFinal = false
) {
progressPopup.serversSearchedText.textContent = `Servers searched: ${totalServersChecked}`;
progressPopup.timeElapsedText.textContent = `Time elapsed: ${timeElapsed}s`;
if (isFinal) {
progressPopup.serversSearchedText.textContent += ' (Search completed)';
}
}
/*******************************************************
name of function: fadeOutAndRemove
description: Fades out and removes the popup and overlay.
*******************************************************/
function fadeOutAndRemove_7th_progress(popup, overlay) {
popup.style.opacity = '0';
popup.style.transform = 'translate(-50%, -50%) scale(0.9)';
overlay.style.opacity = '0';
setTimeout(() => {
popup.remove();
overlay.remove();
}, 300); // Match the duration of the transition
}
/*********************************************************************************************************************************************************************************************************************************************
End of: This is all the functions for the 8 buttons
*********************************************************************************************************************************************************************************************************************************************/
/*********************************************************************************************************************************************************************************************************************************************
The Universal Functions
*********************************************************************************************************************************************************************************************************************************************/
/*******************************************************
name of function: disableLoadMoreButton
description: Disables the "Load More" button
*******************************************************/
function disableLoadMoreButton() {
const loadMoreButton = document.querySelector('.rbx-running-games-load-more');
if (loadMoreButton) {
loadMoreButton.disabled = true;
loadMoreButton.style.opacity = '0.5'; // Optional: Make the button look disabled
loadMoreButton.style.cursor = 'not-allowed'; // Optional: Change cursor to indicate disabled state
loadMoreButton.title = 'Disabled by Roblox Locator'; // Set tooltip text
} else {
ConsoleLogEnabled('Load More button not found!');
ConsoleLogEnabled('Maybe moved by another extension!');
}
}
/*******************************************************
name of function: Loadingbar
description: Shows or hides a loading bar (now using pulsing boxes)
*******************************************************/
function Loadingbar(disable) {
const serverListSection = document.querySelector('#rbx-running-games');
const serverCardsContainer = document.querySelector('#rbx-game-server-item-container');
const emptyGameInstancesContainer = document.querySelector('.section-content-off.empty-game-instances-container');
const noServersMessage = emptyGameInstancesContainer?.querySelector('.no-servers-message');
// Check if serverCardsContainer does not exist
if (!serverCardsContainer && emptyGameInstancesContainer && noServersMessage?.textContent.includes('Unable to load servers')) {
notifications('Unable to load servers. Please refresh the page.', 'warning', '⚠️', '5000');
return;
}
if (disable) {
if (serverCardsContainer) {
serverCardsContainer.innerHTML = ''; // Clear contents
serverCardsContainer.removeAttribute('style'); // Remove inline styles if present
}
// Prevent duplicate loading bars
if (document.querySelector('#loading-bar')) return;
// Create and display the loading boxes
const loadingContainer = document.createElement('div');
loadingContainer.id = 'loading-bar';
loadingContainer.style.cssText = `
display: flex;
justify-content: center;
align-items: center;
gap: 5px;
margin-top: 10px;
`;
const fragment = document.createDocumentFragment();
for (let i = 0; i < 3; i++) {
const box = document.createElement('div');
box.style.cssText = `
width: 10px;
height: 10px;
background-color: white;
margin: 0 5px;
border-radius: 2px;
animation: pulse 1.2s ${i * 0.2}s infinite;
`;
fragment.appendChild(box);
}
loadingContainer.appendChild(fragment);
if (serverListSection) {
serverListSection.appendChild(loadingContainer);
}
// Inject CSS only once
if (!document.querySelector('#loading-style')) {
const styleSheet = document.createElement('style');
styleSheet.id = 'loading-style';
styleSheet.textContent = `
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.5); }
}
`;
document.head.appendChild(styleSheet);
}
// Remove the unwanted gradient div
document.querySelector('div[style*="background: linear-gradient(145deg"]')?.remove();
} else {
// Remove the loading bar
document.querySelector('#loading-bar')?.remove();
}
}
/*******************************************************
name of function: fetchPlayerThumbnails
description: Fetches player thumbnails for up to 5 players. Skips the batch if an error occurs.
*******************************************************/
async function fetchPlayerThumbnails(playerTokens) {
// Limit to the first 5 player tokens
const limitedTokens = playerTokens.slice(0, 5);
const body = limitedTokens.map(token => ({
requestId: `0:${token}:AvatarHeadshot:150x150:png:regular`,
type: "AvatarHeadShot",
targetId: 0,
token,
format: "png",
size: "150x150",
}));
try {
const response = await fetch("https://thumbnails.roblox.com/v1/batch", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(body),
});
// Check if the response is successful
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
return data.data || []; // Return the data or an empty array if no data is present
} catch (error) {
ConsoleLogEnabled('Error fetching player thumbnails:', error);
return []; // Return an empty array if an error occurs
}
}
/*******************************************************
name of function: disableFilterButton
description: Disables or enables the filter button based on the input.
*******************************************************/
function disableFilterButton(disable) {
const filterButton = document.querySelector('.RL-filter-button');
const refreshButtons = document.querySelectorAll('.btn-more.rbx-refresh.refresh-link-icon.btn-control-xs.btn-min-width');
const filterOverlayId = 'filter-button-overlay';
const refreshOverlayClass = 'refresh-button-overlay';
if (filterButton) {
const parent = filterButton.parentElement;
if (disable) {
// Disable the filter button with an overlay
filterButton.disabled = true;
filterButton.style.opacity = '0.5';
filterButton.style.cursor = 'not-allowed';
// Create an overlay if it doesn't exist
let overlay = document.getElementById(filterOverlayId);
if (!overlay) {
overlay = document.createElement('div');
overlay.id = filterOverlayId;
overlay.style.position = 'absolute';
overlay.style.top = '-10px';
overlay.style.left = '-10px';
overlay.style.width = 'calc(100% + 20px)';
overlay.style.height = 'calc(100% + 20px)';
overlay.style.backgroundColor = 'transparent';
overlay.style.zIndex = '9999';
overlay.style.pointerEvents = 'all'; // Block clicks
parent.style.position = 'relative';
parent.appendChild(overlay);
}
} else {
// Enable the filter button
filterButton.disabled = false;
filterButton.style.opacity = '1';
filterButton.style.cursor = 'pointer';
// Remove the overlay if it exists
const overlay = document.getElementById(filterOverlayId);
if (overlay) {
overlay.remove();
}
}
} else {
console.log('Filter button not found!');
}
if (refreshButtons.length > 0) {
refreshButtons.forEach((refreshButton) => {
const refreshParent = refreshButton.parentElement;
if (disable) {
// Create an overlay over the refresh button
let refreshOverlay = refreshParent.querySelector(`.${refreshOverlayClass}`);
if (!refreshOverlay) {
refreshOverlay = document.createElement('div');
refreshOverlay.className = refreshOverlayClass;
refreshOverlay.style.position = 'absolute';
refreshOverlay.style.top = '-10px';
refreshOverlay.style.left = '-10px';
refreshOverlay.style.width = 'calc(100% + 20px)';
refreshOverlay.style.height = 'calc(100% + 20px)';
refreshOverlay.style.backgroundColor = 'transparent';
refreshOverlay.style.zIndex = '9999';
refreshOverlay.style.pointerEvents = 'all'; // Block clicks
refreshParent.style.position = 'relative';
refreshParent.appendChild(refreshOverlay);
}
} else {
// Remove the overlay if it exists
const refreshOverlay = refreshParent.querySelector(`.${refreshOverlayClass}`);
if (refreshOverlay) {
refreshOverlay.remove();
}
}
});
} else {
ConsoleLogEnabled('Refresh button not found!');
}
}
async function rbx_card(serverId, playerTokens, maxPlayers, playing, gameId) {
// Fetch player thumbnails (up to 5)
const thumbnails = await fetchPlayerThumbnails(playerTokens);
// Create the server card container
const cardItem = document.createElement('li');
cardItem.className = 'rbx-game-server-item col-md-3 col-sm-4 col-xs-6';
// Create the player thumbnails container
const playerThumbnailsContainer = document.createElement('div');
playerThumbnailsContainer.className = 'player-thumbnails-container';
// Add player thumbnails to the container (up to 5)
thumbnails.forEach(thumbnail => {
const playerAvatar = document.createElement('span');
playerAvatar.className = 'avatar avatar-headshot-md player-avatar';
const thumbnailImage = document.createElement('span');
thumbnailImage.className = 'thumbnail-2d-container avatar-card-image';
const img = document.createElement('img');
img.src = thumbnail.imageUrl;
img.alt = '';
img.title = '';
thumbnailImage.appendChild(img);
playerAvatar.appendChild(thumbnailImage);
playerThumbnailsContainer.appendChild(playerAvatar);
});
// Add the 6th placeholder for remaining players
if (playing > 5) {
const remainingPlayers = playing - 5;
const placeholder = document.createElement('span');
placeholder.className = 'avatar avatar-headshot-md player-avatar hidden-players-placeholder';
placeholder.textContent = `+${remainingPlayers}`;
placeholder.style.cssText = `
background-color: #6a6f81; /* Grayish-blue background */
color: white;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%; /* Fully round */
font-size: 16px; /* Larger font size */
width: 60px; /* Larger width */
height: 60px; /* Larger height */
`;
playerThumbnailsContainer.appendChild(placeholder);
}
// Create the server details container
const serverDetails = document.createElement('div');
serverDetails.className = 'rbx-game-server-details game-server-details';
// Add server status (e.g., "15 of 15 people max")
const serverStatus = document.createElement('div');
serverStatus.className = 'text-info rbx-game-status rbx-game-server-status text-overflow';
serverStatus.textContent = `${playing} of ${maxPlayers} people max`;
serverDetails.appendChild(serverStatus);
// Add the player count gauge
const gaugeContainer = document.createElement('div');
gaugeContainer.className = 'server-player-count-gauge border';
const gaugeInner = document.createElement('div');
gaugeInner.className = 'gauge-inner-bar border';
gaugeInner.style.width = `${(playing / maxPlayers) * 100}%`;
gaugeContainer.appendChild(gaugeInner);
serverDetails.appendChild(gaugeContainer);
// Create a container for the buttons
const buttonContainer = document.createElement('div');
buttonContainer.className = 'button-container';
buttonContainer.style.cssText = `
display: flex;
gap: 8px; /* Space between buttons */
`;
// Add the "Join" button
const joinButton = document.createElement('button');
joinButton.type = 'button';
joinButton.className = 'btn-full-width btn-control-xs rbx-game-server-join game-server-join-btn btn-primary-md btn-min-width';
joinButton.textContent = 'Join';
// Add click event to join the server
joinButton.addEventListener('click', () => {
showLoadingOverlay();
Roblox.GameLauncher.joinGameInstance(gameId, serverId);
});
buttonContainer.appendChild(joinButton);
// Add the "Invite" button
const inviteButton = document.createElement('button');
inviteButton.type = 'button';
inviteButton.className = 'btn-full-width btn-control-xs rbx-game-server-invite game-server-invite-btn btn-secondary-md btn-min-width';
inviteButton.textContent = 'Invite';
// Add click event to log the invite link
inviteButton.addEventListener('click', () => {
const inviteLink = `https://oqarshi.github.io/Invite/?placeid=${gameId}&serverid=${serverId}`;
//ConsoleLogEnabled('Copied invite link:', inviteLink);
navigator.clipboard.writeText(inviteLink).then(() => {
notifications('Success! Invite link copied to clipboard!', 'success', '🎉', '2000');
//ConsoleLogEnabled('Invite link copied to clipboard');
}).catch(err => {
ConsoleLogEnabled('Failed to copy invite link:', err);
notifications('Failed! Invite link copied to clipboard!', 'error', '⚠️', '2000');
});
});
buttonContainer.appendChild(inviteButton);
// Add the button container to the server details
serverDetails.appendChild(buttonContainer);
// Assemble the card
const cardContainer = document.createElement('div');
cardContainer.className = 'card-item';
cardContainer.appendChild(playerThumbnailsContainer);
cardContainer.appendChild(serverDetails);
cardItem.appendChild(cardContainer);
// Add the card to the server list
const serverList = document.querySelector('#rbx-game-server-item-container');
serverList.appendChild(cardItem);
}
/*********************************************************************************************************************************************************************************************************************************************
End of function for the notification function
*********************************************************************************************************************************************************************************************************************************************/
/*********************************************************************************************************************************************************************************************************************************************
Launching Function
*********************************************************************************************************************************************************************************************************************************************/
function showLoadingOverlay() {
// Create the content div (no overlay background)
const content = document.createElement('div');
content.style.position = 'fixed';
content.style.top = 'calc(50% - 15px)'; // Move 15px higher
content.style.left = '50%';
content.style.transform = 'translate(-50%, -50%)'; // Center the box horizontally
content.style.width = '400px';
content.style.height = '250px';
content.style.backgroundColor = '#1e1e1e'; // Dark background
content.style.display = 'flex';
content.style.flexDirection = 'column';
content.style.justifyContent = 'center';
content.style.alignItems = 'center';
content.style.boxShadow = '0 4px 20px rgba(0, 0, 0, 0.5)'; // Subtle shadow
content.style.borderRadius = '12px'; // Slightly rounded corners
content.style.zIndex = '1000000'; // z-index set to 1 million
content.style.opacity = '0'; // Start with 0 opacity for fade-in
content.style.transition = 'opacity 0.5s ease'; // Fade-in transition
content.style.fontFamily = 'Arial, sans-serif'; // Clean font
// Create the loading text
const loadingText = document.createElement('p');
loadingText.textContent = 'Joining Roblox Game...';
loadingText.style.fontSize = '20px';
loadingText.style.color = '#fff'; // White text for contrast
loadingText.style.marginTop = '20px'; // Spacing between image and text
loadingText.style.fontWeight = 'bold'; // Bold text
// Create the base64 image
const base64Image = document.createElement('img');
base64Image.src = window.Base64Images.logo;
base64Image.style.width = '80px'; // Slightly larger image
base64Image.style.height = '80px'; // Slightly larger image
base64Image.style.borderRadius = '8px'; // Rounded corners for the image
// Create the loading boxes container
const loadingBoxes = document.createElement('div');
loadingBoxes.style.display = 'flex';
loadingBoxes.style.alignItems = 'center';
loadingBoxes.style.justifyContent = 'center';
loadingBoxes.style.marginTop = '15px'; // Spacing between text and boxes
// Create the three loading boxes
for (let i = 0; i < 3; i++) {
const box = document.createElement('div');
box.style.width = '10px';
box.style.height = '10px';
box.style.backgroundColor = '#fff'; // White boxes
box.style.margin = '0 5px'; // Spacing between boxes
box.style.borderRadius = '2px'; // Slightly rounded corners
box.style.animation = `pulse 1.2s ${i * 0.2}s infinite`; // Animation with delay
loadingBoxes.appendChild(box);
}
// Define the pulse animation using CSS
const style = document.createElement('style');
style.textContent = `
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.5); }
}
`;
document.head.appendChild(style); // Add the animation to the document
// Append the image, text, and loading boxes to the content div
content.appendChild(base64Image);
content.appendChild(loadingText);
content.appendChild(loadingBoxes);
// Append the content div to the body
document.body.appendChild(content);
// Trigger fade-in animation
setTimeout(() => {
content.style.opacity = '1';
}, 10); // Small delay to trigger the transition
// Remove the content after 8 seconds with fade-out animation
setTimeout(() => {
content.style.opacity = '0'; // Fade out
setTimeout(() => {
document.body.removeChild(content); // Remove after fade-out completes
}, 500); // Wait for the fade-out transition to finish
}, 8000); // 8000 milliseconds = 8 seconds
}
/*********************************************************************************************************************************************************************************************************************************************
End of function for the launching function
*********************************************************************************************************************************************************************************************************************************************/
const serverRegionsByIp = {
"128.116.0.0": { city: "Hong Kong", country: { name: "Hong Kong", code: "HK" }, latitude: 22.3193, longitude: 114.1694 },
"128.116.1.0": { city: "Los Angeles", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 34.0522, longitude: -118.2437 },
"128.116.2.0": { city: "Warsaw", country: { name: "Poland", code: "PL" }, region: { name: "Mazowieckie", code: "14" }, latitude: 52.2297, longitude: 21.0122 },
"128.116.3.0": { city: "Warsaw", country: { name: "Poland", code: "PL" }, region: { name: "Mazowieckie", code: "14" }, latitude: 52.2297, longitude: 21.0122 },
"128.116.4.0": { city: "Paris", country: { name: "France", code: "FR" }, region: { name: "Île-de-France", code: "IDF" }, latitude: 48.8566, longitude: 2.3522 },
"128.116.5.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
"128.116.6.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
"128.116.7.0": { city: "Mumbai", country: { name: "India", code: "IN" }, region: { name: "Mahārāshtra", code: "MH" }, latitude: 19.0760, longitude: 72.8777 },
"128.116.8.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
"128.116.9.0": { city: "Mumbai", country: { name: "India", code: "IN" }, region: { name: "Mahārāshtra", code: "MH" }, latitude: 19.0760, longitude: 72.8777 },
"128.116.10.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
"128.116.11.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
"128.116.12.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
"128.116.13.0": { city: "Amsterdam", country: { name: "Netherlands", code: "NL" }, region: { name: "Noord-Holland", code: "NH" }, latitude: 52.3676, longitude: 4.9041 },
"128.116.14.0": { city: "Hong Kong", country: { name: "Hong Kong", code: "HK" }, latitude: 22.3193, longitude: 114.1694 },
"128.116.15.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
"128.116.16.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
"128.116.17.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
"128.116.18.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 },
"128.116.19.0": { city: "Paris", country: { name: "France", code: "FR" }, region: { name: "Île-de-France", code: "IDF" }, latitude: 48.8566, longitude: 2.3522 },
"128.116.20.0": { city: "Paris", country: { name: "France", code: "FR" }, region: { name: "Île-de-France", code: "IDF" }, latitude: 48.8566, longitude: 2.3522 },
"128.116.21.0": { city: "Amsterdam", country: { name: "Netherlands", code: "NL" }, region: { name: "Noord-Holland", code: "NH" }, latitude: 52.3676, longitude: 4.9041 },
"128.116.22.0": { city: "Atlanta", country: { name: "United States", code: "US" }, region: { name: "Georgia", code: "GA" }, latitude: 33.7490, longitude: -84.3880 },
"128.116.23.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
"128.116.24.0": { city: "Atlanta", country: { name: "United States", code: "US" }, region: { name: "Georgia", code: "GA" }, latitude: 33.7490, longitude: -84.3880 },
"128.116.25.0": { city: "Atlanta", country: { name: "United States", code: "US" }, region: { name: "Georgia", code: "GA" }, latitude: 33.7490, longitude: -84.3880 },
"128.116.26.0": { city: "Paris", country: { name: "France", code: "FR" }, region: { name: "Île-de-France", code: "IDF" }, latitude: 48.8566, longitude: 2.3522 },
"128.116.27.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
"128.116.28.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
"128.116.29.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
"128.116.30.0": { city: "Hong Kong", country: { name: "Hong Kong", code: "HK" }, latitude: 22.3193, longitude: 114.1694 },
"128.116.31.0": { city: "Warsaw", country: { name: "Poland", code: "PL" }, region: { name: "Mazowieckie", code: "14" }, latitude: 52.2297, longitude: 21.0122 },
"128.116.32.0": { city: "New York City", country: { name: "United States", code: "US" }, region: { name: "New York", code: "NY" }, latitude: 40.7128, longitude: -74.0060 },
"128.116.33.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 },
"128.116.34.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
"128.116.35.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 },
"128.116.36.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 },
"128.116.37.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 },
"128.116.38.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 },
"128.116.39.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
"128.116.40.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
"128.116.41.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
"128.116.42.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
"128.116.43.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
"128.116.44.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
"128.116.45.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 },
"128.116.46.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
"128.116.47.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
"128.116.48.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
"128.116.49.0": { city: "Los Angeles", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 34.0522, longitude: -118.2437 },
"128.116.50.0": { city: "Singapore", country: { name: "Singapore", code: "SG" }, latitude: 1.3521, longitude: 103.8198 },
"128.116.51.0": { city: "Sydney", country: { name: "Australia", code: "AU" }, region: { name: "New South Wales", code: "NSW" }, latitude: -33.8688, longitude: 151.2093 },
"128.116.52.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
"128.116.53.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
"128.116.54.0": { city: "Amsterdam", country: { name: "Netherlands", code: "NL" }, region: { name: "Noord-Holland", code: "NH" }, latitude: 52.3676, longitude: 4.9041 },
"128.116.55.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
"128.116.56.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
"128.116.57.0": { city: "San Jose", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3382, longitude: -121.8863 },
"128.116.58.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
"128.116.59.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
"128.116.60.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
"128.116.61.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
"128.116.62.0": { city: "Seattle", country: { name: "United States", code: "US" }, region: { name: "Washington", code: "WA" }, latitude: 47.6062, longitude: -122.3321 },
"128.116.63.0": { city: "Los Angeles", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 34.0522, longitude: -118.2437 },
"128.116.64.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
"128.116.65.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
"128.116.66.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
"128.116.67.0": { city: "San Jose", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3382, longitude: -121.8863 },
"128.116.68.0": { city: "Santa Clara", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3541, longitude: -121.9552 },
"128.116.69.0": { city: "Santa Clara", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3541, longitude: -121.9552 },
"128.116.70.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
"128.116.71.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
"128.116.72.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 },
"128.116.73.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 },
"128.116.74.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
"128.116.75.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
"128.116.76.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
"128.116.77.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
"128.116.78.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
"128.116.79.0": { city: "Singapore", country: { name: "Singapore", code: "SG" }, latitude: 1.3521, longitude: 103.8198 },
"128.116.80.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
"128.116.81.0": { city: "Santa Clara", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3541, longitude: -121.9552 },
"128.116.82.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
"128.116.83.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
"128.116.84.0": { city: "Elk Grove Village", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 42.0039, longitude: -87.9706 },
"128.116.85.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 },
"128.116.86.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
"128.116.87.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
"128.116.88.0": { city: "Elk Grove Village", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 42.0039, longitude: -87.9706 },
"128.116.89.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 },
"128.116.90.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
"128.116.91.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
"128.116.92.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
"128.116.93.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
"128.116.94.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
"128.116.95.0": { city: "Dallas", country: { name: "United States", code: "US" }, region: { name: "Texas", code: "TX" }, latitude: 32.7767, longitude: -96.7970 },
"128.116.96.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
"128.116.97.0": { city: "Singapore", country: { name: "Singapore", code: "SG" }, latitude: 1.3521, longitude: 103.8198 },
"128.116.98.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
"128.116.99.0": { city: "Atlanta", country: { name: "United States", code: "US" }, region: { name: "Georgia", code: "GA" }, latitude: 33.7490, longitude: -84.3880 },
"128.116.100.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
"128.116.101.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
"128.116.102.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
"128.116.103.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
"128.116.104.0": { city: "Mumbai", country: { name: "India", code: "IN" }, region: { name: "Mahārāshtra", code: "MH" }, latitude: 19.0760, longitude: 72.8777 },
"128.116.105.0": { city: "Santa Clara", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3541, longitude: -121.9552 },
"128.116.106.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
"128.116.107.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
"128.116.108.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
"128.116.109.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
"128.116.110.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
"128.116.111.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
"128.116.112.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
"128.116.113.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
"128.116.114.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
"128.116.115.0": { city: "Seattle", country: { name: "United States", code: "US" }, region: { name: "Washington", code: "WA" }, latitude: 47.6062, longitude: -122.3321 },
"128.116.116.0": { city: "Los Angeles", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 34.0522, longitude: -118.2437 },
"128.116.117.0": { city: "San Jose", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3382, longitude: -121.8863 },
"128.116.118.0": { city: "Hong Kong", country: { name: "Hong Kong", code: "HK" }, latitude: 22.3193, longitude: 114.1694 },
"128.116.119.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 },
"128.116.120.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
"128.116.121.0": { city: "Amsterdam", country: { name: "Netherlands", code: "NL" }, region: { name: "Noord-Holland", code: "NH" }, latitude: 52.3676, longitude: 4.9041 },
"128.116.122.0": { city: "Paris", country: { name: "France", code: "FR" }, region: { name: "Île-de-France", code: "IDF" }, latitude: 48.8566, longitude: 2.3522 },
"128.116.123.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
"128.116.124.0": { city: "Warsaw", country: { name: "Poland", code: "PL" }, region: { name: "Mazowieckie", code: "14" }, latitude: 52.2297, longitude: 21.0122 },
"128.116.125.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
"128.116.126.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
"128.116.127.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 },
};
// find_game_id function does nothing lmao but its ere i guees
function find_game_id() {
ConsoleLogEnabled('Trying to find game id');
const gameIdMatch = window.location.pathname.match(/\/games\/(\d+)\//);
if (!gameIdMatch) {
ConsoleLogEnabled("Game ID not found in URL!");
return;
}
const gameId = gameIdMatch[1];
}
/*******************************************************
name of function: Initiate the observer
description: Start observing the document for changes
*******************************************************/
// end of the check for the url
}
/*******************************************************
End of code for the random hop button and the filter button on roblox.com/games/*
*******************************************************/
})();