- // ==UserScript==
- // @name RoLocate
- // @namespace https://oqarshi.github.io/
- // @version 33.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.gf.qytechs.cn/scripts/526611/1535754/Rolocate%20Base64%20Image%20Library.js
- // ==/UserScript==
-
-
-
- (function() {
- 'use strict';
-
-
-
- function initializeLocalStorage() {
- // Define default settings
- const defaultSettings = {
- enableLogs: false, // disabled by default
- removeads: 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 33.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://gf.qytechs.cn/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>
- <li>Remove All Roblox Ads: <a>Blocks most ads on the roblox site. Still experimental.</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>
- <label class="toggle-slider">
- <input type="checkbox" id="removeads">
- <span class="slider"></span>
- Remove All Roblox Ads
- </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 = "V33.3";
- const PREV_VERSION = "V32.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>Fixed script to new html changes by Roblox website, plus a new feature: "Remove All Roblox Ads" Disabled by default. Its still experimental and may not block all ads. Check out the <a href="https://oqarshi.github.io/Invite/rolocate/index.html" target="_blank">FAQ page</a>! This won't appear again until the next update.</p>
- <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 removeAds() {
- if (localStorage.getItem("ROLOCATE_removeads") !== "true") {
- return;
- }
-
- const iframeSelector = `.ads-container iframe,.abp iframe,.abp-spacer iframe,.abp-container iframe,.top-abp-container iframe,
- #AdvertisingLeaderboard iframe,#AdvertisementRight iframe,#MessagesAdSkyscraper iframe,.Ads_WideSkyscraper iframe,
- .profile-ads-container iframe, #ad iframe, iframe[src*="roblox.com/user-sponsorship/"]`;
-
- const iframes = document.getElementsByTagName("iframe");
- const scripts = document.getElementsByTagName("script");
- const doneMap = new WeakMap();
-
- function removeElements() {
- // Remove Iframes
- for (let i = iframes.length; i--;) {
- const iframe = iframes[i];
- if (!doneMap.get(iframe) && iframe.matches(iframeSelector)) {
- iframe.remove();
- doneMap.set(iframe, true);
- }
- }
-
- // Remove Scripts
- for (let i = scripts.length; i--;) {
- const script = scripts[i];
- if (doneMap.get(script)) {
- continue;
- }
- doneMap.set(script, true);
-
- if (script.src && (
- script.src.includes("imasdk.googleapis.com") ||
- script.src.includes("googletagmanager.com") ||
- script.src.includes("radar.cedexis.com") ||
- script.src.includes("ns1p.net")
- )) {
- script.remove();
- } else {
- const cont = script.textContent;
- if (!cont.includes("ContentJS") && (
- cont.includes("scorecardresearch.com") ||
- cont.includes("cedexis.com") ||
- cont.includes("pingdom.net") ||
- cont.includes("ns1p.net") ||
- cont.includes("Roblox.Hashcash") ||
- cont.includes("Roblox.VideoPreRollDFP") ||
- cont.includes("Roblox.AdsHelper=") ||
- cont.includes("googletag.enableServices()") ||
- cont.includes("gtag('config'")
- )) {
- script.remove();
- } else if (cont.includes("Roblox.EventStream.Init")) {
- script.textContent = cont.replace(/"[^"]*"/g, "\"\"");
- }
- }
- }
-
- // Hide Sponsored Game Cards
- document.querySelectorAll(".game-card-native-ad").forEach(ad => {
- const gameCard = ad.closest(".game-card-container");
- if (gameCard) {
- gameCard.style.display = "none";
- }
- });
- }
-
- // Observe DOM for dynamically added elements
- new MutationObserver(removeElements).observe(document.body, {
- childList: true,
- subtree: true
- });
-
- removeElements(); // Initial run
- }
-
-
-
- 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();
- removeAds();
- });
-
-
-
- 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-public-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-public-running-games-footer');
-
- if (!loadMoreButtonContainer) {
- ConsoleLogEnabled("Error: 'Load More' button container not found! Ensure the element exists in the DOM.");
- 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) {
- ConsoleLogEnabled("Warning: An existing message was found and will be replaced.");
- existingMessage.remove(); // Remove the existing message if it exists
- }
-
- loadMoreButtonContainer.appendChild(messageElement);
-
- ConsoleLogEnabled("Message displayed successfully:", message);
-
- 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;
- }
-
-
- // Check if the response indicates that the user needs to purchase the game
- if (json.status === 12 && json.message === 'Cannot join this non-root place due to join restrictions') { // yea error message!
- reject('subplace_join_restriction'); // 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-public-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 = "Error: Cannot access server regions because you have not purchased the game.";
- notifications('Cannot access server regions because you have not purchased the game.', 'error', '⚠️', '15000');
- Loadingbar(false); // disable loading bar
- return;
- } else if (error === 'subplace_join_restriction') {
- messageElement.textContent = "This game requires users to teleport to a subplace. As a result, server regions cannot be retrieved.";
- notifications('Error: This game requires users to teleport to a subplace. As a result, server regions cannot be retrieved.', 'error', '⚠️', '15000');
- 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-public-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-public-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/*
- *******************************************************/
-
-
-
-
- })();