- // ==UserScript==
- // @name RoLocate
- // @namespace https://oqarshi.github.io/
- // @version 35.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
- // @grant GM_getValue
- // @grant GM_listValues
- // @grant GM_setValue
- // @grant GM_deleteValue
- // @require https://update.greatest.deepsurf.us/scripts/526611/1574250/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
- toggleserverhopbutton: true, // enable by default
- AutoRunServerRegions: false, // disabled by default
- ShowOldGreeting: false, // disabled by default
- togglerecentserverbutton: true, // enable by default
- quicknav: false, // disabled by default
- prioritylocation: "automatic", // automatic 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);
- }
- });
- }
-
- //// testing for locations not in production
- //(async () => {
- // console.log("[GM Storage Dump] --- Start ---");
- // const keys = await GM_listValues();
- // for (const key of keys) {
- // console.log(`[GM] ${key}:`, await GM_getValue(key));
- // }
- // console.log("[GM Storage Dump] --- End ---");
- //})();//
- //// testing for locations
- //(async () => {
- // const keys = await GM_listValues();
- // for (const key of keys) {
- // GM_deleteValue(key);
- // console.log(`Deleted ${key}`);
- // }
- //})();
-
-
- function initializeCoordinatesStorage() {
- // Check if coordinates are already stored
- try {
- const storedCoords = GM_getValue("ROLOCATE_coordinates");
- if (!storedCoords) {
- // Set default empty coordinates
- GM_setValue("ROLOCATE_coordinates", JSON.stringify({
- lat: "",
- lng: ""
- }));
- } else {
- // Validate existing coordinates
- const parsedCoords = JSON.parse(storedCoords);
- if ((!parsedCoords.lat || !parsedCoords.lng) && localStorage.getItem("ROLOCATE_prioritylocation") === "manual") {
- // If manual mode but no coordinates, revert to automatic
- localStorage.setItem("ROLOCATE_prioritylocation", "automatic");
- }
- }
- } catch (e) {
- console.error("Error initializing coordinates storage:", e);
- // Set default empty coordinates if parsing fails
- GM_setValue("ROLOCATE_coordinates", JSON.stringify({
- lat: "",
- lng: ""
- }));
- }
- }
-
-
- function getSettingsContent(section) {
- if (section === "home") {
- return `
- <div class="home-section">
- <img class="rolocate-logo" src="${window.Base64Images.logo}" alt="ROLOCATE Logo">
- <div class="version">Rolocate: Version 35.3</div>
- <div class="section-separator"></div>
- <p>Rolocate Settings Menu.</p>
- </div>
- `;
- }
-
- if (section === "appearance") {
- return `
- <div class="appearance-section">
- <label class="toggle-slider section-hover">
- <input type="checkbox" id="ShowOldGreeting">
- <span class="slider"></span>
- Show Old Greeting
- </label>
- <div class="hint-text">
- <p>Restores the classic Roblox greeting style on your home page</p>
- </div>
- </div>
- `;
- }
-
- if (section === "advanced") {
- return `
- <div class="advanced-section">
- <span class="warning_advanced">For Experienced Users Only🧠🙃</span>
- <div class="section-separator"></div>
-
- <label class="toggle-slider section-hover">
- <input type="checkbox" id="enableLogs">
- <span class="slider"></span>
- Enable Console Logs
- </label>
-
- <label class="toggle-slider section-hover">
- <input type="checkbox" id="togglefilterserversbutton">
- <span class="slider"></span>
- Enable Server Filters
- </label>
-
- <label class="toggle-slider section-hover">
- <input type="checkbox" id="toggleserverhopbutton">
- <span class="slider"></span>
- Enable Server Hop Button
- </label>
-
- <div class="location-settings section-hover">
- <div class="setting-header">
- <span>Set Default Location Mode</span>
- <span class="help-icon" title="Choose how your location will be determined">?</span>
- </div>
-
- <select id="prioritylocation-select">
- <option value="manual" style="color: rgb(255, 40, 40);">Manual</option>
- <option value="automatic" style="color: rgb(255, 40, 40);">Automatic</option>
- </select>
-
- <div id="location-hint">
- <strong>Manual:</strong> Set your location manually below
- <strong>Automatic:</strong> Auto detect your device's location
- </div>
-
- <div id="manual-coordinates" style="margin-top: 15px; display: none;">
- <div class="coordinates-inputs" style="display: flex; gap: 10px; margin-bottom: 12px;">
- <div style="flex: 1;">
- <label for="latitude" style="display: block; margin-bottom: 8px; font-size: 14px;">Latitude</label>
- <input type="text" id="latitude" placeholder="e.g. 40.7128"
- style="width: 100%; padding: 10px 12px; border-radius: 8px; border: 1px solid rgba(255, 255, 255, 0.05);; background: rgba(255,255,255,0.05); color: #e0e0e0;">
- </div>
- <div style="flex: 1;">
- <label for="longitude" style="display: block; margin-bottom: 8px; font-size: 14px;">Longitude</label>
- <input type="text" id="longitude" placeholder="e.g. -74.0060"
- style="width: 100%; padding: 10px 12px; border-radius: 8px; border: 1px solid rgba(255, 255, 255, 0.05);; background: rgba(255,255,255,0.05); color: #e0e0e0;">
- </div>
- </div>
- <button id="save-coordinates" class="edit-nav-button" style="width: 100%; margin-top: 8px;">
- Save Coordinates
- </button>
- <div class="hint-text" style="margin-top: 12px; font-size: 13px; color: #a0a0a0;">
- Enter your location's decimal coordinates, or if you're not comfortable sharing them, use the nearest Roblox server coordinates (e.g., Los Angeles: 34.0549, -118.2426).
- </div>
- </div>
- </div>
- </div>
- `;
- }
-
- if (section === "about") {
- return `
- <div class="about-section">
- <div class="section-separator"></div>
- <h3 class="red-accent">Credits:</h3>
- <p>This project was created by:</p>
- <ul>
- <li><strong>Developer:</strong> <a href="https://www.roblox.com/users/545334824/profile" target="_blank" style="text-decoration: underline; color: #007bff;">Oqarshi</a></li>
- <li><strong>Rolocate Source Code:</strong> <a href="https://greatest.deepsurf.us/en/scripts/523727-rolocate/code" target="_blank" style="text-decoration: underline; color: #007bff;">GreasyFork</a></li>
- <li><strong>Invite & FAQ Source Code:</strong> <a href="https://github.com/Oqarshi/Invite" target="_blank" style="text-decoration: underline; color: #007bff;">GitHub</a></li>
- <li><strong>FAQ Website:</strong> <a href="https://oqarshi.github.io/Invite/rolocate/index.html" target="_blank" style="text-decoration: underline; color: #007bff;">RoLocate FAQ</a></li>
- <li><strong>Suggest or Report Issues:</strong> <a href="https://greatest.deepsurf.us/en/scripts/523727-rolocate/feedback" target="_blank" style="text-decoration: underline; color: #007bff;">Submit Feedback</a></li>
- <li><strong>Inspiration:</strong> <a href="https://chromewebstore.google.com/detail/btroblox-making-roblox-be/hbkpclpemjeibhioopcebchdmohaieln" target="_blank" style="text-decoration: underline; color: #007bff;">Btroblox Team</a></li>
- </ul>
- </div>
- `;
- }
-
- if (section === "help") {
- return `
- <div class="help-section">
- <div class="section-separator"></div>
- <h3 class="red-accent">General Tab</h3>
- <ul>
- <li><strong>Auto Run Server Regions:</strong> <span>Replaces Roblox's 8 default servers with at least 8 servers, providing detailed info such as location and ping.</span></li>
- <li><strong>Remove All Roblox Ads:</strong> <span>Blocks most ads on the Roblox site. Still experimental.</span></li>
- <li><strong>Recent Servers:</strong> <span>Shows the most recent servers you have joined in the past 3 days.</span></li>
- <li><strong>Quick Navigation:</strong> <span>Ability to add quick navigations to the leftside panel of the Roblox page.</span></li>
- </ul>
-
- <div class="section-separator"></div>
-
- <h3 class="red-accent">Appearance Tab</h3>
- <ul>
- <li><strong>Show Old Greeting:</strong> <span>Shows the old greeting Roblox had on their home page.</span></li>
- </ul>
-
- <div class="section-separator"></div>
-
- <h3 class="red-accent">Advanced Tab</h3>
- <ul>
- <li><strong>Enable Console Logs:</strong> <span>Enables console.log messages from the script.</span></li>
- <li><strong>Enable Server Filters:</strong> <span>Enables server filter features on the game page.</span></li>
- <li><strong>Enable Server Hop Button:</strong> <span>Enables server hop feature on the game page.</span></li>
- <li><strong>Set default location:</strong> <span>Enables the user to set a default location for Roblox server regions. Turn this on if the script cannot automatically detect your location.</span></li>
- </ul>
- </div>
- `;
- }
-
- // General tab (default)
- return `
- <div class="general-section">
- <label class="toggle-slider section-hover">
- <input type="checkbox" id="AutoRunServerRegions">
- <span class="slider"></span>
- Auto Run Server Regions
- </label>
-
- <label class="toggle-slider section-hover">
- <input type="checkbox" id="removeads">
- <span class="slider"></span>
- Remove All Roblox Ads
- </label>
-
- <label class="toggle-slider section-hover">
- <input type="checkbox" id="togglerecentserverbutton">
- <span class="slider"></span>
- Recent Servers
- </label>
-
- <div class="quicknav-container section-hover">
- <label class="toggle-slider slider-element">
- <input type="checkbox" id="quicknav">
- <span class="slider"></span>
- Quick Navigation
- </label>
- <button id="edit-quicknav-btn" class="edit-nav-button">
- Edit Quick Nav
- </button>
- </div>
- </div>
- `;
- }
-
-
- function openSettingsMenu() {
- if (document.getElementById("userscript-settings-menu")) return;
-
- // Initialize localStorage with default values if they don't exist
- initializeLocalStorage();
- initializeCoordinatesStorage();
-
- // 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>RoLocate</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" class="animated-content">${getSettingsContent("home")}</div>
- </div>
- </div>
- `;
-
- document.body.appendChild(overlay);
-
- // Inject CSS styles
- const style = document.createElement("style");
- style.textContent = `
- @keyframes fadeIn {
- from { opacity: 0; transform: scale(0.96); }
- to { opacity: 1; transform: scale(1); }
- }
-
- @keyframes fadeOut {
- from { opacity: 1; transform: scale(1); }
- to { opacity: 0; transform: scale(0.96); }
- }
-
- @keyframes sectionFade {
- from { opacity: 0; transform: translateY(12px); }
- to { opacity: 1; transform: translateY(0); }
- }
-
- @keyframes slideIn {
- from { transform: translateX(-20px); opacity: 0; }
- to { transform: translateX(0); opacity: 1; }
- }
-
- #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.7s cubic-bezier(0.19, 1, 0.22, 1);
- }
-
- .settings-container {
- display: flex;
- position: relative;
- width: 580px; /* Reduced from 680px */
- height: 420px; /* Reduced from 480px */
- background: linear-gradient(145deg, #1a1a1a, #232323);
- border-radius: 12px; /* Slightly smaller radius */
- overflow: hidden;
- box-shadow: 0 25px 50px -12px rgba(0,0,0,0.7);
- font-family: 'Inter', 'Segoe UI', Arial, sans-serif;
- border: 1px solid rgba(255, 255, 255, 0.05);
- }
-
- #close-settings {
- position: absolute;
- top: 12px; /* Reduced from 16px */
- right: 12px; /* Reduced from 16px */
- background: transparent;
- border: none;
- color: #c0c0c0;
- font-size: 20px; /* Reduced from 22px */
- cursor: pointer;
- z-index: 10001;
- transition: all 0.5s ease;
- width: 30px; /* Reduced from 34px */
- height: 30px; /* Reduced from 34px */
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- #close-settings:hover {
- color: #ff3b47;
- background: rgba(255, 59, 71, 0.1);
- transform: rotate(90deg);
- }
-
- .settings-sidebar {
- width: 32%; /* Reduced from 35% */
- background: #272727;
- padding: 18px 12px; /* Reduced from 24px 15px */
- color: white;
- display: flex;
- flex-direction: column;
- align-items: center;
- box-shadow: 6px 0 12px -6px rgba(0,0,0,0.3);
- position: relative;
- overflow: hidden;
- }
-
- .settings-sidebar h2 {
- margin-bottom: 16px; /* Reduced from 20px */
- font-weight: 600;
- font-size: 22px; /* Reduced from 24px */
- text-shadow: 0 1px 3px rgba(0,0,0,0.5);
- text-decoration: none;
- position: relative;
- text-align: center;
- }
-
- .settings-sidebar h2::after {
- content: "";
- position: absolute;
- left: 50%;
- transform: translateX(-50%);
- bottom: -6px; /* Reduced from -8px */
- width: 36px; /* Reduced from 40px */
- height: 3px;
- background: white;
- border-radius: 2px;
- }
-
- .settings-sidebar ul {
- list-style: none;
- padding: 0;
- width: 100%;
- margin-top: 5px; /* Reduced from 10px */
- }
-
- .settings-sidebar li {
- padding: 10px 12px; /* Reduced from 14px */
- margin: 6px 0; /* Reduced from 8px 0 */
- text-align: left;
- cursor: pointer;
- transition: all 0.5s cubic-bezier(0.19, 1, 0.22, 1);
- border-radius: 8px;
- font-weight: 500;
- font-size: 17px; /* increased from 15px */
- position: relative;
- animation: slideIn 0.5s cubic-bezier(0.19, 1, 0.22, 1);
- animation-fill-mode: both;
- display: flex;
- align-items: center;
- }
-
- .settings-sidebar li:hover {
- background: #444;
- transform: translateX(5px);
- }
-
- .settings-sidebar .active {
- background: #444;
- color: white;
- transform: translateX(0);
- }
-
- .settings-sidebar .active:hover {
- transform: translateX(0);
- }
-
- .settings-sidebar li:hover::before {
- height: 100%;
- }
-
- .settings-sidebar .active::before {
- background: #dc3545;
- }
-
- /* Custom Scrollbar */
- .settings-content {
- flex: 1;
- padding: 24px; /* Reduced from 32px */
- color: white;
- text-align: center;
- max-height: 100%;
- overflow-y: auto;
- scrollbar-width: thin;
- scrollbar-color: darkgreen black;
- background: #1e1e1e;
- position: relative;
- }
-
- /* Webkit (Chrome, Safari) Scrollbar */
- .settings-content::-webkit-scrollbar {
- width: 6px; /* Reduced from 8px */
- }
- .settings-content::-webkit-scrollbar-track {
- background: #333;
- border-radius: 3px; /* Reduced from 4px */
- }
- .settings-content::-webkit-scrollbar-thumb {
- background: linear-gradient(180deg, #dc3545, #b02a37);
- border-radius: 3px; /* Reduced from 4px */
- }
- .settings-content::-webkit-scrollbar-thumb:hover {
- background: linear-gradient(180deg, #ff3b47, #dc3545);
- }
-
- .settings-content h2 {
- margin-bottom: 24px; /* Reduced from 30px */
- font-weight: 600;
- font-size: 22px; /* Reduced from 24px */
- color: white;
- text-shadow: 0 1px 3px rgba(0,0,0,0.4);
- letter-spacing: 0.5px;
- position: relative;
- display: inline-block;
- padding-bottom: 6px; /* Reduced from 8px */
- }
-
- .settings-content h2::after {
- content: "";
- position: absolute;
- bottom: 0;
- left: 0;
- width: 100%;
- height: 2px;
- background: white;
- border-radius: 2px;
- }
-
- .settings-content div {
- animation: sectionFade 0.7s cubic-bezier(0.19, 1, 0.22, 1);
- }
-
- /* Toggle Slider Styles */
- .toggle-slider {
- display: flex;
- align-items: center;
- margin: 12px 0; /* Reduced from 16px 0 */
- cursor: pointer;
- padding: 8px 14px; /* Reduced from 10px 16px */
- background: rgba(255, 255, 255, 0.03);
- border-radius: 6px; /* Reduced from 8px */
- transition: all 0.5s ease;
- user-select: none;
- border: 1px solid rgba(255, 255, 255, 0.05);
- }
-
- .toggle-slider:hover {
- background: rgba(255, 255, 255, 0.05);
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
- transform: translateY(-2px);
- }
-
- .toggle-slider input {
- display: none;
- }
-
- .toggle-slider .slider {
- position: relative;
- display: inline-block;
- width: 42px; /* Reduced from 48px */
- height: 22px; /* Reduced from 24px */
- background-color: rgba(255, 255, 255, 0.2);
- border-radius: 22px;
- margin-right: 12px; /* Reduced from 14px */
- transition: all 0.5s cubic-bezier(0.19, 1, 0.22, 1);
- box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2);
- }
-
- .toggle-slider .slider::before {
- content: "";
- position: absolute;
- height: 16px; /* Reduced from 18px */
- width: 16px; /* Reduced from 18px */
- left: 3px;
- bottom: 3px;
- background-color: white;
- border-radius: 50%;
- transition: all 0.5s cubic-bezier(0.19, 1, 0.22, 1);
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
- }
-
- .toggle-slider input:checked + .slider {
- background-color: #4CAF50;
- box-shadow: 0 0 0 1px rgba(220, 53, 69, 0.05), inset 0 1px 3px rgba(0, 0, 0, 0.2);
- }
-
- .toggle-slider input:checked + .slider::before {
- transform: translateX(20px); /* Reduced from 24px */
- }
-
- .toggle-slider input:checked + .slider::after {
- opacity: 1;
- }
-
- .rolocate-logo {
- width: 90px !important; /* Reduced from 110px */
- height: 90px !important; /* Reduced from 110px */
- object-fit: contain;
- border-radius: 14px; /* Reduced from 16px */
- display: block;
- margin: 0 auto 16px auto; /* Reduced from 20px */
- box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
- transition: all 0.5s ease;
- border: 2px solid rgba(220, 53, 69, 0.4);
- }
-
- .rolocate-logo:hover {
- transform: scale(1.05);
- }
-
- .version {
- font-size: 13px; /* Reduced from 14px */
- color: #aaa;
- margin-bottom: 24px; /* Reduced from 30px */
- display: inline-block;
- padding: 5px 14px; /* Reduced from 6px 16px */
- background: rgba(220, 53, 69, 0.1);
- border-radius: 18px; /* Reduced from 20px */
- border: 1px solid rgba(220, 53, 69, 0.2);
- }
-
- .settings-content ul {
- text-align: left;
- list-style-type: none;
- padding: 0;
- margin-top: 16px; /* Reduced from 20px */
- }
-
- .settings-content ul li {
- margin: 12px 0; /* Reduced from 16px 0 */
- padding: 10px 14px; /* Reduced from 12px 16px */
- background: rgba(255, 255, 255, 0.03);
- border-radius: 6px; /* Reduced from 8px */
- transition: all 0.4s ease;
- }
-
- .settings-content ul li:hover {
- background: rgba(255, 255, 255, 0.05);
- border-left: 3px solid #4CAF50;
- transform: translateX(5px);
- }
-
- .settings-content ul li strong {
- color: #4CAF50;
- }
-
- .warning_advanced {
- font-size: 14px; /* Reduced from 16px */
- color: #ff3b47;
- font-weight: bold;
- padding: 8px 14px; /* Reduced from 10px 16px */
- background: rgba(220, 53, 69, 0.1);
- border-radius: 6px;
- margin-bottom: 16px; /* Reduced from 20px */
- display: inline-block;
- border: 1px solid rgba(220, 53, 69, 0.2);
- }
-
- .average_text {
- font-size: 16px; /* Reduced from 18px */
- color: #e0e0e0;
- font-weight: 500;
- margin-top: 12px; /* Reduced from 15px */
- line-height: 1.5;
- letter-spacing: 0.3px;
- background: linear-gradient(90deg, #ff3b47, #ff6b74);
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- display: inline-block;
- }
-
- .quicknav-container {
- display: flex;
- align-items: center;
- gap: 12px; /* Reduced from 16px */
- margin: 16px 0; /* Reduced from 20px 0 */
- flex-wrap: wrap;
- }
-
- .edit-nav-button {
- padding: 6px 14px; /* Reduced from 8px 16px */
- background: #4CAF50;
- color: white;
- border: none;
- border-radius: 6px; /* Reduced from 8px */
- cursor: pointer;
- font-family: 'Inter', 'Helvetica', sans-serif;
- font-size: 12px; /* Reduced from 13px */
- font-weight: 600;
- letter-spacing: 0.5px;
- text-transform: uppercase;
- transition: all 0.5s cubic-bezier(0.19, 1, 0.22, 1);
- height: auto;
- line-height: 1.5;
- position: relative;
- overflow: hidden;
- }
-
- .edit-nav-button:hover {
- transform: translateY(-3px);
- background: linear-gradient(135deg, #1e8449 0%, #196f3d 100%);
- }
-
- .edit-nav-button:hover::before {
- left: 100%;
- }
-
- .edit-nav-button:active {
- background: linear-gradient(135deg, #1e8449 0%, #196f3d 100%);
- transform: translateY(1px);
- }
-
- /* Dropdown styling */
- select {
- width: 100%;
- padding: 10px 14px; /* Reduced from 12px 16px */
- border-radius: 6px; /* Reduced from 8px */
- background: rgba(255, 255, 255, 0.05);
- color: #e0e0e0;
- font-size: 14px; /* Reduced from 14px */
- appearance: none;
- background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="%23dc3545" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>');
- background-repeat: no-repeat;
- background-position: right 14px center;
- background-size: 14px;
- transition: all 0.5s ease;
- cursor: pointer;
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
- border-color: rgba(255, 255, 255, 0.05);
- }
-
- /* Dropdown hint styling */
- #location-hint {
- margin-top: 10px; /* Reduced from 12px */
- font-size: 12px; /* Reduced from 13px */
- color: #c0c0c0;
- background: rgba(255, 255, 255, 0.05);
- border-radius: 6px; /* Reduced from 8px */
- padding: 10px 14px; /* Reduced from 12px 16px */
- border: 1px solid rgba(255, 255, 255, 0.05);
- line-height: 1.6;
- transition: all 0.5s ease;
- }
-
- /* Section separator */
- .section-separator {
- width: 100%;
- height: 1px;
- background: linear-gradient(90deg, transparent, #272727, transparent);
- margin: 24px 0; /* Reduced from 30px 0 */
- }
-
-
- /* Help section styles */
- .help-section h3, .about-section h3 {
- color: white;
- margin-top: 20px; /* Reduced from 25px */
- margin-bottom: 12px; /* Reduced from 15px */
- font-size: 16px; /* Reduced from 18px */
- text-align: left;
- }
-
- /* Hint text styling */
- .hint-text {
- font-size: 13px; /* Reduced from 14px */
- color: #a0a0a0;
- margin-top: 6px; /* Reduced from 8px */
- margin-left: 16px; /* Reduced from 20px */
- text-align: left;
- }
-
- /* Location settings styling */
- .location-settings {
- background: rgba(255, 255, 255, 0.03);
- border-radius: 6px; /* Reduced from 8px */
- padding: 14px; /* Reduced from 16px */
- margin-top: 16px; /* Reduced from 20px */
- border: 1px solid rgba(255, 255, 255, 0.05);
- transition: all 0.5s ease;
- }
-
- .setting-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 10px; /* Reduced from 12px */
- }
-
- .setting-header span {
- font-size: 14px; /* Reduced from 15px */
- font-weight: 500;
- }
-
- .help-icon {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 18px; /* Reduced from 20px */
- height: 18px; /* Reduced from 20px */
- background: rgba(220, 53, 69, 0.2);
- border-radius: 50%;
- font-size: 11px; /* Reduced from 12px */
- color: #ff3b47;
- cursor: help;
- transition: all 0.5s ease;
- }
-
- /* Manual coordinates input styling */
- #manual-coordinates {
- margin-top: 12px !important; /* Reduced from 15px */
- }
-
- .coordinates-inputs {
- gap: 8px !important; /* Reduced from 10px */
- margin-bottom: 10px !important; /* Reduced from 12px */
- }
-
- #manual-coordinates input {
- padding: 8px 10px !important; /* Reduced from 10px 12px */
- border-radius: 6px !important; /* Reduced from 8px */
- font-size: 13px !important; /* Reduced from default */
- }
-
- #manual-coordinates label {
- margin-bottom: 6px !important; /* Reduced from 8px */
- font-size: 13px !important; /* Reduced from 14px */
- }
-
- #save-coordinates {
- margin-top: 6px !important; /* Reduced from 8px */
- }
-
- /* Animated content */
- .animated-content {
- animation: sectionFade 0.7s cubic-bezier(0.19, 1, 0.22, 1);
- }
- `;
-
- document.head.appendChild(style);
-
- // Enhanced sidebar navigation with animation
- document.querySelectorAll(".settings-sidebar li").forEach((li, index) => {
- // Add staggered animation delay
- li.style.animationDelay = `${0.05 * (index + 1)}s`;
-
- 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 animation
- settingsBody.style.opacity = "0";
- settingsBody.style.transform = "translateY(10px)";
- settingsTitle.style.opacity = "0";
- settingsTitle.style.transform = "translateY(10px)";
-
- setTimeout(() => {
- // Update content
- settingsTitle.textContent = section.charAt(0).toUpperCase() + section.slice(1);
- settingsBody.innerHTML = getSettingsContent(section);
-
- // Show the edit button when Quick Nav is checked
- if (section === "general") {
- const quickNavCheckbox = document.getElementById("quicknav");
- const editButton = document.getElementById("edit-quicknav-btn");
-
- if (quickNavCheckbox && editButton) {
- // Initialize button visibility
- editButton.style.display = localStorage.getItem("ROLOCATE_quicknav") === "true" ? "block" : "none";
-
- // Update button visibility when checkbox changes
- quickNavCheckbox.addEventListener("change", function() {
- editButton.style.display = this.checked ? "block" : "none";
- });
- }
- }
-
- // Apply fade-in animation
- settingsBody.style.transition = "all 0.4s cubic-bezier(0.19, 1, 0.22, 1)";
- settingsTitle.style.transition = "all 0.4s cubic-bezier(0.19, 1, 0.22, 1)";
-
- // Trigger reflow to restart animation
- void settingsBody.offsetWidth;
- void settingsTitle.offsetWidth;
-
- settingsBody.style.opacity = "1";
- settingsBody.style.transform = "translateY(0)";
- settingsTitle.style.opacity = "1";
- settingsTitle.style.transform = "translateY(0)";
-
- // Apply stored settings to ensure toggles match localStorage
- applyStoredSettings();
- }, 200);
- });
- });
-
- // Close button with enhanced animation
- document.getElementById("close-settings").addEventListener("click", function() {
- // Check if manual mode is selected with empty coordinates
- const priorityLocation = localStorage.getItem("ROLOCATE_prioritylocation");
- if (priorityLocation === "manual") {
- try {
- const coords = JSON.parse(GM_getValue("ROLOCATE_coordinates", '{"lat":"","lng":""}'));
- if (!coords.lat || !coords.lng) {
- notifications('Please set the latitude and longitude values for the manual location, or set it to automatic.', 'error', '⚠️', 8000);
- return; // Prevent closing
- }
- } catch (e) {
- console.error("Error checking coordinates:", e);
- notifications('Error checking location settings', 'error', '⚠️', 8000);
- return; // Prevent closing
- }
- }
-
- // Proceed with closing if validation passes
- const menu = document.getElementById("userscript-settings-menu");
- menu.style.animation = "fadeOut 0.4s cubic-bezier(0.19, 1, 0.22, 1) forwards";
-
- // Add rotation to close button when closing
- this.style.transform = "rotate(90deg)";
-
- setTimeout(() => menu.remove(), 400);
- });
-
- // Apply stored settings immediately when opened
- applyStoredSettings();
-
- // Add "Edit Quick Nav" button functionality
- setTimeout(() => {
- const editButton = document.getElementById("edit-quicknav-btn");
- if (editButton) {
- // Initialize button visibility
- const quickNavEnabled = localStorage.getItem("ROLOCATE_quicknav") === "true";
- editButton.style.display = quickNavEnabled ? "block" : "none";
-
- // Add click handler for edit button
- editButton.addEventListener("click", function() {
- // Here you'd open a modal or implement the edit quick nav functionality
- alert("Quick Navigation Editor will open here!");
- // Alternatively, implement a proper modal for editing quick nav links
- });
- }
- }, 100);
-
- // Add ripple effect to buttons
- const buttons = document.querySelectorAll(".edit-nav-button, .settings-button");
- buttons.forEach(button => {
- button.addEventListener("mousedown", function(e) {
- const ripple = document.createElement("span");
- const rect = this.getBoundingClientRect();
-
- const size = Math.max(rect.width, rect.height);
- const x = e.clientX - rect.left - size / 2;
- const y = e.clientY - rect.top - size / 2;
-
- ripple.style.cssText = `
- position: absolute;
- background: rgba(255,255,255,0.4);
- border-radius: 50%;
- pointer-events: none;
- width: ${size}px;
- height: ${size}px;
- top: ${y}px;
- left: ${x}px;
- transform: scale(0);
- transition: transform 0.6s, opacity 0.6s;
- `;
-
- this.appendChild(ripple);
-
- setTimeout(() => {
- ripple.style.transform = "scale(2)";
- ripple.style.opacity = "0";
- setTimeout(() => ripple.remove(), 600);
- }, 10);
- });
- });
- }
-
-
- function showQuickNavPopup() {
- // Remove existing quick nav if it exists
- const existingNav = document.getElementById("premium-quick-nav");
- if (existingNav) existingNav.remove();
-
- // POPUP CREATION
- // Create overlay
- const overlay = document.createElement("div");
- overlay.id = "quicknav-overlay";
- overlay.style.position = "fixed";
- overlay.style.top = "0";
- overlay.style.left = "0";
- overlay.style.width = "100%";
- overlay.style.height = "100%";
- overlay.style.backgroundColor = "rgba(0,0,0,0)"; // Darker overlay for dark mode
- overlay.style.backdropFilter = "blur(1px)";
- overlay.style.zIndex = "10000";
- overlay.style.opacity = "0";
- overlay.style.transition = "opacity 0.3s ease";
-
- // Create popup
- const popup = document.createElement("div");
- popup.id = "premium-quick-nav-popup";
- popup.style.position = "fixed";
- popup.style.top = "50%";
- popup.style.left = "50%";
- popup.style.transform = "translate(-50%, -50%) scale(0.95)";
- popup.style.opacity = "0";
- popup.style.background = "linear-gradient(145deg, #0a0a0a, #121212)"; // Darker background for dark mode
- popup.style.color = "white";
- popup.style.padding = "32px";
- popup.style.borderRadius = "16px";
- popup.style.boxShadow = "0 20px 40px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.05)";
- popup.style.zIndex = "10001";
- popup.style.width = "600px";
- popup.style.maxWidth = "90%";
- popup.style.maxHeight = "85vh";
- popup.style.overflowY = "auto";
- popup.style.transition = "transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.4s ease";
-
- // Get saved quick navs (if any)
- const saved = JSON.parse(localStorage.getItem("ROLOCATE_quicknav_settings") || "[]");
-
- // Build header
- const header = `
- <div style="position: relative; margin-bottom: 24px; text-align: center;">
- <h2 style="margin: 0 0 8px; font-size: 28px; font-weight: 600; background: linear-gradient(90deg, #4CAF50, #8BC34A); -webkit-background-clip: text; background-clip: text; color: transparent;">Quick Navigation</h2>
- <p style="margin: 0; font-size: 16px; color: #a0a0a0; font-weight: 300;">Configure up to 9 custom navigation shortcuts</p>
- <div style="width: 60px; height: 4px; background: linear-gradient(90deg, #4CAF50, #8BC34A); margin: 16px auto; border-radius: 2px;"></div>
-
- <img src="${window.Base64Images.logo}" alt="Logo" style="position: absolute; bottom: -581px; left: 0; height: 40px; margin: 12px; border-radius: 12px; transition: all 0.3s ease-in-out; box-shadow: 0 0 10px rgba(255, 0, 0, 0.6);"
- onmouseover="this.style.transform='scale(1.2)'; this.style.boxShadow='0 0 15px rgba(255, 0, 0, 1)';"
- onmouseout="this.style.transform='scale(1)'; this.style.boxShadow='0 0 10px rgba(255, 0, 0, 0.6)';" />
- </div>
- `;
-
-
-
- // Build inputs for 9 links in a 3x3 grid
- const inputsGrid = `
- <div class="quicknav-inputs-grid" style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 24px;">
- ${Array.from({length: 9}, (_, i) => `
- <div class="quicknav-input-group" style="background: rgba(255,255,255,0.03); padding: 16px; border-radius: 12px; border: 1px solid rgba(255,255,255,0.05);">
- <p style="font-weight: 500; font-size: 14px; margin: 0 0 8px; color: #A5D6A7;">${i + 1}</p>
- <input type="text" id="quicknav-name-${i}" placeholder="Name" value="${saved[i]?.name || ""}"
- style="width: 100%; padding: 10px 12px; margin-bottom: 8px; border-radius: 8px; border: none; background: rgba(255,255,255,0.05); color: white; font-size: 14px; transition: all 0.2s;">
- <input type="text" id="quicknav-link-${i}" placeholder="URL" value="${saved[i]?.link || ""}"
- style="width: 100%; padding: 10px 12px; border-radius: 8px; border: none; background: rgba(255,255,255,0.05); color: white; font-size: 14px; transition: all 0.2s;">
- </div>
- `).join("")}
- </div>
- `;
-
- // Build footer with buttons
- const footer = `
- <div style="display: flex; justify-content: flex-end; gap: 12px;">
- <button id="cancel-quicknav" style="background: transparent; color: #a0a0a0; border: 1px solid rgba(255,255,255,0.1); padding: 12px 20px; border-radius: 8px; cursor: pointer; font-weight: 500; transition: all 0.2s;">
- Cancel
- </button>
- <button id="save-quicknav" style="background: linear-gradient(90deg, #4CAF50, #388E3C); color: white; border: none; padding: 12px 24px; border-radius: 8px; cursor: pointer; font-weight: 500; box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3); transition: all 0.2s;">
- Save Changes
- </button>
- </div>
- `;
-
- // Combine all sections
- popup.innerHTML = header + inputsGrid + footer;
-
- // Add elements to DOM
- document.body.appendChild(overlay);
- document.body.appendChild(popup);
-
- // POPUP EVENTS
- // Add input hover and focus effects
- popup.querySelectorAll('input').forEach(input => {
- input.addEventListener('focus', () => {
- input.style.background = 'rgba(255,255,255,0.1)';
- input.style.boxShadow = '0 0 0 2px rgba(76, 175, 80, 0.4)';
- });
-
- input.addEventListener('blur', () => {
- input.style.background = 'rgba(255,255,255,0.05)';
- input.style.boxShadow = 'none';
- });
-
- input.addEventListener('mouseover', () => {
- if (document.activeElement !== input) {
- input.style.background = 'rgba(255,255,255,0.08)';
- }
- });
-
- input.addEventListener('mouseout', () => {
- if (document.activeElement !== input) {
- input.style.background = 'rgba(255,255,255,0.05)';
- }
- });
- });
-
- // Add button hover effects
- const saveBtn = popup.querySelector('#save-quicknav');
- saveBtn.addEventListener('mouseover', () => {
- saveBtn.style.background = 'linear-gradient(90deg, #66BB6A, #4CAF50)';
- saveBtn.style.boxShadow = '0 4px 15px rgba(76, 175, 80, 0.4)';
- saveBtn.style.transform = 'translateY(-1px)';
- });
-
- saveBtn.addEventListener('mouseout', () => {
- saveBtn.style.background = 'linear-gradient(90deg, #4CAF50, #388E3C)';
- saveBtn.style.boxShadow = '0 4px 12px rgba(76, 175, 80, 0.3)';
- saveBtn.style.transform = 'translateY(0)';
- });
-
- const cancelBtn = popup.querySelector('#cancel-quicknav');
- cancelBtn.addEventListener('mouseover', () => {
- cancelBtn.style.background = 'rgba(255,255,255,0.05)';
- });
-
- cancelBtn.addEventListener('mouseout', () => {
- cancelBtn.style.background = 'transparent';
- });
-
- // Animate in
- setTimeout(() => {
- overlay.style.opacity = "1";
- popup.style.opacity = "1";
- popup.style.transform = "translate(-50%, -50%) scale(1)";
- }, 10);
-
- // POPUP CLOSE FUNCTION
- function closePopup() {
- overlay.style.opacity = "0";
- popup.style.opacity = "0";
- popup.style.transform = "translate(-50%, -50%) scale(0.95)";
- setTimeout(() => {
- overlay.remove();
- popup.remove();
- }, 300);
- }
-
- // Save on click
- popup.querySelector("#save-quicknav").addEventListener("click", () => {
- const quickNavSettings = [];
- for (let i = 0; i < 9; i++) {
- const name = document.getElementById(`quicknav-name-${i}`).value.trim();
- const link = document.getElementById(`quicknav-link-${i}`).value.trim();
- if (name && link) {
- quickNavSettings.push({
- name,
- link
- });
- }
- }
- localStorage.setItem("ROLOCATE_quicknav_settings", JSON.stringify(quickNavSettings));
- closePopup();
- });
-
- // Cancel button
- popup.querySelector("#cancel-quicknav").addEventListener("click", closePopup);
-
- // Close when clicking overlay
- overlay.addEventListener("click", (e) => {
- if (e.target === overlay) {
- closePopup();
- }
- });
-
- // Close with ESC key
- document.addEventListener("keydown", function escClose(e) {
- if (e.key === "Escape") {
- closePopup();
- document.removeEventListener("keydown", escClose);
- }
- });
-
- // AUTO-INIT AND KEYBOARD SHORTCUT
- // Set up keyboard shortcut (Alt+Q)
- document.addEventListener("keydown", function keyboardShortcut(e) {
- if (e.altKey && e.key === "q") {
- showQuickNavPopup();
- }
- });
- }
-
-
-
-
- function applyStoredSettings() {
- // Handle all checkboxes
- document.querySelectorAll("input[type='checkbox']").forEach(checkbox => {
- const storageKey = `ROLOCATE_${checkbox.id}`;
- const savedValue = localStorage.getItem(storageKey);
- checkbox.checked = savedValue === "true";
-
- checkbox.addEventListener("change", () => {
- localStorage.setItem(storageKey, checkbox.checked);
-
- if (checkbox.id === "quicknav") {
- const editBtn = document.getElementById("edit-quicknav-btn");
- if (editBtn) {
- editBtn.style.display = checkbox.checked ? "inline-block" : "none";
- }
- }
- });
-
- if (checkbox.id === "quicknav" && checkbox.checked) {
- const editBtn = document.getElementById("edit-quicknav-btn");
- if (editBtn) {
- editBtn.style.display = "inline-block";
- }
- }
- });
-
- // Handle dropdown for prioritylocation-select
- const prioritySelect = document.getElementById("prioritylocation-select");
- if (prioritySelect) {
- const storageKey = "ROLOCATE_prioritylocation";
- const savedValue = localStorage.getItem(storageKey) || "automatic";
- prioritySelect.value = savedValue;
-
- // Show/hide coordinates inputs based on selected value
- const manualCoordinates = document.getElementById("manual-coordinates");
- if (manualCoordinates) {
- manualCoordinates.style.display = savedValue === "manual" ? "block" : "none";
-
- // Set input values from stored coordinates if available
- if (savedValue === "manual") {
- try {
- const savedCoords = JSON.parse(GM_getValue("ROLOCATE_coordinates", '{"lat":"","lng":""}'));
- document.getElementById("latitude").value = savedCoords.lat || "";
- document.getElementById("longitude").value = savedCoords.lng || "";
-
- // If manual mode but no coordinates saved, revert to automatic
- if (!savedCoords.lat || !savedCoords.lng) {
- prioritySelect.value = "automatic";
- localStorage.setItem(storageKey, "automatic");
- manualCoordinates.style.display = "none";
- }
- } catch (e) {
- console.error("Error loading saved coordinates:", e);
- }
- }
- }
-
- prioritySelect.addEventListener("change", () => {
- const newValue = prioritySelect.value;
- localStorage.setItem(storageKey, newValue);
-
- // Show/hide coordinates inputs based on new value
- if (manualCoordinates) {
- manualCoordinates.style.display = newValue === "manual" ? "block" : "none";
-
- // When switching to manual mode, load any saved coordinates
- if (newValue === "manual") {
- try {
- const savedCoords = JSON.parse(GM_getValue("ROLOCATE_coordinates", '{"lat":"","lng":""}'));
- document.getElementById("latitude").value = savedCoords.lat || "";
- document.getElementById("longitude").value = savedCoords.lng || "";
-
- // If no coordinates exist, keep the inputs empty
- } catch (e) {
- console.error("Error loading saved coordinates:", e);
- }
- }
- }
- });
- }
-
- // Button click handlers
- const editQuickNavBtn = document.getElementById("edit-quicknav-btn");
- if (editQuickNavBtn) {
- editQuickNavBtn.addEventListener("click", () => {
- showQuickNavPopup();
- });
- }
-
- // Save coordinates button handler
- const saveCoordinatesBtn = document.getElementById("save-coordinates");
- if (saveCoordinatesBtn) {
- saveCoordinatesBtn.addEventListener("click", () => {
- const latInput = document.getElementById("latitude");
- const lngInput = document.getElementById("longitude");
- const lat = latInput.value.trim();
- const lng = lngInput.value.trim();
-
- // If manual mode but no coordinates provided, revert to automatic
- if (!lat || !lng) {
- const prioritySelect = document.getElementById("prioritylocation-select");
- if (prioritySelect) {
- prioritySelect.value = "automatic";
- localStorage.setItem("ROLOCATE_prioritylocation", "automatic");
- document.getElementById("manual-coordinates").style.display = "none";
-
- // show feedback to user even if they dont see it
- saveCoordinatesBtn.textContent = "Reverted to Automatic!";
- saveCoordinatesBtn.style.background = "#4CAF50";
-
- setTimeout(() => {
- saveCoordinatesBtn.textContent = "Save Coordinates";
- saveCoordinatesBtn.style.background = "background: #4CAF50;";
- }, 2000);
- }
- return;
- }
-
- // Validate coordinates
- const latNum = parseFloat(lat);
- const lngNum = parseFloat(lng);
- if (isNaN(latNum) || isNaN(lngNum) || latNum < -90 || latNum > 90 || lngNum < -180 || lngNum > 180) {
- alert("Invalid coordinates! Latitude must be between -90 and 90, and longitude between -180 and 180.");
- return;
- }
-
- // Save valid coordinates
- const coordinates = {
- lat,
- lng
- };
- GM_setValue("ROLOCATE_coordinates", JSON.stringify(coordinates));
-
- // Ensure we're in manual mode
- localStorage.setItem("ROLOCATE_prioritylocation", "manual");
- if (prioritySelect) {
- prioritySelect.value = "manual";
- }
-
- // Provide feedback
- saveCoordinatesBtn.textContent = "Saved!";
- saveCoordinatesBtn.style.background = "linear-gradient(135deg, #1e8449 0%, #196f3d 100%);";
-
- setTimeout(() => {
- saveCoordinatesBtn.textContent = "Save Coordinates";
- saveCoordinatesBtn.style.background = "background: #4CAF50;";
- }, 2000);
- });
- }
- }
-
-
-
-
- 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);
- }
-
-
-
-
- /*************************************************************************
- Premium Notification System
- *************************************************************************/
- function notifications(message, type = 'info', emoji = '', duration = 3000) {
- // Helper function to manipulate colors - supports hex, rgb, and rgba
- function adjustColor(color, percent) {
- // Handle hex colors
- if (color.startsWith('#')) {
- 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);
- }
- // Handle rgb/rgba colors
- else if (color.startsWith('rgb')) {
- const isRGBA = color.startsWith('rgba');
- const parts = color.match(/\d+(\.\d+)?/g).map(Number);
-
- for (let i = 0; i < 3; i++) {
- parts[i] = Math.max(0, Math.min(255, parts[i] + (2.55 * percent)));
- }
-
- return isRGBA ?
- `rgba(${parts[0]}, ${parts[1]}, ${parts[2]}, ${parts[3]})` :
- `rgb(${parts[0]}, ${parts[1]}, ${parts[2]})`;
- }
-
- return color; // Return original if format not recognized
- }
-
- // Inject CSS styles for the toast system once
- if (!document.getElementById('premium-toast-styles')) {
- const style = document.createElement('style');
- style.id = 'premium-toast-styles';
- style.innerHTML = `
- @keyframes toast-slide-in {
- 0% { opacity: 0; transform: translateX(50px); }
- 100% { opacity: 1; transform: translateX(0); }
- }
-
- @keyframes toast-slide-out {
- 0% { opacity: 1; transform: translateX(0); }
- 100% { opacity: 0; transform: translateX(50px); }
- }
-
- @keyframes progress-shrink {
- 0% { width: 100%; }
- 100% { width: 0%; }
- }
-
- @keyframes emoji-pop {
- 0% { transform: scale(0.8); opacity: 0.7; }
- 40% { transform: scale(1.3); opacity: 1; }
- 60% { transform: scale(0.9); opacity: 0.95; }
- 80% { transform: scale(1.1); opacity: 1; }
- 100% { transform: scale(1); opacity: 1; }
- }
-
- @keyframes emoji-float {
- 0% { transform: translateY(0); }
- 50% { transform: translateY(-4px); }
- 100% { transform: translateY(0); }
- }
-
- @keyframes emoji-glow {
- 0% { text-shadow: 0 0 5px rgba(255,255,255,0); }
- 50% { text-shadow: 0 0 10px rgba(255,255,255,0.5); }
- 100% { text-shadow: 0 0 5px rgba(255,255,255,0); }
- }
-
- #toast-container {
- position: fixed;
- top: 24px;
- right: 24px;
- z-index: 999999;
- display: flex;
- flex-direction: column;
- gap: 12px;
- pointer-events: none;
- }
-
- .toast {
- position: relative;
- min-width: 320px;
- max-width: 420px;
- padding: 16px 20px;
- border-radius: 12px;
- box-shadow: 0 8px 20px rgba(0,0,0,0.18), 0 2px 8px rgba(0,0,0,0.15), 0 0 1px rgba(255,255,255,0.2);
- animation: toast-slide-in 0.5s cubic-bezier(0.25, 1, 0.5, 1) forwards;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
- backdrop-filter: blur(10px);
- word-wrap: break-word;
- pointer-events: auto;
- overflow: hidden;
- display: flex;
- flex-direction: column;
- }
-
- .toast.removing {
- animation: toast-slide-out 0.5s cubic-bezier(0.55, 0, 0.1, 1) forwards;
- }
-
- .toast .toast-content {
- display: flex;
- align-items: center;
- gap: 12px;
- color: white;
- font-size: 15px;
- line-height: 1.5;
- font-weight: 500;
- letter-spacing: 0.2px;
- }
-
- .toast-emoji-wrapper {
- position: relative;
- display: flex;
- justify-content: center;
- align-items: center;
- width: 32px;
- height: 32px;
- }
-
- .toast-emoji {
- font-size: 22px;
- position: relative;
- display: inline-block;
- animation: emoji-pop 0.6s ease-out, emoji-float 3s ease-in-out infinite, emoji-glow 2s ease-in-out infinite;
- transform-origin: center;
- z-index: 2;
- }
-
- .toast .message {
- flex: 1;
- }
-
- .toast-close-btn {
- position: absolute;
- top: 12px;
- right: 12px;
- width: 20px;
- height: 20px;
- cursor: pointer;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- background: rgba(255, 255, 255, 0.15);
- transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
- border: 1px solid rgba(255, 255, 255, 0.2);
- }
-
- .toast-close-btn:before, .toast-close-btn:after {
- content: '';
- position: absolute;
- width: 12px;
- height: 2px;
- background: rgba(255, 255, 255, 0.9);
- border-radius: 1px;
- transition: all 0.3s ease;
- }
-
- .toast-close-btn:before {
- transform: rotate(45deg);
- }
-
- .toast-close-btn:after {
- transform: rotate(-45deg);
- }
-
- .toast-close-btn:hover {
- background: rgba(255, 255, 255, 0.25);
- transform: scale(1.1) rotate(90deg);
- box-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
- }
-
- .toast-close-btn:hover:before, .toast-close-btn:hover:after {
- background: rgba(255, 255, 255, 1);
- }
-
- .toast .progress-bar-container {
- position: absolute;
- bottom: 0;
- left: 0;
- height: 4px;
- width: 100%;
- background-color: rgba(255, 255, 255, 0.2);
- overflow: hidden;
- }
-
- .toast .progress-bar {
- height: 100%;
- width: 100%;
- background: linear-gradient(90deg, rgba(255,255,255,0.5), rgba(255,255,255,0.9));
- animation-name: progress-shrink;
- animation-timing-function: linear;
- animation-fill-mode: forwards;
- box-shadow: 0 0 8px rgba(255, 255, 255, 0.5);
- }
-
- .toast-icon {
- width: 24px;
- height: 24px;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 50%;
- background: rgba(255, 255, 255, 0.25);
- flex-shrink: 0;
- box-shadow: 0 0 8px rgba(255, 255, 255, 0.2);
- }
-
- .toast.success {
- background: linear-gradient(135deg, #43A047, #66BB6A);
- border-left: 4px solid #2E7D32;
- }
-
- .toast.error {
- background: linear-gradient(135deg, #E53935, #EF5350);
- border-left: 4px solid #C62828;
- }
-
- .toast.info {
- background: linear-gradient(135deg, #1E88E5, #42A5F5);
- border-left: 4px solid #1565C0;
- }
-
- .toast.warning {
- background: linear-gradient(135deg, #FB8C00, #FFA726);
- border-left: 4px solid #EF6C00;
- }
- `;
- 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 ${type.toLowerCase()}`;
-
- // Create content wrapper with optional emoji and icon
- const content = document.createElement('div');
- content.className = 'toast-content';
-
- // Add type-specific icon
- const icon = document.createElement('div');
- icon.className = 'toast-icon';
-
- // Set icon content based on type
- let iconContent = '';
- switch (type.toLowerCase()) {
- case 'success':
- iconContent = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>';
- break;
- case 'error':
- iconContent = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>';
- break;
- case 'warning':
- iconContent = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>';
- break;
- case 'info':
- default:
- iconContent = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>';
- break;
- }
-
- icon.innerHTML = iconContent;
- content.appendChild(icon);
-
- // Add emoji if provided with enhanced animations
- if (emoji) {
- const emojiWrapper = document.createElement('div');
- emojiWrapper.className = 'toast-emoji-wrapper';
-
- const emojiSpan = document.createElement('span');
- emojiSpan.className = 'toast-emoji';
- emojiSpan.textContent = emoji;
-
- emojiWrapper.appendChild(emojiSpan);
- content.appendChild(emojiWrapper);
- }
-
- // Add message
- const messageSpan = document.createElement('span');
- messageSpan.className = 'message';
- messageSpan.textContent = message;
- content.appendChild(messageSpan);
-
- toast.appendChild(content);
-
- // Create the enhanced close button (X)
- const closeBtn = document.createElement('div');
- closeBtn.className = 'toast-close-btn';
- closeBtn.addEventListener('click', () => removeToast(toast));
- toast.appendChild(closeBtn);
-
- // Create progress bar container and progress bar
- const progressBarContainer = document.createElement('div');
- progressBarContainer.className = 'progress-bar-container';
-
- const progressBar = document.createElement('div');
- progressBar.className = 'progress-bar';
- progressBar.style.animationDuration = `${duration}ms`;
-
- progressBarContainer.appendChild(progressBar);
- toast.appendChild(progressBarContainer);
-
- // Append toast to container
- container.appendChild(toast);
-
- // Auto-remove toast after the specified duration
- const removeTimeout = setTimeout(() => removeToast(toast), duration);
- let removeTimeoutRef = removeTimeout;
-
- // Add hover pause functionality
- toast.addEventListener('mouseenter', () => {
- // Pause the progress bar animation
- progressBar.style.animationPlayState = 'paused';
- clearTimeout(removeTimeoutRef);
-
- // Subtle scale effect on hover
- toast.style.transform = 'scale(1.02)';
- toast.style.transition = 'transform 0.3s ease';
- });
-
- toast.addEventListener('mouseleave', () => {
- // Resume the progress bar animation
- progressBar.style.animationPlayState = 'running';
- // Reset scale
- toast.style.transform = 'scale(1)';
-
- // Calculate remaining time based on progress bar width percentage
- const remainingPercentage = progressBar.offsetWidth / progressBarContainer.offsetWidth;
- const remainingTime = duration * remainingPercentage;
- // Set new timeout with remaining time
- clearTimeout(removeTimeoutRef);
- removeTimeoutRef = setTimeout(() => removeToast(toast), remainingTime);
- });
-
- // Function to fade out and remove toast
- function removeToast(toastEl) {
- clearTimeout(removeTimeoutRef);
- toastEl.classList.add('removing');
- setTimeout(() => toastEl.remove(), 500);
- }
-
- // Return an object with methods to control the toast
- return {
- remove: () => removeToast(toast),
- update: (newMessage) => {
- messageSpan.textContent = newMessage;
- },
- setType: (newType) => {
- toast.className = `toast ${newType.toLowerCase()}`;
- },
- setDuration: (newDuration) => {
- clearTimeout(removeTimeoutRef);
- // Reset the progress bar animation
- progressBar.style.animation = 'none';
- setTimeout(() => {
- progressBar.style.animation = `progress-shrink ${newDuration}ms linear forwards`;
- removeTimeoutRef = setTimeout(() => removeToast(toast), newDuration);
- }, 10);
- },
- updateEmoji: (newEmoji) => {
- if (emoji) {
- const emojiElement = toast.querySelector('.toast-emoji');
- if (emojiElement) {
- // Reset animation by cloning and replacing
- const parent = emojiElement.parentNode;
- const newEmojiElement = emojiElement.cloneNode(true);
- newEmojiElement.textContent = newEmoji;
- parent.replaceChild(newEmojiElement, emojiElement);
- }
- }
- }
- };
- }
-
-
- function Update_Popup() {
- const VERSION = "V35.3";
- const PREV_VERSION = "V34.3";
-
- // Check if a version other than V35.3 exists and show the popup
- const currentVersion = localStorage.getItem('version') || "V0.0"; // Get saved version or default to "V0.0"
- if (currentVersion !== VERSION) {
- localStorage.setItem('version', VERSION); // Set the new version
- } else {
- return; // If the current version is the latest, do not show the popup
- }
-
- // Remove any previous version flag if present
- if (localStorage.getItem(PREV_VERSION)) {
- localStorage.removeItem(PREV_VERSION);
- }
-
- const css = `
- .first-time-popup {
- display: flex;
- position: fixed;
- inset: 0;
- background: rgba(0, 0, 0, 0.25); /* Increased opacity for darker background without blur */
- justify-content: center;
- align-items: center;
- z-index: 1000;
- opacity: 0;
- animation: fadeIn 0.5s ease-in-out forwards;
- }
- .first-time-popup-content {
- background: linear-gradient(135deg, rgba(30, 30, 40, 0.95) 0%, rgba(15, 15, 25, 0.98) 100%);
- border-radius: 24px;
- padding: 35px;
- width: 450px;
- max-width: 90%;
- box-shadow: 0 15px 40px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.1);
- text-align: center;
- color: #fff;
- transform: scale(0.85);
- animation: scaleUp 0.6s cubic-bezier(0.165, 0.84, 0.44, 1) forwards;
- position: relative;
- overflow: hidden;
- }
- .first-time-popup-content::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- height: 3px;
- background: linear-gradient(90deg, #4da6ff, #9966ff, #4da6ff);
- background-size: 200% 100%;
- animation: shimmer 3s infinite linear;
- }
- .popup-header {
- font-size: 24px;
- font-weight: 800;
- color: #fff;
- text-transform: uppercase;
- letter-spacing: 1.5px;
- margin-bottom: 8px;
- text-align: center;
- text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
- }
- .popup-version {
- font-size: 18px;
- font-weight: bold;
- color: #ffcc00;
- margin-bottom: 20px;
- display: inline-block;
- padding: 5px 15px;
- border-radius: 20px;
- background: rgba(255, 204, 0, 0.1);
- box-shadow: 0 0 0 1px rgba(255, 204, 0, 0.3);
- }
- .popup-info {
- font-size: 15px;
- color: #e0e0e0;
- margin-bottom: 25px;
- line-height: 1.7;
- padding: 18px;
- border-radius: 16px;
- background: rgba(255, 255, 255, 0.03);
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1), inset 0 0 0 1px rgba(255, 255, 255, 0.05);
- }
- .popup-info p {
- margin: 12px 0;
- }
- .popup-info a {
- color: #4da6ff;
- text-decoration: none;
- font-weight: bold;
- transition: all 0.3s ease;
- padding: 2px 5px;
- border-radius: 4px;
- background: rgba(77, 166, 255, 0.1);
- }
- .popup-info a:hover {
- color: #80bfff;
- text-decoration: none;
- background: rgba(77, 166, 255, 0.2);
- box-shadow: 0 0 0 1px rgba(77, 166, 255, 0.3);
- }
- .popup-footer {
- font-size: 14px;
- color: rgba(255, 255, 255, 0.6);
- font-weight: 500;
- margin-top: 15px;
- transition: opacity 0.4s ease-out;
- padding: 8px;
- border-radius: 8px;
- background: rgba(0, 0, 0, 0.2);
- }
- .popup-footer.hidden {
- opacity: 0;
- visibility: hidden;
- }
- .popup-note {
- font-size: 13px;
- font-weight: bold;
- color: #ff6666;
- margin-top: 12px;
- }
- .popup-logo {
- display: block;
- margin: 0 auto 20px;
- width: 90px;
- height: auto;
- border-radius: 18px;
- box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.1);
- transform: translateY(0);
- transition: transform 0.3s ease;
- }
- .popup-logo:hover {
- transform: translateY(-3px);
- }
- .developer-message {
- display: inline-block;
- padding: 10px 15px;
- margin: 10px 0;
- background: rgba(40, 167, 69, 0.1);
- border-left: 3px solid #28a745;
- color: #bfffca;
- border-radius: 3px;
- font-weight: 500;
- text-align: left;
- line-height: 1.5;
- }
- .feature-item {
- display: flex;
- align-items: center;
- margin: 12px 0;
- text-align: left;
- }
- .feature-icon {
- margin-right: 10px;
- color: #4da6ff;
- font-size: 18px;
- }
- .feature-highlight {
- display: inline-block;
- padding: 2px 8px;
- background: rgba(77, 166, 255, 0.15);
- border-radius: 4px;
- color: #ffffff;
- font-weight: bold;
- }
- .first-time-popup-close {
- position: absolute;
- top: 15px;
- right: 20px;
- font-size: 26px;
- font-weight: bold;
- cursor: pointer;
- color: rgba(255, 255, 255, 0.6);
- opacity: 0.4;
- transition: all 0.3s ease;
- pointer-events: none;
- width: 30px;
- height: 30px;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 50%;
- }
- .first-time-popup-close.active {
- opacity: 1;
- pointer-events: auto;
- background: rgba(255, 255, 255, 0.05);
- }
- .first-time-popup-close:hover {
- color: #ff4d4d;
- transform: rotate(90deg);
- background: rgba(255, 77, 77, 0.1);
- }
- @keyframes fadeIn {
- from { opacity: 0; }
- to { opacity: 1; }
- }
- @keyframes fadeOut {
- from { opacity: 1; }
- to { opacity: 0; }
- }
- @keyframes scaleUp {
- 0% { transform: scale(0.85); }
- 70% { transform: scale(1.03); }
- 100% { transform: scale(1); }
- }
- @keyframes scaleDown {
- from { transform: scale(1); }
- to { transform: scale(0.85); opacity: 0; }
- }
- @keyframes shimmer {
- 0% { background-position: 0% 0; }
- 100% { background-position: 200% 0; }
- }
- `;
-
- 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">Rolocate Update</div>
- <div class="popup-version">${VERSION}</div>
- <div class="popup-info">
- <div class="developer-message">
- <span style="font-weight: bold;">From the Developer:</span> Please report any issues on GreasyFork if something breaks! Thank you for your support.
- </div>
-
- <div class="feature-item">
- <span class="feature-icon">✨</span>
- <div>Settings menu slightly revamped</div>
- </div>
-
- <div class="feature-item">
- <span class="feature-icon">🆕</span>
- <div>New in Advanced Settings: <span class="feature-highlight">Set Default Location Mode</span> - Use <span class="feature-highlight">Manual</span> if automatic location detection isn't working</div>
- </div>
-
- <div class="feature-item">
- <span class="feature-icon">🕷</span>
- <div>Fixed a bug that infinitly checks for location. Issue at <a href="https://greatest.deepsurf.us/en/scripts/523727-rolocate/discussions/297042" target="_blank">Greasyfork.</a></div>
- </div>
-
- <div class="feature-item">
- <span class="feature-icon">📚</span>
- <div>Need help? Check out our <a href="https://oqarshi.github.io/Invite/rolocate/index.html" target="_blank">FAQ page</a> or create an issue on greasyfork! 😊</div>
- </div>
-
- <p style="margin-top: 15px; text-align: center; opacity: 0.8;">This message will not appear again until the next update.</p>
- </div>
- <div class="popup-footer">Closing enabled in <span id="countdown-timer"><strong>3</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 = 3;
- const countdownInterval = setInterval(() => {
- countdown--;
- countdownTimer.innerHTML = `<strong>${countdown}</strong>`;
-
- if (countdown <= 0) {
- clearInterval(countdownInterval);
- closeButton.classList.add('active');
- footer.classList.add('hidden');
- }
- }, 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 (existing method)
- document.querySelectorAll(".game-card-native-ad").forEach(ad => {
- const gameCard = ad.closest(".game-card-container");
- if (gameCard) {
- gameCard.style.display = "none";
- }
- });
-
- // New: Block Sponsored Ads Game Card
- document.querySelectorAll("div.gamecardcontainer").forEach(container => {
- if (container.querySelector("div.game-card-native-ad")) {
- container.style.display = "none";
- }
- });
-
- // New: Block Sponsored Section On HomePage
- document.querySelectorAll(".game-sort-carousel-wrapper").forEach(wrapper => {
- const sponsoredLink = wrapper.querySelector('a[href*="Sponsored"]');
- if (sponsoredLink) {
- wrapper.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("[ROLOCATE]", ...args);
- }
- }
-
-
- async function showOldRobloxGreeting() {
- ConsoleLogEnabled("Function showOldRobloxGreeting() started.");
-
- // Check if the URL is roblox.com/home
- if (!window.location.href.includes("roblox.com/home")) {
- ConsoleLogEnabled("Not on roblox.com/home. Exiting function.");
- return; // ⛔ Stops execution if not on the home page
- }
-
- // Check LocalStorage before proceeding
- if (localStorage.getItem("ROLOCATE_ShowOldGreeting") !== "true") {
- ConsoleLogEnabled("ShowOldGreeting is disabled. Exiting function.");
- return; // ⛔ Stops execution if setting is off
- }
-
- ConsoleLogEnabled("Waiting 500ms before proceeding.");
- await new Promise(r => setTimeout(r, 500));
-
- function observeElement(selector) {
- ConsoleLogEnabled(`Observing element: ${selector}`);
- return new Promise((resolve) => {
- const observer = new MutationObserver(() => {
- const element = document.querySelector(selector);
- if (element) {
- ConsoleLogEnabled(`Element found: ${selector}`);
- observer.disconnect();
- resolve(element);
- }
- });
- observer.observe(document.body, {
- childList: true,
- subtree: true
- });
- });
- }
-
- async function fetchAvatar(selector, fallbackImage) {
- ConsoleLogEnabled(`Fetching avatar from selector: ${selector}`);
- for (let attempt = 0; attempt < 3; attempt++) {
- ConsoleLogEnabled(`Attempt ${attempt + 1} to fetch avatar.`);
- const imgElement = document.querySelector(selector);
- if (imgElement && imgElement.src !== fallbackImage) {
- ConsoleLogEnabled(`Avatar found: ${imgElement.src}`);
- return imgElement.src;
- }
- await new Promise(r => setTimeout(r, 1500));
- }
- ConsoleLogEnabled("Avatar not found, using fallback image.");
- return fallbackImage;
- }
-
- let homeContainer = await observeElement("#HomeContainer .section:first-child");
- ConsoleLogEnabled("Home container located.");
-
- let userNameElement = document.querySelector("#navigation.rbx-left-col > ul > li > a .font-header-2");
- ConsoleLogEnabled(`User name found: ${userNameElement ? userNameElement.innerText : "Unknown"}`);
-
- let user = {
- name: userNameElement ? `Hello, ${userNameElement.innerText}!` : "Hello, Roblox User!",
- avatar: await fetchAvatar("#navigation.rbx-left-col > ul > li > a img", window.Base64Images.image_place_holder)
- };
-
- ConsoleLogEnabled(`Final user details: Name - ${user.name}, Avatar - ${user.avatar}`);
-
- let headerContainer = document.createElement("div");
- headerContainer.classList.add("new-header");
- headerContainer.style.opacity = "0";
-
- let profileFrame = document.createElement("div");
- profileFrame.classList.add("profile-frame");
-
- let profileImage = document.createElement("img");
- profileImage.src = user.avatar;
- profileImage.classList.add("profile-img");
-
- profileFrame.appendChild(profileImage);
-
- let userDetails = document.createElement("div");
- userDetails.classList.add("user-details");
-
- let userName = document.createElement("h1");
- userName.classList.add("user-name");
- userName.textContent = user.name;
-
- userDetails.appendChild(userName);
-
- headerContainer.appendChild(profileFrame);
- headerContainer.appendChild(userDetails);
-
- ConsoleLogEnabled("Replacing old home container with new header.");
- homeContainer.replaceWith(headerContainer);
-
- let styleTag = document.createElement("style");
- styleTag.textContent = `
- .new-header {
- display: flex;
- align-items: center;
- margin-bottom: 30px;
- transition: opacity 1.5s ease-in-out;
- }
- .profile-frame {
- width: 150px;
- height: 150px;
- border-radius: 50%;
- overflow: hidden;
- border: 3px solid #121215;
- display: flex;
- justify-content: center;
- align-items: center;
- }
- .profile-img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- .user-details {
- margin-left: 20px;
- display: flex;
- align-items: center;
- }
- .user-name {
- font-size: 1.2em;
- font-weight: bold;
- color: white;
- }
- `;
- document.head.appendChild(styleTag);
- ConsoleLogEnabled("Style tag added.");
-
- setTimeout(() => {
- ConsoleLogEnabled("Fading in new header.");
- headerContainer.style.opacity = "1";
- }, 50);
- }
-
-
-
- let lastUrl = window.location.href.split("#")[0]; // Store only the base URL
-
- function observeURLChanges() {
- const observer = new MutationObserver(() => {
- let currentUrl = window.location.href.split("#")[0]; // Ignore fragment changes
-
- if (currentUrl !== lastUrl) {
- ConsoleLogEnabled(`URL changed from ${lastUrl} to ${currentUrl}`);
- lastUrl = currentUrl; // Update the stored URL
-
- // Re-run functions when going back to home
- if (currentUrl.includes("roblox.com/home")) {
- ConsoleLogEnabled("Detected return to home page. Reloading greeting.");
- showOldRobloxGreeting();
- }
- }
- });
-
- observer.observe(document.body, {
- childList: true,
- subtree: true
- });
- }
-
-
- function quicknavbutton() {
- if (localStorage.getItem('ROLOCATE_quicknav') === 'true') {
- const settingsRaw = localStorage.getItem('ROLOCATE_quicknav_settings');
- if (!settingsRaw) return;
-
- let settings;
- try {
- settings = JSON.parse(settingsRaw);
- } catch (e) {
- console.error('Failed to parse ROLOCATE_quicknav_settings:', e);
- return;
- }
-
- const sidebar = document.querySelector('.left-col-list');
- if (!sidebar) return;
-
- const premiumButton = sidebar.querySelector('.rbx-upgrade-now');
-
- const style = document.createElement('style');
- style.textContent = `
- .rolocate-icon-custom {
- display: inline-block;
- width: 24px;
- height: 24px;
- margin-left: 3px;
- background-image: url("${window.Base64Images.quicknav}");
- background-size: contain;
- background-repeat: no-repeat;
- }
-
- `;
- document.head.appendChild(style);
-
-
-
- settings.forEach(({
- name,
- link
- }) => {
- const li = document.createElement('li');
-
- const a = document.createElement('a');
- a.className = 'dynamic-overflow-container text-nav';
- a.href = link;
- a.target = '_self';
-
- const divIcon = document.createElement('div');
- const spanIcon = document.createElement('span');
- spanIcon.className = 'rolocate-icon-custom';
- divIcon.appendChild(spanIcon);
-
- const spanText = document.createElement('span');
- spanText.className = 'font-header-2 dynamic-ellipsis-item';
- spanText.title = name;
- spanText.textContent = name;
-
- a.appendChild(divIcon);
- a.appendChild(spanText);
- li.appendChild(a);
-
- if (premiumButton && premiumButton.parentElement === sidebar) {
- sidebar.insertBefore(li, premiumButton);
- } else {
- sidebar.appendChild(li);
- }
- });
- }
- }
-
-
- function validateManualMode() {
- // Check if in manual mode
- if (localStorage.getItem("ROLOCATE_prioritylocation") === "manual") {
- ConsoleLogEnabled("Manual mode detected");
-
- try {
- // Get stored coordinates
- const coords = JSON.parse(GM_getValue("ROLOCATE_coordinates", '{"lat":"","lng":""}'));
- ConsoleLogEnabled("Coordinates fetched:", coords);
-
- // If coordinates are empty, switch to automatic
- if (!coords.lat || !coords.lng) {
- localStorage.setItem("ROLOCATE_prioritylocation", "automatic");
- ConsoleLogEnabled("No coordinates set. Switched to automatic mode.");
- return true; // Indicates that a switch occurred
- }
- } catch (e) {
- ConsoleLogEnabled("Error checking coordinates:", e);
- // If there's an error reading coordinates, switch to automatic
- localStorage.setItem("ROLOCATE_prioritylocation", "automatic");
- ConsoleLogEnabled("Error encountered while fetching coordinates. Switched to automatic mode.");
- return true;
- }
- }
-
- ConsoleLogEnabled("No Errors detected.");
- return false; // No switch occurred
- }
-
-
-
-
- // Run the initial setup
- window.addEventListener("load", () => {
- loadBase64Library(() => {
- ConsoleLogEnabled("Loaded Base64Images. It is ready to use!");
- });
-
- AddSettingsButton(() => {
- ConsoleLogEnabled("Loaded Settings button!");
- });
-
- Update_Popup();
- initializeLocalStorage();
- removeAds();
- showOldRobloxGreeting();
- quicknavbutton();
- ConsoleLogEnabled("Loaded Settings!");
- validateManualMode();
-
- // Start observing URL changes
- observeURLChanges();
- });
-
-
-
-
- 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 occurred! 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" || localStorage.getItem("ROLOCATE_toggleserverhopbutton") === "true" || localStorage.getItem("ROLOCATE_togglerecentserverbutton") === "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
-
- *********************************************************************************************************************************************************************************************************************************************/
- //Testing
- //HandleRecentServersAddGames("126884695634066", "853e79a5-1a2b-4178-94bf-a242de1aecd6");
- //HandleRecentServersAddGames("126884695634066", "a08849f1-40e32-4b3215c-31231231a268-e948519caf39");
- //HandleRecentServersAddGames("126884695634066", "a08849f1-40e32-4b5c-31236541231a268-e948519caf39");
- //HandleRecentServersAddGames("126884695634066", "a08849f1-40e32-4b5c-31231287631a268-e948519caf39");
- //HandleRecentServersAddGames("126884695634066", "a08849f1-40e32-4b5c-31231231a268-87e948519caf39");
- //HandleRecentServersAddGames("126884695634066", "a08849f1-40e32-4b5c-31231231a268089-e948519caf39");
- //document.querySelector('.recent-servers-section')?.remove(); // remove old list
- //HandleRecentServers(); // re-render with updated order
-
- function InitRobloxLaunchHandler() {
- if (!window.location.href.startsWith('https://www.roblox.com/games/')) return;
- if (window._robloxJoinInterceptorInitialized) return;
- window._robloxJoinInterceptorInitialized = true;
-
- const originalJoin = Roblox.GameLauncher.joinGameInstance;
-
- Roblox.GameLauncher.joinGameInstance = function(gameId, serverId) {
- ConsoleLogEnabled(`Intercepted join: Game ID = ${gameId}, Server ID = ${serverId}`);
- HandleRecentServersAddGames(gameId, serverId);
- document.querySelector('.recent-servers-section')?.remove(); // remove old list
- HandleRecentServers(); // re-render with updated order
- return originalJoin.apply(this, arguments);
- };
- }
-
-
-
-
- function HandleRecentServersAddGames(gameId, serverId) {
- const storageKey = "ROLOCATE_recentservers_button";
- const stored = JSON.parse(localStorage.getItem(storageKey) || "{}");
- const key = `${gameId}_${serverId}`;
- stored[key] = Date.now(); // Always update timestamp
- localStorage.setItem(storageKey, JSON.stringify(stored));
- }
-
- function HandleRecentServersURL() {
- // Static-like variable to remember if we've already found an invalid URL
- if (HandleRecentServersURL.alreadyInvalid) {
- return; // Skip if previously marked as invalid
- }
-
- const url = window.location.href;
-
- // Regex pattern to match ROLOCATE_GAMEID and SERVERID from the hash
- const match = url.match(/ROLOCATE_GAMEID=(\d+)_SERVERID=([a-f0-9-]+)/i);
-
- if (match && match.length === 3) {
- const gameId = match[1];
- const serverId = match[2];
-
- // Call the handler with extracted values
- HandleRecentServersAddGames(gameId, serverId);
- InitRobloxLaunchHandler();
- } else {
- ConsoleLogEnabled("No gameId and serverId found in URL.");
- InitRobloxLaunchHandler();
- HandleRecentServersURL.alreadyInvalid = true; // Set internal flag
- }
- }
-
-
-
- function HandleRecentServers() {
- const serverList = document.querySelector('.server-list-options');
- if (!serverList || document.querySelector('.recent-servers-section')) return;
-
- const match = window.location.href.match(/\/games\/(\d+)\//);
- if (!match) return;
- const currentGameId = match[1];
-
- const allHeaders = document.querySelectorAll('.server-list-header');
- let friendsSectionHeader = null;
-
- allHeaders.forEach(header => {
- if (header.textContent.trim() === 'Servers My Friends Are In') {
- friendsSectionHeader = header.closest('.container-header');
- }
- });
-
- if (!friendsSectionHeader) return;
-
- // Custom premium dark theme CSS variables
- const theme = {
- bgDark: '#14161a',
- bgCard: '#1c1f25',
- bgCardHover: '#22262e',
- bgGradient: 'linear-gradient(145deg, #1e2228, #18191e)',
- bgGradientHover: 'linear-gradient(145deg, #23272f, #1c1f25)',
- accentPrimary: '#4d85ee',
- accentSecondary: '#3464c9',
- accentGradient: 'linear-gradient(to bottom, #4d85ee, #3464c9)',
- accentGradientHover: 'linear-gradient(to bottom, #5990ff, #3b6fdd)',
- textPrimary: '#e8ecf3',
- textSecondary: '#a0a8b8',
- textMuted: '#6c7484',
- borderLight: 'rgba(255, 255, 255, 0.06)',
- borderLightHover: 'rgba(255, 255, 255, 0.12)',
- shadow: '0 5px 15px rgba(0, 0, 0, 0.25)',
- shadowHover: '0 8px 25px rgba(0, 0, 0, 0.3)',
- dangerColor: '#ff5b5b',
- dangerColorHover: '#ff7575',
- dangerGradient: 'linear-gradient(to bottom, #ff5b5b, #e04444)',
- dangerGradientHover: 'linear-gradient(to bottom, #ff7575, #f55)'
- };
-
- const recentSection = document.createElement('div');
- recentSection.className = 'recent-servers-section premium-dark';
- recentSection.style.marginBottom = '24px';
-
- const headerContainer = document.createElement('div');
- headerContainer.className = 'container-header';
-
- const headerInner = document.createElement('div');
- headerInner.className = 'server-list-container-header';
- headerInner.style.padding = '0 4px';
-
- const headerTitle = document.createElement('h2');
- headerTitle.className = 'server-list-header';
- headerTitle.textContent = 'Recent Servers';
- headerTitle.style.cssText = `
- font-weight: 600;
- color: ${theme.textPrimary};
- letter-spacing: 0.5px;
- position: relative;
- display: inline-block;
- padding-bottom: 4px;
- `;
-
- // Add premium underline accent to header
- const headerAccent = document.createElement('span');
- headerAccent.style.cssText = `
- position: absolute;
- bottom: 0;
- left: 0;
- width: 40px;
- height: 2px;
- background: ${theme.accentGradient};
- border-radius: 2px;
- `;
- headerTitle.appendChild(headerAccent);
-
- headerInner.appendChild(headerTitle);
- headerContainer.appendChild(headerInner);
-
- const contentContainer = document.createElement('div');
- contentContainer.className = 'section-content-off empty-game-instances-container';
- contentContainer.style.padding = '8px 4px';
-
- const storageKey = "ROLOCATE_recentservers_button";
- let stored = JSON.parse(localStorage.getItem(storageKey) || "{}");
-
- // Auto-remove servers older than 3 days
- const currentTime = Date.now();
- const threeDaysInMs = 3 * 24 * 60 * 60 * 1000; // 3days in miliseconds
- let storageUpdated = false;
-
- Object.keys(stored).forEach(key => {
- const serverTime = stored[key];
- if (currentTime - serverTime > threeDaysInMs) {
- delete stored[key];
- storageUpdated = true;
- }
- });
-
- if (storageUpdated) {
- localStorage.setItem(storageKey, JSON.stringify(stored));
- }
-
- const keys = Object.keys(stored).filter(key => key.startsWith(`${currentGameId}_`));
- if (keys.length === 0) {
- const emptyMessage = document.createElement('div');
- emptyMessage.className = 'no-servers-message';
- emptyMessage.innerHTML = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="opacity: 0.7; margin-right: 10px;">
- <path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="${theme.accentPrimary}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
- <path d="M9.09 9C9.3251 8.33167 9.78915 7.76811 10.4 7.40913C11.0108 7.05016 11.7289 6.91894 12.4272 7.03871C13.1255 7.15849 13.7588 7.52152 14.2151 8.06353C14.6713 8.60553 14.9211 9.29152 14.92 10C14.92 12 11.92 13 11.92 13" stroke="${theme.accentPrimary}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
- <path d="M12 17H12.01" stroke="${theme.accentPrimary}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
- </svg>No Recent Servers Found`;
- emptyMessage.style.cssText = `
- color: ${theme.textSecondary};
- text-align: center;
- padding: 28px 0;
- font-size: 14px;
- letter-spacing: 0.3px;
- font-weight: 500;
- display: flex;
- align-items: center;
- justify-content: center;
- background: rgba(20, 22, 26, 0.4);
- backdrop-filter: blur(5px);
- border-radius: 12px;
- border: 1px solid rgba(77, 133, 238, 0.15);
- box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.2);
- `;
- contentContainer.appendChild(emptyMessage);
- } else {
- keys.sort((a, b) => stored[b] - stored[a]);
-
- // Create server cards wrapper
- const cardsWrapper = document.createElement('div');
- cardsWrapper.style.cssText = `
- display: flex;
- flex-direction: column;
- gap: 12px;
- margin: 2px 0;
- `;
-
- keys.forEach((key, index) => {
- const [gameId, serverId] = key.split("_");
- const timeStored = stored[key];
- const date = new Date(timeStored);
- const formattedTime = date.toLocaleString(undefined, {
- hour: '2-digit',
- minute: '2-digit',
- year: 'numeric',
- month: 'short',
- day: 'numeric'
- });
-
- const serverCard = document.createElement('div');
- serverCard.className = 'recent-server-card premium-dark';
- serverCard.dataset.serverKey = key;
- serverCard.style.cssText = `
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 16px 22px;
- height: 76px;
- border-radius: 14px;
- background: ${theme.bgGradient};
- box-shadow: ${theme.shadow};
- color: ${theme.textPrimary};
- font-family: 'Segoe UI', 'Helvetica Neue', sans-serif;
- font-size: 14px;
- box-sizing: border-box;
- width: 100%;
- position: relative;
- overflow: hidden;
- border: 1px solid ${theme.borderLight};
- transition: all 0.2s ease-out;
- `;
-
- // Add hover effect
- serverCard.onmouseover = function() {
- this.style.boxShadow = theme.shadowHover;
- this.style.transform = 'translateY(-2px)';
- this.style.borderColor = theme.borderLightHover;
- this.style.background = theme.bgGradientHover;
- };
-
- serverCard.onmouseout = function() {
- this.style.boxShadow = theme.shadow;
- this.style.transform = 'translateY(0)';
- this.style.borderColor = theme.borderLight;
- this.style.background = theme.bgGradient;
- };
-
- // Add glass effect overlay
- const glassOverlay = document.createElement('div');
- glassOverlay.style.cssText = `
- position: absolute;
- left: 0;
- top: 0;
- right: 0;
- height: 50%;
- background: linear-gradient(to bottom, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0));
- border-radius: 14px 14px 0 0;
- pointer-events: none;
- `;
- serverCard.appendChild(glassOverlay);
-
- // Server icon with glow
- const serverIconWrapper = document.createElement('div');
- serverIconWrapper.style.cssText = `
- position: absolute;
- left: 14px;
- display: flex;
- align-items: center;
- justify-content: center;
- width: 32px;
- height: 32px;
- `;
-
- const serverIcon = document.createElement('div');
- serverIcon.innerHTML = `
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M2 17L12 22L22 17" stroke="${theme.accentPrimary}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
- <path d="M2 12L12 17L22 12" stroke="${theme.accentPrimary}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
- <path d="M2 7L12 12L22 7L12 2L2 7Z" stroke="${theme.accentPrimary}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
- </svg>
- `;
- serverIconWrapper.appendChild(serverIcon);
-
- // Add subtle glow to the server icon
- const iconGlow = document.createElement('div');
- iconGlow.style.cssText = `
- position: absolute;
- width: 24px;
- height: 24px;
- border-radius: 50%;
- background: ${theme.accentPrimary};
- opacity: 0.15;
- filter: blur(8px);
- z-index: -1;
- `;
- serverIconWrapper.appendChild(iconGlow);
-
- const left = document.createElement('div');
- left.style.cssText = `
- display: flex;
- flex-direction: column;
- justify-content: center;
- margin-left: 12px;
- `;
-
- const lastPlayed = document.createElement('div');
- lastPlayed.textContent = `Last Played: ${formattedTime}`;
- lastPlayed.style.cssText = `
- font-weight: 600;
- font-size: 14px;
- color: ${theme.textPrimary};
- line-height: 1.3;
- letter-spacing: 0.3px;
- margin-left: 40px;
- text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
- `;
-
- const metaInfo = document.createElement('div');
- metaInfo.innerHTML = `<span style="color: ${theme.accentPrimary};">Game ID:</span> ${gameId} <span style="color: ${theme.textMuted};">•</span> <span style="color: ${theme.accentPrimary};">Server ID:</span> ${serverId}`;
- metaInfo.style.cssText = `
- font-size: 12px;
- color: ${theme.textSecondary};
- margin-top: 5px;
- opacity: 0.9;
- margin-left: 40px;
- `;
-
- left.appendChild(lastPlayed);
- left.appendChild(metaInfo);
- serverCard.appendChild(serverIconWrapper);
-
- const buttonGroup = document.createElement('div');
- buttonGroup.style.cssText = `
- display: flex;
- gap: 12px;
- align-items: center;
- z-index: 2;
- `;
-
- // Create the smaller remove button to be positioned on the left
- const removeButton = document.createElement('button');
- removeButton.innerHTML = `
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
- <path d="M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
- </svg>
- `;
- removeButton.className = 'btn-control-xs remove-button';
- removeButton.style.cssText = `
- background: ${theme.dangerGradient};
- color: white;
- border: none;
- padding: 6px;
- border-radius: 8px;
- font-size: 13px;
- font-weight: 600;
- cursor: pointer;
- transition: all 0.15s ease;
- letter-spacing: 0.4px;
- box-shadow: 0 2px 8px rgba(255, 91, 91, 0.3);
- display: flex;
- align-items: center;
- justify-content: center;
- width: 30px;
- height: 30px;
- `;
-
- // Add remove button hover effect
- removeButton.onmouseover = function() {
- this.style.background = theme.dangerGradientHover;
- this.style.boxShadow = '0 4px 10px rgba(255, 91, 91, 0.4)';
- this.style.transform = 'translateY(-1px)';
- };
-
- removeButton.onmouseout = function() {
- this.style.background = theme.dangerGradient;
- this.style.boxShadow = '0 2px 8px rgba(255, 91, 91, 0.3)';
- this.style.transform = 'translateY(0)';
- };
-
- // Add remove button functionality
- removeButton.addEventListener('click', function(e) {
- e.stopPropagation();
- const serverKey = this.closest('.recent-server-card').dataset.serverKey;
-
- // Animate removal
- serverCard.style.transition = 'all 0.3s ease-out';
- serverCard.style.opacity = '0';
- serverCard.style.height = '0';
- serverCard.style.margin = '0';
- serverCard.style.padding = '0';
-
- setTimeout(() => {
- serverCard.remove();
-
- // Update localStorage
- const storedData = JSON.parse(localStorage.getItem(storageKey) || "{}");
- delete storedData[serverKey];
- localStorage.setItem(storageKey, JSON.stringify(storedData));
-
- // If no servers left, show empty message
- if (document.querySelectorAll('.recent-server-card').length === 0) {
- const emptyMessage = document.createElement('div');
- emptyMessage.className = 'no-servers-message';
- emptyMessage.innerHTML = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="opacity: 0.7; margin-right: 10px;">
- <path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="${theme.accentPrimary}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
- <path d="M9.09 9C9.3251 8.33167 9.78915 7.76811 10.4 7.40913C11.0108 7.05016 11.7289 6.91894 12.4272 7.03871C13.1255 7.15849 13.7588 7.52152 14.2151 8.06353C14.6713 8.60553 14.9211 9.29152 14.92 10C14.92 12 11.92 13 11.92 13" stroke="${theme.accentPrimary}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
- <path d="M12 17H12.01" stroke="${theme.accentPrimary}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
- </svg>No Recent Servers Found`;
- emptyMessage.style.cssText = `
- color: ${theme.textSecondary};
- text-align: center;
- padding: 28px 0;
- font-size: 14px;
- letter-spacing: 0.3px;
- font-weight: 500;
- display: flex;
- align-items: center;
- justify-content: center;
- background: rgba(20, 22, 26, 0.4);
- backdrop-filter: blur(5px);
- border-radius: 12px;
- border: 1px solid rgba(77, 133, 238, 0.15);
- box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.2);
- `;
- cardsWrapper.appendChild(emptyMessage);
- }
- }, 300);
- });
-
- // Create a separator element
- const separator = document.createElement('div');
- separator.style.cssText = `
- height: 24px;
- width: 1px;
- background-color: rgba(255, 255, 255, 0.15);
- margin: 0 2px;
- `;
-
- const joinButton = document.createElement('button');
- joinButton.innerHTML = `
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-right: 6px;">
- <path d="M5 12H19" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
- <path d="M12 5L19 12L12 19" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
- </svg>
- Join
- `;
- joinButton.className = 'btn-control-xs join-button';
- joinButton.style.cssText = `
- background: ${theme.accentGradient};
- color: white;
- border: none;
- padding: 8px 18px;
- border-radius: 10px;
- font-size: 13px;
- font-weight: 600;
- cursor: pointer;
- transition: all 0.15s ease;
- letter-spacing: 0.4px;
- box-shadow: 0 2px 10px rgba(52, 100, 201, 0.3);
- display: flex;
- align-items: center;
- justify-content: center;
- `;
-
- // Add join button functionality
- joinButton.addEventListener('click', function() {
- try {
- Roblox.GameLauncher.joinGameInstance(gameId, serverId);
- } catch (error) {
- ConsoleLogEnabled("Error joining game:", error);
- }
- });
-
- // Add hover effect for join button
- joinButton.onmouseover = function() {
- this.style.background = theme.accentGradientHover;
- this.style.boxShadow = '0 4px 12px rgba(77, 133, 238, 0.4)';
- this.style.transform = 'translateY(-1px)';
- };
-
- joinButton.onmouseout = function() {
- this.style.background = theme.accentGradient;
- this.style.boxShadow = '0 2px 10px rgba(52, 100, 201, 0.3)';
- this.style.transform = 'translateY(0)';
- };
-
- const inviteButton = document.createElement('button');
- inviteButton.innerHTML = `
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-right: 6px;">
- <path d="M16 18L18 20L22 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
- <path d="M20 12V13.4C20 13.4 19.5 13 19 13C18.5 13 18 13.5 18 14C18 14.5 18.5 15 19 15C19.5 15 20 14.6 20 14.6V16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
- <path d="M4 20C4 17 7 17 8 17C9 17 13 17 13 17" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
- <path d="M9.5 10C10.8807 10 12 8.88071 12 7.5C12 6.11929 10.8807 5 9.5 5C8.11929 5 7 6.11929 7 7.5C7 8.88071 8.11929 10 9.5 10Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
- </svg>
- Invite
- `;
- inviteButton.className = 'btn-control-xs invite-button';
- inviteButton.style.cssText = `
- background: rgba(28, 31, 37, 0.6);
- color: ${theme.textPrimary};
- border: 1px solid rgba(255, 255, 255, 0.12);
- padding: 8px 18px;
- border-radius: 10px;
- font-size: 13px;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.15s ease;
- display: flex;
- align-items: center;
- justify-content: center;
- backdrop-filter: blur(4px);
- `;
-
- // Add invite button functionality
- inviteButton.addEventListener('click', function() {
- const inviteUrl = `https://oqarshi.github.io/Invite/?placeid=${gameId}&serverid=${serverId}`;
-
- // Copy to clipboard
- navigator.clipboard.writeText(inviteUrl).then(
- function() {
- // Show feedback that URL was copied
- const originalText = inviteButton.innerHTML;
- inviteButton.innerHTML = `
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-right: 6px;">
- <path d="M20 6L9 17L4 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
- </svg>
- Copied!
- `;
- ConsoleLogEnabled(`Invite link copied to clipboard`);
- notifications('Success! Invite link copied to clipboard!', 'success', '🎉', ' 2000');
- // Reset button after 2 seconds
- setTimeout(() => {
- inviteButton.innerHTML = originalText;
- }, 2000);
- },
- function(err) {
- ConsoleLogEnabled('Could not copy text: ', err);
- }
- );
- });
-
- // Add hover effect for invite button
- inviteButton.onmouseover = function() {
- this.style.background = 'rgba(35, 39, 46, 0.8)';
- this.style.borderColor = 'rgba(255, 255, 255, 0.18)';
- this.style.transform = 'translateY(-1px)';
- };
-
- inviteButton.onmouseout = function() {
- this.style.background = 'rgba(28, 31, 37, 0.6)';
- this.style.borderColor = 'rgba(255, 255, 255, 0.12)';
- this.style.transform = 'translateY(0)';
- };
-
- // MODIFIED: Now add buttons in the new order: Remove, Separator, Join, Invite
- buttonGroup.appendChild(removeButton);
- buttonGroup.appendChild(separator);
- buttonGroup.appendChild(joinButton);
- buttonGroup.appendChild(inviteButton);
-
- serverCard.appendChild(left);
- serverCard.appendChild(buttonGroup);
- cardsWrapper.appendChild(serverCard);
-
- // Add subtle line accent
- const lineAccent = document.createElement('div');
- lineAccent.style.cssText = `
- position: absolute;
- left: 0;
- top: 16px;
- bottom: 16px;
- width: 3px;
- background: ${theme.accentGradient};
- border-radius: 0 2px 2px 0;
- `;
- serverCard.appendChild(lineAccent);
-
- // Add subtle corner accent
- if (index === 0) {
- const cornerAccent = document.createElement('div');
- cornerAccent.style.cssText = `
- position: absolute;
- right: 0;
- top: 0;
- width: 40px;
- height: 40px;
- overflow: hidden;
- pointer-events: none;
- `;
-
- const cornerInner = document.createElement('div');
- cornerInner.style.cssText = `
- position: absolute;
- right: -20px;
- top: -20px;
- width: 40px;
- height: 40px;
- background: ${theme.accentPrimary};
- transform: rotate(45deg);
- opacity: 0.15;
- `;
-
- cornerAccent.appendChild(cornerInner);
- serverCard.appendChild(cornerAccent);
- }
- });
-
- contentContainer.appendChild(cardsWrapper);
- }
-
- recentSection.appendChild(headerContainer);
- recentSection.appendChild(contentContainer);
- friendsSectionHeader.parentNode.insertBefore(recentSection, friendsSectionHeader);
- }
-
-
-
-
- /*******************************************************
- 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. Sometimes user location cannot be detected."
- },
- {
- 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: false,
- disabled: true,
- disabledExplanation: "**Disabled**: Due to the recent Roblox update, this feature no longer works. :("
- }
- ];
-
- // 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.classList.add(data.disabled ? "disabled" : "enabled");
-
- // Create a wrapper for the button content that can have opacity applied
- const buttonContentWrapper = document.createElement('div');
- buttonContentWrapper.style.cssText = `
- width: 100%;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- ${data.disabled ? 'opacity: 0.7;' : ''}
- `;
-
- buttonContainer.style.cssText = `
- width: 190px;
- height: 30px;
- background-color: ${data.disabled ? '#2c2c2c' : '#393B3D'};
- margin: 5px;
- border-radius: 5px;
- padding: 3.5px;
- position: relative;
- cursor: ${data.disabled ? 'not-allowed' : '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;
- opacity: 1;
- z-index: 1001;
- `;
-
- // 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 "DISABLED" style if the button is disabled
- if (data.disabled) {
- // Show explanation tooltip (left side like experimental)
- const disabledTooltip = document.createElement('div');
- disabledTooltip.className = 'disabled-tooltip';
- disabledTooltip.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;
- opacity: 1;
- `;
- disabledTooltip.innerHTML = data.disabledExplanation.replace(/\*\*(.*?)\*\*/g, '<span style="font-weight: bold; color: #ff5555;">$1</span>');
-
- buttonContainer.appendChild(disabledTooltip);
-
- // Add disabled indicator
- const disabledIndicator = document.createElement('span');
- disabledIndicator.textContent = 'DISABLED';
- disabledIndicator.style.cssText = `
- margin-left: 8px;
- color: #ff5555;
- font-size: 10px;
- font-weight: bold;
- background-color: rgba(255, 85, 85, 0.1);
- padding: 1px 4px;
- border-radius: 3px;
- `;
- buttonText.appendChild(disabledIndicator);
-
- // Show on hover
- buttonContainer.addEventListener('mouseenter', () => {
- disabledTooltip.style.display = 'block';
- });
- buttonContainer.addEventListener('mouseleave', () => {
- disabledTooltip.style.display = 'none';
- });
- }
-
- // 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;
- opacity: 1;
- `;
-
- // 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);
- }
-
- // Append tooltip directly to button container so it won't inherit opacity
- buttonContainer.appendChild(tooltip);
-
- // Append button text to content wrapper
- buttonContentWrapper.appendChild(buttonText);
-
- // Append content wrapper to button container
- buttonContainer.appendChild(buttonContentWrapper);
-
- buttonContainer.addEventListener('mouseover', () => {
- tooltip.style.display = 'block';
- if (data.experimental) {
- experimentalTooltip.style.display = 'block';
- }
- // Only change background color on hover if the button is not disabled
- if (!data.disabled) {
- buttonContainer.style.backgroundColor = '#4A4C4E'; // Hover effect
- }
- });
- buttonContainer.addEventListener('mouseout', () => {
- tooltip.style.display = 'none';
- if (data.experimental) {
- experimentalTooltip.style.display = 'none';
- }
- // Only revert background color if the button is not disabled
- if (!data.disabled) {
- buttonContainer.style.backgroundColor = '#393B3D'; // Revert to original color
- }
- });
-
- buttonContainer.addEventListener('click', () => {
- // Prevent click functionality for disabled buttons
- if (data.disabled) {
- return;
- }
-
- 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("ROLOCATE_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(`ROLOCATE_recentServers_${gameId}`, JSON.stringify(validServers));
- });
-
- // Get the list of recently joined servers for the current game
- const storedServers = JSON.parse(localStorage.getItem(`ROLOCATE_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(`ROLOCATE_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') && localStorage.getItem("ROLOCATE_togglefilterserversbutton") === "true") {
- ConsoleLogEnabled("Added 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;
- }
- });
- }
- // new condition to trigger recent server logic
- if (localStorage.getItem("ROLOCATE_togglerecentserverbutton") === "true") {
- HandleRecentServers();
- HandleRecentServersURL();
- }
-
- if (playButton && !document.querySelector('.custom-play-button') && localStorage.getItem("ROLOCATE_toggleserverhopbutton") === "true") {
- ConsoleLogEnabled("Added Server Hop 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();
- });
- }
-
- const filterEnabled = localStorage.getItem("ROLOCATE_togglefilterserversbutton") === "true";
- const hopEnabled = localStorage.getItem("ROLOCATE_toggleserverhopbutton") === "true";
- const recentEnabled = localStorage.getItem("ROLOCATE_togglerecentserverbutton") === "true";
-
- const filterPresent = !filterEnabled || document.querySelector('.RL-filter-button');
- const hopPresent = !hopEnabled || document.querySelector('.custom-play-button');
- const recentPresent = !recentEnabled || document.querySelector('.recent-servers-section');
-
- if (filterPresent && hopPresent && recentPresent) {
- obs.disconnect();
- ConsoleLogEnabled("Disconnected Observer");
- }
- });
-
- 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);
- notifications('Error: Failed to fetch server data. Please try again later.', 'error', '⚠️', '5000');
- Loadingbar(false);
- 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 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;
- }
-
- const existingMessage = loadMoreButtonContainer.querySelector('.premium-message-container');
-
- // If message is "END", remove any existing message and exit
- if (message === "END") {
- if (existingMessage) {
- existingMessage.remove();
- ConsoleLogEnabled("Message container removed.");
- } else {
- ConsoleLogEnabled("No message container found to remove.");
- }
- return;
- }
-
- // Remove existing message if present before showing a new one
- if (existingMessage) {
- existingMessage.remove();
- ConsoleLogEnabled("Warning: An existing message was found and replaced.");
- }
-
- // Inject CSS only once
- if (!document.getElementById('premium-message-styles')) {
- const style = document.createElement('style');
- style.id = 'premium-message-styles';
- style.textContent = `
- .premium-message-container {
- margin-top: 20px;
- padding: 18px 26px;
- background: linear-gradient(145deg, #2b0000, #1a0000);
- border-radius: 14px;
- box-shadow: 0 6px 20px rgba(255, 0, 0, 0.2);
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
- font-size: 16px;
- color: #ffdddd;
- transition: all 0.3s ease-in-out, transform 0.3s ease, box-shadow 0.3s ease;
- opacity: 0;
- animation: fadeIn 0.6s ease forwards;
- border: 1px solid #440000;
- backdrop-filter: blur(6px);
- display: flex;
- align-items: center;
- gap: 16px;
- cursor: default;
- user-select: none;
- }
-
- .premium-message-container:hover {
- transform: scale(1.015);
- box-shadow: 0 8px 24px rgba(255, 0, 0, 0.25);
- background: linear-gradient(145deg, #330000, #220000);
- color: #ffe5e5;
- }
-
- .premium-message-logo {
- width: 28px;
- height: 28px;
- border-radius: 6px;
- object-fit: contain;
- box-shadow: 0 0 8px rgba(255, 0, 0, 0.2);
- background-color: #000;
- }
-
- .premium-message-text {
- flex: 1;
- text-align: left;
- font-weight: 500;
- letter-spacing: 0.3px;
- }
-
- @keyframes fadeIn {
- to { opacity: 1; }
- }
- `;
- document.head.appendChild(style);
- }
-
- // Create the message container
- const container = document.createElement('div');
- container.className = 'premium-message-container';
-
- // Create and insert the logo
- const logo = document.createElement('img');
- logo.className = 'premium-message-logo';
- logo.src = window.Base64Images.logo;
-
- // Create and insert the message text
- const messageText = document.createElement('div');
- messageText.className = 'premium-message-text';
- messageText.textContent = message;
-
- // Build the full component
- container.appendChild(logo);
- container.appendChild(messageText);
- loadMoreButtonContainer.appendChild(container);
-
- ConsoleLogEnabled("Message displayed successfully:", message);
- return container;
- }
-
-
-
-
- // 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';
-
- // Inject styles for dropdown icon
- const style = document.createElement('style');
- style.textContent = `
- .dropdown-wrapper {
- position: relative;
- display: inline-block;
- width: 100%;
- }
-
- .dropdown-wrapper select {
- width: 100%;
- padding-right: 30px;
- appearance: none;
- -webkit-appearance: none;
- -moz-appearance: none;
- }
-
- .dropdown-wrapper .dropdown-icon {
- position: absolute;
- right: 10px;
- top: 40%;
- transform: translateY(-50%);
- pointer-events: none;
- font-size: 12px;
- color: #fff;
-
- }
- `;
- document.head.appendChild(style);
-
- 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. It is rare though.</p>
- </div>
- </div>
- <label for="serverCount">Select Number of Servers:</label>
- <div class="dropdown-wrapper">
- <select id="serverCount">
- <option value="10">10 Servers</option>
- <option value="25" selected>25 Servers</option>
- <option value="50">50 Servers</option>
- <option value="100">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>
- <span class="dropdown-icon">▼</span>
- </div>
- <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');
-
- serverCountDropdown.addEventListener('change', () => {
- if (serverCountDropdown.value === 'custom') {
- customServerCountInput.style.display = 'block';
- } else {
- customServerCountInput.style.display = 'none';
- }
- });
-
- confirmButton.addEventListener('click', () => {
- let serverCount;
-
- if (serverCountDropdown.value === 'custom') {
- serverCount = parseInt(customServerCountInput.value);
- 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);
- }
-
- if (serverCount > 100) {
- notifications('Searching over 100 servers may take some time and you might get rate limited!', 'info', '⌛', '8000');
- }
-
- callback(serverCount);
- disableFilterButton(true);
- disableLoadMoreButton(true);
- hidePopup();
- Loadingbar(true);
- });
-
- closeButton.addEventListener('click', () => {
- hidePopup();
- });
-
- function hidePopup() {
- const overlay = document.querySelector('.overlay');
- const popup = document.querySelector('.filter-popup');
-
- overlay.classList.add('fade-out');
- popup.classList.add('fade-out');
-
- setTimeout(() => {
- overlay.remove();
- popup.remove();
- }, 300);
- }
- }
-
-
- // 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 or 2 seconds and 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: '28px',
- alignItems: 'center',
- padding: '32px 36px',
- background: 'linear-gradient(145deg, rgba(18,18,18,0.98) 0%, rgba(10,10,10,0.98) 100%)',
- borderRadius: '24px',
- boxShadow: '0 20px 40px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.05)',
- backdropFilter: 'blur(30px)',
- opacity: '0',
- transform: 'translateY(-40px) scale(0.95)',
- transition: 'all 0.9s cubic-bezier(0.19, 1, 0.22, 1)',
- position: 'relative',
- border: '1px solid rgba(255,255,255,0.08)',
- margin: '32px',
- fontFamily: "'Inter', system-ui, -apple-system, sans-serif",
- fontSize: '16px'
- });
-
- // Enhanced animated border glow with more effect
- const borderGlow = document.createElement('div');
- Object.assign(borderGlow.style, {
- position: 'absolute',
- inset: '-1px',
- borderRadius: '24px',
- pointerEvents: 'none',
- background: 'linear-gradient(45deg, rgba(255,30,30,0.2), rgba(255,80,80,0.05), rgba(255,30,30,0.15), rgba(255,80,80,0.05))',
- backgroundSize: '400% 400%',
- zIndex: '-1',
- animation: 'gradientFlow 15s ease infinite',
- opacity: '0.8',
- filter: 'blur(1px)'
- });
- filterContainer.appendChild(borderGlow);
-
- // add CSS for animations and scrollbar styling
- const style = document.createElement('style');
- style.textContent = `
- @keyframes gradientFlow {
- 0% { background-position: 0% 50%; }
- 50% { background-position: 100% 50%; }
- 100% { background-position: 0% 50%; }
- }
-
- @keyframes pulse {
- 0% { box-shadow: 0 0 0 0 rgba(255, 40, 40, 0.4); }
- 70% { box-shadow: 0 0 0 12px rgba(255, 40, 40, 0); }
- 100% { box-shadow: 0 0 0 0 rgba(255, 40, 40, 0); }
- }
-
- select::-webkit-scrollbar {
- width: 8px;
- }
- select::-webkit-scrollbar-track {
- background: rgba(20,20,20,0.6);
- border-radius: 8px;
- }
- select::-webkit-scrollbar-thumb {
- background: rgba(255,40,40,0.5);
- border-radius: 8px;
- border: 2px solid rgba(20,20,20,0.6);
- }
- select::-webkit-scrollbar-thumb:hover {
- background: rgba(255,40,40,0.7);
- }
-
- .logo-pulse {
- animation: pulse 2s infinite;
- }
- `;
- document.head.appendChild(style);
-
- // Enhanced logo with more sophisticated hover effects
- const logoWrapper = document.createElement('div');
- Object.assign(logoWrapper.style, {
- position: 'relative',
- marginRight: '24px',
- });
-
- const logo = document.createElement('img');
- logo.src = window.Base64Images.logo;
- Object.assign(logo.style, {
- width: '60px',
- height: '60px',
- borderRadius: '16px',
- transition: 'all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1)',
- filter: 'drop-shadow(0 8px 16px rgba(255,40,40,0.3))',
- border: '2px solid rgba(255,40,40,0.3)',
- });
-
- const logoGlow = document.createElement('div');
- Object.assign(logoGlow.style, {
- position: 'absolute',
- inset: '-4px',
- borderRadius: '20px',
- background: 'radial-gradient(circle at center, rgba(255,40,40,0.4) 0%, rgba(255,40,40,0) 70%)',
- opacity: '0',
- transition: 'opacity 0.5s ease',
- pointerEvents: 'none',
- zIndex: '-1',
- });
-
- logo.addEventListener('mouseover', () => {
- logo.style.transform = 'rotate(-8deg) scale(1.15)';
- logo.style.filter = 'drop-shadow(0 12px 24px rgba(255,40,40,0.5))';
- logo.style.border = '2px solid rgba(255,40,40,0.6)';
- logoGlow.style.opacity = '1';
- logo.classList.add('logo-pulse');
- });
-
- logo.addEventListener('mouseout', () => {
- logo.style.transform = 'rotate(0) scale(1)';
- logo.style.filter = 'drop-shadow(0 8px 16px rgba(255,40,40,0.3))';
- logo.style.border = '2px solid rgba(255,40,40,0.3)';
- logoGlow.style.opacity = '0';
- logo.classList.remove('logo-pulse');
- });
-
- logoWrapper.appendChild(logoGlow);
- logoWrapper.appendChild(logo);
- filterContainer.appendChild(logoWrapper);
-
- // Function to create a premium dropdown with enhanced styling
- const createDropdown = (id, placeholder) => {
- const wrapper = document.createElement('div');
- Object.assign(wrapper.style, {
- position: 'relative',
- minWidth: '240px',
- flex: '1'
- });
-
- // Label for the dropdown with subtle animation
- const label = document.createElement('div');
- label.textContent = placeholder.replace('All ', '');
- Object.assign(label.style, {
- color: 'rgba(255,255,255,0.7)',
- marginBottom: '10px',
- fontSize: '14px',
- fontWeight: '500',
- letterSpacing: '0.5px',
- transform: 'translateX(2px)',
- transition: 'color 0.3s ease',
- textTransform: 'uppercase'
- });
- wrapper.appendChild(label);
-
- const dropdown = document.createElement('select');
- dropdown.id = id;
- dropdown.innerHTML = `<option value="">${placeholder}</option>`;
- Object.assign(dropdown.style, {
- width: '100%',
- padding: '18px 56px 18px 24px',
- fontSize: '16px',
- fontWeight: '500',
- background: 'linear-gradient(145deg, rgba(35,35,35,0.9), rgba(20,20,20,0.9))',
- color: '#FF2828',
- border: '1px solid rgba(255,255,255,0.1)',
- borderRadius: '14px',
- boxShadow: '0 8px 16px rgba(0,0,0,0.25), inset 0 1px 0 rgba(255,255,255,0.05)',
- appearance: 'none',
- cursor: 'pointer',
- transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
- opacity: '0',
- transform: 'translateY(-20px)',
- letterSpacing: '0.3px'
- });
-
- // Enhanced chevron icon with SVG
- const chevron = document.createElement('div');
- chevron.innerHTML = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>`;
- Object.assign(chevron.style, {
- position: 'absolute',
- right: '22px',
- bottom: '18px',
- transform: 'translateY(0)',
- pointerEvents: 'none',
- transition: 'transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1)',
- color: 'rgba(255,40,40,0.9)',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center'
- });
-
- // Refined dropdown interactions
- dropdown.addEventListener('mouseover', () => {
- dropdown.style.background = 'linear-gradient(145deg, rgba(50,50,50,0.9), rgba(35,35,35,0.9))';
- dropdown.style.boxShadow = '0 12px 24px rgba(0,0,0,0.35), inset 0 1px 0 rgba(255,255,255,0.08)';
- dropdown.style.borderColor = 'rgba(255,40,40,0.3)';
- label.style.color = 'rgba(255,40,40,0.9)';
- chevron.style.transform = 'translateY(0) rotate(180deg)';
- });
-
- dropdown.addEventListener('mouseout', () => {
- if (document.activeElement !== dropdown) {
- dropdown.style.background = 'linear-gradient(145deg, rgba(35,35,35,0.9), rgba(20,20,20,0.9))';
- dropdown.style.boxShadow = '0 8px 16px rgba(0,0,0,0.25), inset 0 1px 0 rgba(255,255,255,0.05)';
- dropdown.style.borderColor = 'rgba(255,255,255,0.1)';
- label.style.color = 'rgba(255,255,255,0.7)';
- chevron.style.transform = 'translateY(0) rotate(0deg)';
- }
- });
-
- dropdown.addEventListener('focus', () => {
- dropdown.style.outline = 'none';
- dropdown.style.borderColor = 'rgba(255,40,40,0.5)';
- dropdown.style.boxShadow = '0 12px 24px rgba(0,0,0,0.35), 0 0 0 3px rgba(255,40,40,0.2), inset 0 1px 0 rgba(255,255,255,0.08)';
- label.style.color = 'rgba(255,40,40,0.9)';
- chevron.style.transform = 'translateY(0) rotate(180deg)';
- });
-
- dropdown.addEventListener('blur', () => {
- dropdown.style.borderColor = 'rgba(255,255,255,0.1)';
- dropdown.style.boxShadow = '0 8px 16px rgba(0,0,0,0.25), inset 0 1px 0 rgba(255,255,255,0.05)';
- label.style.color = 'rgba(255,255,255,0.7)';
- chevron.style.transform = 'translateY(0) rotate(0deg)';
- });
-
- dropdown.addEventListener('change', () => {
- dropdown.style.transform = 'scale(0.97)';
- setTimeout(() => dropdown.style.transform = 'scale(1)', 150);
-
- // Add a subtle flash effect on change
- const flash = document.createElement('div');
- Object.assign(flash.style, {
- position: 'absolute',
- inset: '0',
- borderRadius: '14px',
- backgroundColor: 'rgba(255,40,40,0.15)',
- pointerEvents: 'none',
- opacity: '0',
- transition: 'opacity 0.3s ease'
- });
- wrapper.appendChild(flash);
- flash.style.opacity = '1';
- setTimeout(() => {
- flash.style.opacity = '0';
- setTimeout(() => wrapper.removeChild(flash), 300);
- }, 50);
- });
-
- // Enhanced fade-in animation with staggered timing
- setTimeout(() => {
- dropdown.style.opacity = '1';
- dropdown.style.transform = 'translateY(0)';
- }, 500);
-
- wrapper.appendChild(dropdown);
- wrapper.appendChild(chevron);
- return wrapper;
- };
-
- // Create enhanced 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;
- });
-
- // Sort countries alphabetically
- const sortedCountries = Object.keys(countryCounts).sort();
-
- sortedCountries.forEach(country => {
- const option = document.createElement('option');
- option.value = country;
- option.textContent = `${country} (${countryCounts[country]})`;
- countryDropdown.querySelector('select').appendChild(option);
- });
-
- // Add a subtle visual indicator
- const separator = document.createElement('div');
- Object.assign(separator.style, {
- height: '65px',
- width: '1px',
- background: 'linear-gradient(to bottom, rgba(255,255,255,0), rgba(255,40,40,0.3), rgba(255,255,255,0))',
- margin: '0 4px'
- });
-
- 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;
- });
-
- // Sort cities alphabetically
- const sortedCities = Object.keys(cityCounts).sort();
-
- sortedCities.forEach(city => {
- const option = document.createElement('option');
- option.value = city;
- option.textContent = `${city} (${cityCounts[city]})`;
- cityDropdown.querySelector('select').appendChild(option);
- });
-
- // Enhanced animation for city dropdown update
- cityDropdown.querySelector('select').style.opacity = '0.3';
- cityDropdown.querySelector('select').style.transform = 'translateY(-10px)';
- setTimeout(() => {
- cityDropdown.querySelector('select').style.opacity = '1';
- cityDropdown.querySelector('select').style.transform = 'translateY(0)';
- }, 150);
-
- // Visual indicator that cities were updated
- cityDropdown.style.position = 'relative';
- const updateFlash = document.createElement('div');
- Object.assign(updateFlash.style, {
- position: 'absolute',
- inset: '30px 0 0 0',
- borderRadius: '14px',
- background: 'radial-gradient(circle at center, rgba(255,40,40,0.2) 0%, rgba(255,40,40,0) 70%)',
- pointerEvents: 'none',
- opacity: '1',
- transition: 'opacity 0.8s ease'
- });
- cityDropdown.appendChild(updateFlash);
- setTimeout(() => {
- updateFlash.style.opacity = '0';
- setTimeout(() => cityDropdown.removeChild(updateFlash), 800);
- }, 100);
- }
- });
-
- // Append dropdowns to container with separator
- filterContainer.appendChild(countryDropdown);
- filterContainer.appendChild(separator);
- filterContainer.appendChild(cityDropdown);
-
- // Enhanced fade-in container animation
- setTimeout(() => {
- filterContainer.style.opacity = '1';
- filterContainer.style.transform = 'translateY(0) scale(1)';
- }, 300);
-
- 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); // why does the js beautifer make this code here look trash
- 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');
- Loadingbar(false);
- return;
- }
-
- const messageElement = showMessage("Just a moment — to detect your location accurately, please stay on this page...");
- const premium_message = messageElement.querySelector('.premium-message-text');
-
- try {
- // Retrieve user's location for distance calculations
- const userLocation = await getUserLocation(); // this should be useless because of the new manual feature.
- 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;
-
- if (premium_message) {
- premium_message.textContent = `Filtering servers... Please stay on this page to ensure a faster and more accurate search. ${totalServers} servers found, 0 loaded so far.`;
- }
- 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') {
- if (premium_message) {
- premium_message.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;
- return;
- } else if (error === 'subplace_join_restriction') {
- if (premium_message) {
- premium_message.textContent = "Error: 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
- });
- if (premium_message) {
- premium_message.textContent = `Filtering servers, please do not leave this page...\n${totalServers} servers found, ${i + 1} server locations found`;
- }
- }
-
- if (serverDetails.length === 0) {
- showMessage("END");
- notifications('Error: No servers found. Please try again with an increase in the number of servers to search for.', 'error', '⚠️', '15000');
- 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');
- showMessage("END");
- 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 = "0px"; // Set gap to 0px to remove the gap between grid items
-
- 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("", "");
- } catch (error) {
- ConsoleLogEnabled("Error rebuilding server list:", error);
- notifications('Filtering Error: Failed to obtain permission to send API requests to the Roblox API. Please allow the script to enable request sending.', 'error', '⚠️ ', '8000');
- if (premium_message) {
- premium_message.textContent = "Filtering Error: Failed to obtain permission to send API requests to the Roblox API. Please allow the script to enable request sending.";
- }
- 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') // old stuff not needed anymore
- }
- }
-
-
-
-
- // 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
- }
-
-
-
- // Fallback location resolver with timezone-based estimation
- function resolveOfflineFallbackLocation(resolve) {
- ConsoleLogEnabled("Attempting offline location estimation...");
-
- let guessedLocation = null;
- let closestLocation = null;
- let closestDistance = Infinity;
-
- const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || "";
- const timezoneMap = {
- "America/Los_Angeles": {
- lat: 34.0522,
- lon: -118.2437
- },
- "America/Denver": {
- lat: 39.7392,
- lon: -104.9903
- },
- "America/Chicago": {
- lat: 41.8781,
- lon: -87.6298
- },
- "America/New_York": {
- lat: 40.7128,
- lon: -74.006
- },
- "Europe/London": {
- lat: 51.5074,
- lon: -0.1278
- },
- "Europe/Berlin": {
- lat: 52.52,
- lon: 13.405
- },
- "Europe/Paris": {
- lat: 48.8566,
- lon: 2.3522
- },
- "Asia/Tokyo": {
- lat: 35.6895,
- lon: 139.6917
- },
- "Asia/Kolkata": {
- lat: 28.6139,
- lon: 77.209
- },
- "Australia/Sydney": {
- lat: -33.8688,
- lon: 151.2093
- },
- "America/Argentina/Buenos_Aires": {
- lat: -34.6037,
- lon: -58.3816
- },
- "Africa/Nairobi": {
- lat: -1.286389,
- lon: 36.817223
- },
- "Asia/Singapore": {
- lat: 1.3521,
- lon: 103.8198
- },
- "America/Toronto": {
- lat: 43.65107,
- lon: -79.347015
- },
- "Europe/Moscow": {
- lat: 55.7558,
- lon: 37.6173
- },
- "Europe/Madrid": {
- lat: 40.4168,
- lon: -3.7038
- },
- "Asia/Shanghai": {
- lat: 31.2304,
- lon: 121.4737
- },
- "Africa/Cairo": {
- lat: 30.0444,
- lon: 31.2357
- },
- "Africa/Johannesburg": {
- lat: -26.2041,
- lon: 28.0473
- },
- "Europe/Amsterdam": {
- lat: 52.3676,
- lon: 4.9041
- },
- "Asia/Manila": {
- lat: 14.5995,
- lon: 120.9842
- },
- "Asia/Seoul": {
- lat: 37.5665,
- lon: 126.978
- }
- };
-
-
- // If user's timezone is available in the map
- if (timezoneMap[timezone]) {
- guessedLocation = timezoneMap[timezone];
- ConsoleLogEnabled("User's timezone found:", timezone);
- }
-
- // If the timezone is not found, find the closest match
- if (!guessedLocation) {
- ConsoleLogEnabled("User's timezone not found. Finding closest match...");
- Object.keys(timezoneMap).forEach((tz) => {
- const location = timezoneMap[tz];
- const distance = calculateDistance(location.lat, location.lon, 0, 0); // Distance from the equator (0,0)
- if (distance < closestDistance) {
- closestDistance = distance;
- closestLocation = location;
- }
- });
- guessedLocation = closestLocation;
- }
-
- // If we found a location, return it, otherwise default to New York
- if (guessedLocation) {
- notifications("Estimated location based on timezone. Please allow location access to see what servers are closest to you or change to manual in settings.", "info", "🕒", "6000");
- resolve({
- latitude: guessedLocation.lat,
- longitude: guessedLocation.lon
- });
- } else {
- notifications("Could not estimate location. Fatal error, please report on Greasyfork. Using default (New York).", "error", "⚠️", "6000");
- resolve({
- latitude: 40.7128,
- longitude: -74.0060
- }); // Default to NYC
- }
- }
-
-
-
- function getUserLocation() {
- return new Promise((resolve, reject) => {
- // Check priority location setting
- const priorityLocation = localStorage.getItem("ROLOCATE_prioritylocation") || "automatic";
-
- // If in manual mode, use stored coordinates
- if (priorityLocation === "manual") {
- try {
- const coords = JSON.parse(GM_getValue("ROLOCATE_coordinates", '{"lat":"","lng":""}'));
- if (coords.lat && coords.lng) {
- ConsoleLogEnabled("Using manual location from storage");
- notifications("We successfully detected your location.", "success", "🌎", "2000");
- return resolve({
- latitude: parseFloat(coords.lat), // Changed to match automatic mode
- longitude: parseFloat(coords.lng), // Changed to match automatic mode
- source: "manual",
- accuracy: 0 // Manual coordinates have no accuracy metric
- });
- } else {
- ConsoleLogEnabled("Manual mode selected but no coordinates set - falling back to automatic behavior");
- notifications("Manual mode selected but no coordinates set. Fatal error: Report on greasyfork. Using Automatic Mode.", "error", "", "2000");
- // Fall through to automatic behavior
- }
- } catch (e) {
- ConsoleLogEnabled("Error reading manual coordinates:", e);
- notifications("Error reading manual coordinates. Fatal error: Report on greasyfork. Using Automatic Mode.", "error", "", "2000");
- // Fall through to automatic behavior
- }
- }
-
- // Automatic mode behavior
- if (!navigator.geolocation) {
- ConsoleLogEnabled("Geolocation not supported.");
- notifications("Geolocation is not supported by your browser.", "error", "⚠️", "15000");
- return resolveOfflineFallbackLocation(resolve);
- }
-
- navigator.geolocation.getCurrentPosition(
- (position) => resolveSuccess(position, resolve),
- async (error) => {
- ConsoleLogEnabled("Geolocation error:", error);
-
- // Attempt to inspect geolocation permission state
- try {
- if (navigator.permissions && navigator.permissions.query) {
- const permissionStatus = await navigator.permissions.query({
- name: "geolocation"
- });
- ConsoleLogEnabled("Geolocation permission status:", permissionStatus.state);
-
- if (permissionStatus.state === "denied") {
- return resolveOfflineFallbackLocation(resolve);
- }
- }
- } catch (permError) {
- ConsoleLogEnabled("Permission check failed:", permError);
- }
-
- // Retry geolocation once with a slightly relaxed setting
- navigator.geolocation.getCurrentPosition(
- (position) => resolveSuccess(position, resolve),
- (retryError) => {
- ConsoleLogEnabled("Second geolocation attempt failed:", retryError);
- notifications("Could not get your location. Using fallback.", "error", "⚠️", "15000");
- resolveOfflineFallbackLocation(resolve);
- }, {
- maximumAge: 5000,
- timeout: 10000,
- }
- );
- }, {
- timeout: 10000,
- maximumAge: 0,
- }
- );
- });
- }
-
-
- // Success Handler (unchanged)
- function resolveSuccess(position, resolve) {
- notifications("We successfully detected your location.", "success", "🌎", "2000");
- disableLoadMoreButton(true);
- disableFilterButton(true);
- Loadingbar(true);
- resolve({
- latitude: position.coords.latitude,
- longitude: position.coords.longitude,
- source: "geolocation",
- accuracy: position.coords.accuracy
- });
- }
-
-
-
- /*********************************************************************************************************************************************************************************************************************************************
- 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');
- Loadingbar(false);
- 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.
-
- *********************************************************************************************************************************************************************************************************************************************/
-
- // Enhanced popup styling for the username input dialog
- function find_user_server_tab() {
- // Create the overlay (backdrop) with improved animation
- 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.6);
- z-index: 9999;
- opacity: 0;
- transition: opacity 0.4s ease;
- backdrop-filter: blur(3px);
- `;
- document.body.appendChild(overlay);
-
- // Create the popup container with improved styling
- const popup = document.createElement('div');
- popup.className = 'player-count-popup';
- popup.style.cssText = `
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%) scale(0.95);
- background-color: rgb(30, 32, 34);
- padding: 30px 25px;
- border-radius: 12px;
- z-index: 10000;
- box-shadow: 0 10px 25px rgba(0, 0, 0, 0.8);
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 20px;
- width: 340px;
- opacity: 0;
- transition: opacity 0.4s ease, transform 0.4s ease;
- border: 1px solid rgba(255, 255, 255, 0.1);
- `;
-
- // Improved close button with better hover effects
- const closeButton = document.createElement('button');
- closeButton.innerHTML = '×';
- closeButton.style.cssText = `
- position: absolute;
- top: 12px;
- right: 12px;
- background: transparent;
- border: none;
- color: #999999;
- font-size: 26px;
- cursor: pointer;
- width: 36px;
- height: 36px;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s ease;
- `;
- closeButton.addEventListener('mouseenter', () => {
- closeButton.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
- closeButton.style.color = '#ff4444';
- closeButton.style.transform = 'rotate(90deg)';
- });
- closeButton.addEventListener('mouseleave', () => {
- closeButton.style.backgroundColor = 'transparent';
- closeButton.style.color = '#999999';
- closeButton.style.transform = 'rotate(0deg)';
- });
-
- // Enhanced title with icon
- const titleContainer = document.createElement('div');
- titleContainer.style.cssText = `
- display: flex;
- align-items: center;
- gap: 10px;
- margin-bottom: 5px;
- `;
-
- const titleIcon = document.createElement('span');
- titleIcon.innerHTML = '🔍';
- titleIcon.style.cssText = `font-size: 22px;`;
-
- const title = document.createElement('h3');
- title.textContent = 'Locate User';
- title.style.cssText = `
- color: white;
- margin: 0;
- font-size: 20px;
- font-weight: 600;
- `;
-
- titleContainer.appendChild(titleIcon);
- titleContainer.appendChild(title);
- popup.appendChild(titleContainer);
-
- // Add subtitle with instructions
- const subtitle = document.createElement('p');
- subtitle.textContent = 'Enter the exact Roblox username to find their server';
- subtitle.style.cssText = `
- color: #aaaaaa;
- margin: -10px 0 0 0;
- font-size: 14px;
- text-align: center;
- `;
- popup.appendChild(subtitle);
-
- // Improved input box with focus styling
- const usernameInput = document.createElement('input');
- usernameInput.type = 'text';
- usernameInput.placeholder = 'Username';
- usernameInput.style.cssText = `
- width: 100%;
- padding: 12px 15px;
- font-size: 16px;
- border: 2px solid #444;
- border-radius: 8px;
- background-color: #252729;
- color: white;
- outline: none;
- transition: all 0.3s ease;
- box-sizing: border-box;
- `;
- usernameInput.addEventListener('focus', () => {
- usernameInput.style.borderColor = '#b71c1c';
- usernameInput.style.boxShadow = '0 0 5px rgba(211, 47, 47, 0.5)';
- });
- usernameInput.addEventListener('blur', () => {
- usernameInput.style.borderColor = '#444';
- usernameInput.style.boxShadow = 'none';
- });
- popup.appendChild(usernameInput);
-
- // Enhanced confirm button with better styling and effects
- const confirmButton = document.createElement('button');
- confirmButton.textContent = 'Search';
- confirmButton.style.cssText = `
- padding: 12px 0;
- width: 100%;
- font-size: 16px;
- font-weight: 600;
- background-color: #b71c1c;
- color: white;
- border: none;
- border-radius: 8px;
- cursor: pointer;
- transition: all 0.3s ease;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- `;
-
- // Add icon to button
- const searchIcon = document.createElement('span');
- searchIcon.textContent = '🔍';
- searchIcon.style.fontSize = '18px';
- confirmButton.prepend(searchIcon);
-
- confirmButton.addEventListener('mouseenter', () => {
- confirmButton.style.backgroundColor = '#d32f2f';
- confirmButton.style.transform = 'translateY(-2px)';
- confirmButton.style.boxShadow = '0 5px 15px rgba(211, 47, 47, 0.5)';
- });
- confirmButton.addEventListener('mouseleave', () => {
- confirmButton.style.backgroundColor = '#b71c1c';
- confirmButton.style.transform = 'translateY(0)';
- confirmButton.style.boxShadow = 'none';
- });
- confirmButton.addEventListener('mousedown', () => {
- confirmButton.style.transform = 'translateY(1px)';
- });
- confirmButton.addEventListener('mouseup', () => {
- confirmButton.style.transform = 'translateY(-2px)';
- });
-
- // Handle keyboard enter for better UX
- usernameInput.addEventListener('keydown', (e) => {
- if (e.key === 'Enter') {
- confirmButton.click();
- }
- });
-
- // Handle confirm button click with enhanced validation feedback
- confirmButton.addEventListener('click', () => {
- const username = usernameInput.value.trim();
- if (username) {
- if (username.length >= 3 && username.length <= 20) {
- FindPlayerGameServer(username);
- notifications("Searching for the user's server...", "info", "🔍", "5000");
- fadeOutAndRemove_7th(popup, overlay);
- } else {
- usernameInput.style.borderColor = '#ff4444';
- usernameInput.style.boxShadow = '0 0 5px rgba(255, 68, 68, 0.5)';
- notifications('Username must be between 3 and 20 characters', 'error', '⚠️', '3000');
-
- // Reset input styling after delay
- setTimeout(() => {
- if (document.body.contains(usernameInput)) {
- usernameInput.style.borderColor = '#444';
- usernameInput.style.boxShadow = 'none';
- }
- }, 2000);
- }
- } else {
- usernameInput.style.borderColor = '#ff4444';
- usernameInput.style.boxShadow = '0 0 5px rgba(255, 68, 68, 0.5)';
- notifications('Please enter a username', 'error', '⚠️', '2500');
- }
- });
-
- // Append elements to popup
- popup.appendChild(confirmButton);
- popup.appendChild(closeButton);
-
- // Append popup to body
- document.body.appendChild(popup);
-
- // Focus the input field automatically
- setTimeout(() => {
- usernameInput.focus();
- }, 300);
-
- // Fade in the overlay and popup with improved animation
- setTimeout(() => {
- overlay.style.opacity = '1';
- popup.style.opacity = '1';
- popup.style.transform = 'translate(-50%, -50%) scale(1)';
- }, 10);
-
- // Keep the same fadeOutAndRemove function name for compatibility
- 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);
- }
-
- // 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);
- });
- }
-
-
- // Add this helper function somewhere inside or above your main function
- const checkIfUserOnline = async (userId) => {
- try {
- const response = await fetch("https://presence.roblox.com/v1/presence/users", {
- method: "POST",
- headers: {
- "Content-Type": "application/json"
- },
- body: JSON.stringify({
- userIds: [parseInt(userId)]
- })
- });
- const data = await response.json();
- const presenceType = data.userPresences?.[0]?.userPresenceType ?? 0;
- return presenceType !== 0; // true if online or in-game/studio
- } catch (error) {
- ConsoleLogEnabled("Presence check failed:", error);
- return false; // fail-safe: treat as offline
- }
- };
-
-
- 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];
- const isOnline = await checkIfUserOnline(userId);
- if (!isOnline) {
- notifications("User is currently offline.", "error", "📴", "4000");
- Loadingbar(false);
- disableFilterButton(false);
- fadeOutAndRemove_7th_progress(progressPopup.popup, progressPopup.overlay);
- return `User "${playerName}" is offline.`;
- }
- ConsoleLogEnabled(`User is online — proceeding with server scan.`);
-
- 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 2.5 seconds before next server batch...");
- await wait(2500);
- }
- } else {
- ConsoleLogEnabled("Server fetch failed. Retrying in 10 seconds...");
- notifications("Got rate limited. Waiting 10 seconds...", "info", "❗", "10000")
- await wait(10000);
- }
- }
-
- 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");
- notifications("If the user is playing, try again in a minute. The Roblox API may not be updated yet.", "info", "🔄", "20000");
- }, 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)`;
- }
- }
-
- // Enhanced progress popup for search visualization
- function showSearchProgressPopup(avatarThumbnail) {
- // Create the overlay with blur effect
- 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.6);
- z-index: 9999;
- opacity: 0;
- transition: opacity 0.4s ease;
- backdrop-filter: blur(3px);
- `;
- document.body.appendChild(overlay);
-
- // Create the popup container with improved styling
- const popup = document.createElement('div');
- popup.className = 'search-progress-popup';
- popup.style.cssText = `
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%) scale(0.95);
- background-color: rgb(30, 32, 34);
- padding: 30px;
- border-radius: 12px;
- z-index: 10000;
- box-shadow: 0 10px 25px rgba(0, 0, 0, 0.8);
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 20px;
- width: 340px;
- opacity: 0;
- transition: opacity 0.4s ease, transform 0.4s ease;
- border: 1px solid rgba(255, 255, 255, 0.1);
- `;
-
- // Improved title with visual hierarchy
- const title = document.createElement('h3');
- title.textContent = 'Searching for Player...';
- title.style.cssText = `
- color: white;
- margin: 0 0 10px 0;
- font-size: 20px;
- font-weight: 600;
- text-align: center;
- `;
- popup.appendChild(title);
-
- // Add animated loader element
- const loaderContainer = document.createElement('div');
- loaderContainer.style.cssText = `
- position: relative;
- width: 120px;
- height: 120px;
- display: flex;
- justify-content: center;
- align-items: center;
- `;
-
- // Create ripple effect around avatar
- const ripple = document.createElement('div');
- ripple.style.cssText = `
- position: absolute;
- width: 110px;
- height: 110px;
- border-radius: 50%;
- border: 3px solid #b71c1c;
- animation: ripple 1.5s ease-out infinite;
- opacity: 0;
- `;
-
- // Add the keyframe animation
- const style = document.createElement('style');
- style.textContent = `
- @keyframes ripple {
- 0% {
- transform: scale(0.8);
- opacity: 0.8;
- }
- 100% {
- transform: scale(1.2);
- opacity: 0;
- }
- }
- @keyframes pulse {
- 0% { box-shadow: 0 0 0 0 rgba(255, 68, 68, 0.5); }
- 70% { box-shadow: 0 0 0 10px rgba(58, 122, 255, 0); }
- 100% { box-shadow: 0 0 0 0 rgba(58, 122, 255, 0); }
- }
- `;
- document.head.appendChild(style);
-
- // Enhanced player thumbnail display
- const thumbnail = document.createElement('img');
- thumbnail.src = avatarThumbnail;
- thumbnail.style.cssText = `
- width: 100px;
- height: 100px;
- border-radius: 50%;
- object-fit: cover;
- border: 4px solid #b71c1c;
- box-shadow: 0 0 20px rgba(255, 68, 68, 0.5);
- animation: pulse 2s infinite;
- z-index: 2;
- `;
-
- loaderContainer.appendChild(ripple);
- loaderContainer.appendChild(thumbnail);
- popup.appendChild(loaderContainer);
-
- // Status container
- const statusContainer = document.createElement('div');
- statusContainer.style.cssText = `
- background-color: rgba(0, 0, 0, 0.2);
- border-radius: 8px;
- padding: 15px;
- width: 100%;
- box-sizing: border-box;
- `;
-
- // Improved progress indicators with icons
- const serversSearchedContainer = document.createElement('div');
- serversSearchedContainer.style.cssText = `
- display: flex;
- align-items: center;
- gap: 10px;
- margin-bottom: 10px;
- `;
-
- const serverIcon = document.createElement('span');
- serverIcon.textContent = '🔍';
- serverIcon.style.fontSize = '18px';
-
- const serversSearchedText = document.createElement('p');
- serversSearchedText.textContent = 'Servers searched: 0';
- serversSearchedText.style.cssText = `
- color: white;
- margin: 0;
- font-size: 16px;
- flex-grow: 1;
- `;
-
- serversSearchedContainer.appendChild(serverIcon);
- serversSearchedContainer.appendChild(serversSearchedText);
-
- const timeElapsedContainer = document.createElement('div');
- timeElapsedContainer.style.cssText = `
- display: flex;
- align-items: center;
- gap: 10px;
- `;
-
- const timeIcon = document.createElement('span');
- timeIcon.textContent = '⏱️';
- timeIcon.style.fontSize = '18px';
-
- const timeElapsedText = document.createElement('p');
- timeElapsedText.textContent = 'Time elapsed: 0s';
- timeElapsedText.style.cssText = `
- color: white;
- margin: 0;
- font-size: 16px;
- flex-grow: 1;
- `;
-
- timeElapsedContainer.appendChild(timeIcon);
- timeElapsedContainer.appendChild(timeElapsedText);
-
- statusContainer.appendChild(serversSearchedContainer);
- statusContainer.appendChild(timeElapsedContainer);
- popup.appendChild(statusContainer);
-
- // Add a status message that can be updated
- const statusMessage = document.createElement('p');
- statusMessage.textContent = 'Scanning servers...';
- statusMessage.style.cssText = `
- color: #aaaaaa;
- margin: -10px 0 0 0;
- font-size: 14px;
- text-align: center;
- font-style: italic;
- `;
- popup.appendChild(statusMessage);
-
- // 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,
- statusMessage,
- ripple
- };
- }
-
- // Enhanced update function for search progress popup
- function updateSearchProgressPopup(
- progressPopup,
- totalServersChecked,
- timeElapsed,
- isFinal = false
- ) {
- // Update numbers with animation effect
- const currentServers = parseInt(progressPopup.serversSearchedText.textContent.match(/\d+/)[0]);
- const currentTime = parseInt(progressPopup.timeElapsedText.textContent.match(/\d+/)[0]);
-
- // Animate the number changes
- progressPopup.serversSearchedText.textContent = `Servers searched: ${totalServersChecked}`;
- progressPopup.timeElapsedText.textContent = `Time elapsed: ${timeElapsed}s`;
-
- // Update status message based on progress
- if (totalServersChecked % 5 === 0) {
- const messages = [
- "Scanning servers...",
- "Checking player lists...",
- "Searching for matches...",
- "Processing server data...",
- "Analyzing player tokens..."
- ];
- progressPopup.statusMessage.textContent = messages[Math.floor(Math.random() * messages.length)];
- }
-
- if (isFinal) {
- progressPopup.statusMessage.textContent = "Search completed";
- progressPopup.statusMessage.style.color = "#ffaa00";
- progressPopup.statusMessage.style.fontWeight = "bold";
- progressPopup.ripple.style.animationPlayState = "paused";
- progressPopup.serversSearchedText.textContent = `Servers searched: ${totalServersChecked} (complete)`;
- }
- }
-
- // Enhanced removal function for progress popup
- 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();
- }, 400);
- }
-
-
- /*********************************************************************************************************************************************************************************************************************************************
- 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 Rolocate'; // 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-public-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 the "Unable to load servers." message is visible
- if (!serverCardsContainer && noServersMessage?.textContent.includes('Unable to load servers.')) {
- notifications('Unable to load servers. Please refresh the page.', 'error', '⚠️', '8000');
- return;
- }
-
- // Reset everything if disable is true
- if (disable) {
- if (serverCardsContainer) {
- serverCardsContainer.innerHTML = ''; // Clear contents
- serverCardsContainer.removeAttribute('style'); // Remove inline styles if present
- }
-
- // Prevent duplicate loading bars
- const existingLoadingBar = document.querySelector('#loading-bar');
- if (existingLoadingBar) {
- existingLoadingBar.remove(); // Remove the existing loading bar if it exists
- }
-
- // 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
- const existingStyle = document.querySelector('#loading-style');
- if (!existingStyle) {
- 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 gradient div if it exists
- const gradientDiv = document.querySelector('div[style*="background: linear-gradient(145deg"]');
- if (gradientDiv) {
- gradientDiv.remove();
- }
-
- // ik this approach sucks but its the best i can do. it remove ths premium messages with this specific
- // text so it doesnet remove the other stuff, you prob cant even understand what im sayin right now
- const premiumMessageDiv = document.querySelector('.premium-message-text');
- if (premiumMessageDiv) {
- const messageText = premiumMessageDiv.textContent.trim();
- const errorMessages = [
- "Error: Cannot access server regions because you have not purchased the game.",
- "Error: This game requires users to teleport to a subplace. As a result, server regions cannot be retrieved."
- ];
-
- if (errorMessages.includes(messageText)) {
- showMessage("END");
- }
- }
-
-
-
-
- } else {
- // If disable is false, remove the loading bar
- const loadingBar = document.querySelector('#loading-bar');
- if (loadingBar) {
- loadingBar.remove();
- }
-
- // Reset any applied styles
- const styleSheet = document.querySelector('#loading-style');
- if (styleSheet) {
- styleSheet.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 {
- ConsoleLogEnabled('Filter button not found! Something is wrong!');
- notifications("Something's wrong. Please report and issue on Greasyfork.", "error", "⚠️", "15000");
- }
-
- 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!');
- notifications("Something's wrong. Please report and issue on Greasyfork.", "error", "⚠️", "15000");
- }
- }
-
-
-
- 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];
- }
-
-
- // end of the check for the url
- }
-
- /*******************************************************
- End of code for the random hop button and the filter button on roblox.com/games/*
- *******************************************************/
-
-
-
-
- })();