您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds filter options to roblox server page. Alternative to paid extensions like RoPro, RoGold (Ultimate), RoQol, and RoKit.
// ==UserScript== // @name RoLocate // @namespace https://oqarshi.github.io/ // @version 37.4 // @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.gf.qytechs.cn/scripts/535590/1586769/Rolocate%20Base64%20Image%20Library%2020.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 fastservers: false, // disabled by default invertplayercount: false, // disabled by default enablenotifications: true, // enabled by default experiencedtime: false // disabled by default not in settings }; // 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 () => { // ConsoleLogEnabled("[GM Storage Dump] --- Start ---"); // const keys = await GM_listValues(); // for (const key of keys) { // ConsoleLogEnabled(`[GM] ${key}:`, await GM_getValue(key)); // } // ConsoleLogEnabled("[GM Storage Dump] --- End ---"); //})();// //// testing for locations //(async () => { // const keys = await GM_listValues(); // for (const key of keys) { // GM_deleteValue(key); // ConsoleLogEnabled(`Deleted ${key}`); // } //})(); function initializeCoordinatesStorage() { // coors alredyt in there try { const storedCoords = GM_getValue("ROLOCATE_coordinates"); if (!storedCoords) { // make empty GM_setValue("ROLOCATE_coordinates", JSON.stringify({ lat: "", lng: "" })); } else { // yea 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) { ConsoleLogEnabled("Error initializing coordinates storage:", e); // not commenting this 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 37.4</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 <span class="help-icon" data-help="Show Old Greeting">?</span> </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 <span class="help-icon" data-help="Enable Console Logs">?</span> </label> <label class="toggle-slider section-hover"> <input type="checkbox" id="togglefilterserversbutton"> <span class="slider"></span> Enable Server Filters <span class="help-icon" data-help="Enable Server Filters">?</span> </label> <label class="toggle-slider section-hover"> <input type="checkbox" id="toggleserverhopbutton"> <span class="slider"></span> Enable Server Hop Button <span class="help-icon" data-help="Enable Server Hop Button">?</span> </label> <label class="toggle-slider section-hover"> <input type="checkbox" id="enablenotifications"> <span class="slider"></span> Enable Notifications <span class="help-icon" data-help="Enable Notifications">?</span> </label> <div class="location-settings section-hover"> <div class="setting-header"> <span>Set Default Location Mode</span> <span class="help-icon" data-help="Set default location">?</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://gf.qytechs.cn/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://gf.qytechs.cn/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 id="help-Auto Server Regions"><strong>Auto 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 id="help-Fast Server Search"><strong>Fast Server Search:</strong> <span>Increases the speed of searching for server locations up to 100x. Still experimental.</span></li> <li id="help-Invert Player Count"><strong>Invert Player Count:</strong> <span>For server regions: shows low-player servers when enabled, high-player servers when disabled.</span></li> <li id="help-Remove All Roblox Ads"><strong>Remove All Roblox Ads:</strong> <span>Blocks most ads on the Roblox site.</span></li> <li id="help-Recent Servers"><strong>Recent Servers:</strong> <span>Shows the most recent servers you have joined in the past 3 days.</span></li> <li id="help-Quick Navigation"><strong>Quick Nav:</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 id="help-Show Old Greeting"><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 id="help-Enable Console Logs"><strong>Enable Console Logs:</strong> <span>Enables console.log messages from the script.</span></li> <li id="help-Enable Server Filters"><strong>Enable Server Filters:</strong> <span>Enables server filter features on the game page.</span></li> <li id="help-Enable Server Hop Button"><strong>Enable Server Hop Button:</strong> <span>Enables server hop feature on the game page.</span></li> <li id="help-Enable Notifications"><strong>Enable Notifications:</strong> <span>Enables helpful notifications from the script.</span></li> <li id="help-Set default location"><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 experiment_label"> <input type="checkbox" id="AutoRunServerRegions"> <span class="slider"></span> Auto Server Regions <span class="experimental">Experimental <span class="tooltip">Still being tested</span> </span> <span class="help-icon" data-help="Auto Server Regions">?</span> </label> <label class="toggle-slider section-hover experiment_label"> <input type="checkbox" id="fastservers"> <span class="slider"></span> Fast Server Search <span class="experimental">Experimental <span class="tooltip">Still being tested</span> </span> <span class="help-icon" data-help="Fast Server Search">?</span> </label> <label class="toggle-slider section-hover experiment_label"> <input type="checkbox" id="invertplayercount"> <span class="slider"></span> Invert Player Count <span class="experimental">Experimental <span class="tooltip">Still being tested</span> </span> <span class="help-icon" data-help="Invert Player Count">?</span> </label> <label class="toggle-slider section-hover"> <input type="checkbox" id="removeads"> <span class="slider"></span> Remove All Roblox Ads <span class="help-icon" data-help="Remove All Roblox Ads">?</span> </label> <label class="toggle-slider section-hover"> <input type="checkbox" id="togglerecentserverbutton"> <span class="slider"></span> Recent Servers <span class="help-icon" data-help="Recent Servers">?</span> </label> <div class="quicknav-container section-hover"> <label class="toggle-slider slider-element"> <input type="checkbox" id="quicknav"> <span class="slider"></span> Quick Nav <span class="help-icon" data-help="Quick Navigation">?</span> </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; // storage make go uyea initializeLocalStorage(); initializeCoordinatesStorage(); 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); // put css in const style = document.createElement("style"); style.textContent = ` .help-icon { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; background: rgba(220, 53, 69, 0.15); border-radius: 50%; font-size: 12px; font-weight: 600; color: #e02d3c; cursor: pointer; transition: all 0.2s ease; margin-left: auto; /* Pushes it to the right */ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); position: relative; border: 1px solid rgba(220, 53, 69, 0.2); /* Add hand cursor explicitly */ cursor: pointer; } .help-icon:hover { background: rgba(220, 53, 69, 0.25); transform: translateY(-1px); box-shadow: 0 3px 5px rgba(0, 0, 0, 0.15); cursor: pointer; } /* Add tooltip on hover */ .help-icon::after { content: "Click for help"; position: absolute; bottom: -30px; left: 50%; transform: translateX(-50%); background: rgba(0, 0, 0, 0.8); color: white; padding: 4px 8px; border-radius: 4px; font-size: 11px; white-space: nowrap; opacity: 0; visibility: hidden; transition: all 0.2s ease; pointer-events: none; } .help-icon:hover::after { opacity: 1; visibility: visible; } .help-icon:active { transform: translateY(0); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); } @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(220, 53, 69, 0.4); } 70% { box-shadow: 0 0 0 6px rgba(220, 53, 69, 0); } 100% { box-shadow: 0 0 0 0 rgba(220, 53, 69, 0); } } .help-icon.attention { animation: pulse 2s infinite; } .highlight-help-item { animation: highlight 1.5s ease; background: rgba(76, 175, 80, 0.1); /* Green highlight */ border-left: 3px solid #4CAF50; /* Green border */ } @keyframes highlight { 0% { background: rgba(76, 175, 80, 0.3); } /* Green start */ 100% { background: rgba(76, 175, 80, 0.1); } /* Green end */ } .experiment_label .experimental { 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; position: relative; /* Needed for positioning the tooltip */ z-index: 10001; } .experiment_label .tooltip { visibility: hidden; background-color: rgba(0, 0, 0, 0.7); color: #fff; font-size: 12px; padding: 6px; border-radius: 5px; position: absolute; top: 100%; left: 50%; transform: translateX(-50%); white-space: nowrap; z-index: 10001; opacity: 0; transition: opacity 0.3s; } .experiment_label .experimental:hover .tooltip { visibility: visible; opacity: 1; z-index: 10001; } @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; margin: -12px 0 16px 0; /* Move up by 10px */ 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); // hopefully this works document.querySelectorAll(".settings-sidebar li").forEach((li, index) => { // aniamtions stuff 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"); // aniamtions stuff settingsBody.style.opacity = "0"; settingsBody.style.transform = "translateY(10px)"; settingsTitle.style.opacity = "0"; settingsTitle.style.transform = "translateY(10px)"; setTimeout(() => { // aniamtions stuff settingsTitle.textContent = section.charAt(0).toUpperCase() + section.slice(1); settingsBody.innerHTML = getSettingsContent(section); // quick nav stuff if (section === "general") { const quickNavCheckbox = document.getElementById("quicknav"); const editButton = document.getElementById("edit-quicknav-btn"); if (quickNavCheckbox && editButton) { editButton.style.display = localStorage.getItem("ROLOCATE_quicknav") === "true" ? "block" : "none"; quickNavCheckbox.addEventListener("change", function() { editButton.style.display = this.checked ? "block" : "none"; }); } } 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)"; void settingsBody.offsetWidth; void settingsTitle.offsetWidth; settingsBody.style.opacity = "1"; settingsBody.style.transform = "translateY(0)"; settingsTitle.style.opacity = "1"; settingsTitle.style.transform = "translateY(0)"; 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) { ConsoleLogEnabled("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); }); }); // Handle help icon clicks document.addEventListener('click', function(e) { if (e.target.classList.contains('help-icon')) { // Prevent the event from bubbling up to the toggle button e.stopPropagation(); e.preventDefault(); const helpItem = e.target.getAttribute('data-help'); if (helpItem) { // Switch to help tab const helpTab = document.querySelector('.settings-sidebar li[data-section="help"]'); if (helpTab) helpTab.click(); // Scroll to the corresponding help item after a short delay setTimeout(() => { const helpElement = document.getElementById(`help-${helpItem}`); if (helpElement) { helpElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); helpElement.classList.add('highlight-help-item'); setTimeout(() => { helpElement.classList.remove('highlight-help-item'); }, 1500); } }, 300); } } }); } 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) { ConsoleLogEnabled("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) { ConsoleLogEnabled("Error loading saved coordinates:", e); } } } }); } // Button click handlers const editQuickNavBtn = document.getElementById("edit-quicknav-btn"); if (editQuickNavBtn) { editQuickNavBtn.addEventListener("click", () => { showQuickNavPopup(); }); } const fastServersToggle = document.getElementById("fastservers"); if (fastServersToggle) { fastServersToggle.addEventListener("change", () => { if (fastServersToggle.checked) { notifications('Fast Server Search is experimental. 100x faster on Violentmonkey, ~2x on Tampermonkey (due to a bug).', 'info', '🧪', 2000); } }); } const AutoRunServerRegions = document.getElementById("AutoRunServerRegions"); if (AutoRunServerRegions) { AutoRunServerRegions.addEventListener("change", () => { if (AutoRunServerRegions.checked) { notifications('Auto Server Regions works best when paired with Fast Server Search in Advanced Settings.', 'info', '🧪', 2000); } }); } // 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 // only show notifications if ROLOCATE_enablenotifications is true if (localStorage.getItem('ROLOCATE_enablenotifications') !== 'true') { return; } // yea fancy color generator 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 = "V37.4"; const PREV_VERSION = "V36.3"; // Check if a version other than V37.4 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 active">×</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 style="line-height: 1.4; padding-left: 6px;"> <strong>New Experimental Setting</strong><br> <span class="feature-highlight" style="font-weight: bold;">Invert Server Regions</span> finds server regions with fewer players, as requested. </div> </div> <div class="feature-item"> <span class="feature-icon">📙</span> <div style="line-height: 1.4; padding-left: 6px;"> <strong>Language Support</strong><br> <span class="feature-highlight" style="font-weight: bold;"> Fixed language support </span> See the issue <a href="https://gf.qytechs.cn/en/scripts/523727-rolocate/discussions/298389" target="_blank" rel="noopener noreferrer">here</a>. </div> </div> <div class="feature-item"> <span class="feature-icon">🕷️</span> <div>Fixed Server Hop Button not working</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> </div> `; const popupContainer = document.createElement('div'); popupContainer.innerHTML = popupHTML; document.body.appendChild(popupContainer); const closeButton = popupContainer.querySelector('.first-time-popup-close'); const popup = popupContainer.querySelector('.first-time-popup'); if (closeButton && popup) { closeButton.addEventListener('click', () => { popup.style.animation = 'fadeOut 0.4s ease-in-out forwards'; popup.querySelector('.first-time-popup-content').style.animation = 'scaleDown 0.4s ease-in-out forwards'; setTimeout(() => { popup.remove(); }, 400); }); } else { console.warn('Popup initialization failed: close button or popup not found.'); } } 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"; } }); // Block Sponsored Ads Game Card document.querySelectorAll("div.gamecardcontainer").forEach(container => { if (container.querySelector("div.game-card-native-ad")) { container.style.display = "none"; } }); // 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"; } }); // NEW: Remove elements with class "sdui-feed-item-container" document.querySelectorAll(".sdui-feed-item-container").forEach(node => { node.remove(); }); } // 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) { ConsoleLogEnabled('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; transition: filter 0.2s ease; } `; 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.includes("/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 (!/^https:\/\/www\.roblox\.com(\/[a-z]{2})?\/games\//.test(window.location.href)) 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("/").indexOf("games") !== -1) ? url.split("/")[url.split("/").indexOf("games") + 1] : null; 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."); } let currentDelay = 150; // Start with 0.15 seconds let isRateLimited = false; // 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); if (response.status === 429) { ConsoleLogEnabled("Rate limited! Slowing down requests."); isRateLimited = true; currentDelay = 750; // Switch to 0.75 seconds setTimeout(() => fetchServers(cursor), currentDelay); return; } else if (isRateLimited && response.status === 200) { ConsoleLogEnabled("Recovered from rate limiting. Restoring normal delay."); isRateLimited = false; currentDelay = 150; // Back to normal } try { const data = JSON.parse(response.responseText); if (data.errors) { ConsoleLogEnabled("Skipping unreadable response:", data.errors[0].message); return; } 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); } }); if (data.nextPageCursor && pagesRequested < 4) { pagesRequested++; ConsoleLogEnabled(`Fetching page ${pagesRequested}...`); fetchServers(data.nextPageCursor); } else { pickRandomServer(); } }, currentDelay); } 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 (/^https:\/\/www\.roblox\.com(\/[a-z]{2})?\/games\//.test(window.location.href)) { 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'); // debug //console.log("Checking Filter Button Insertion:"); //console.log("serverListOptions:", serverListOptions); //console.log("RL-filter-button exists:", !!document.querySelector('.RL-filter-button')); //console.log("Filter button enabled?", localStorage.getItem("ROLOCATE_togglefilterserversbutton")); 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 = ((p => { const i = p.indexOf('games'); return i !== -1 && p.length > i + 1 ? p[i + 1] : null; })(window.location.pathname.split('/'))); // 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 = ((p => { const i = p.indexOf('games'); return i !== -1 && p.length > i + 1 ? p[i + 1] : null; })(window.location.pathname.split('/'))); // 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(/\/(?:[a-z]{2}\/)?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 = ((p = window.location.pathname.split('/')) => { const i = p.indexOf('games'); return i !== -1 && p.length > i + 1 ? p[i + 1] : null; })(); 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 = ((p = window.location.pathname.split('/')) => { const i = p.indexOf('games'); return i !== -1 && p.length > i + 1 ? p[i + 1] : null; })(); 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) { //here! const useBatching = localStorage.ROLOCATE_fastservers === "true"; if (!useBatching) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "POST", url: "https://gamejoin.roblox.com/v1/join-game-instance", headers: { "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); if (json.status === 12 && json.message === 'You need to purchase access to this game before you can play.') { reject('purchase_required'); return; } if (json.status === 12 && json.message === 'Cannot join this non-root place due to join restrictions') { reject('subplace_join_restriction'); return; } const address = json?.joinScript?.UdmuxEndpoints?.[0]?.Address ?? json?.joinScript?.MachineAddress; if (!address) { ConsoleLogEnabled("API Response (Unknown Location) Which means Full Server!:", json); reject(`Unable to fetch server location: Status ${json.status}`); return; } const location = serverRegionsByIp[address.replace(/^(128\.116\.\d+)\.\d+$/, "$1.0")]; if (!location) { ConsoleLogEnabled("API Response (Unknown Location):", json); reject(`Unknown server address ${address}`); return; } resolve(location); }, onerror: function(error) { ConsoleLogEnabled("API Request Failed:", error); reject(`Failed to fetch server details: ${error}`); }, }); }); } // Batching logic const queue = fetchServerDetails._queue || []; const concurrencyLimit = 10; if (!fetchServerDetails._queue) { fetchServerDetails._queue = queue; fetchServerDetails._activeCount = 0; } return new Promise((resolve, reject) => { const task = async () => { try { fetchServerDetails._activeCount++; const result = await new Promise((innerResolve, innerReject) => { GM_xmlhttpRequest({ method: "POST", url: "https://gamejoin.roblox.com/v1/join-game-instance", headers: { "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); if (json.status === 12 && json.message === 'You need to purchase access to this game before you can play.') { innerReject('purchase_required'); return; } if (json.status === 12 && json.message === 'Cannot join this non-root place due to join restrictions') { innerReject('subplace_join_restriction'); return; } const address = json?.joinScript?.UdmuxEndpoints?.[0]?.Address ?? json?.joinScript?.MachineAddress; if (!address) { ConsoleLogEnabled("API Response (Unknown Location) Which means Full Server!:", json); innerReject(`Unable to fetch server location: Status ${json.status}`); return; } const location = serverRegionsByIp[address.replace(/^(128\.116\.\d+)\.\d+$/, "$1.0")]; if (!location) { ConsoleLogEnabled("API Response (Unknown Location):", json); innerReject(`Unknown server address ${address}`); return; } innerResolve(location); }, onerror: function(error) { ConsoleLogEnabled("API Request Failed:", error); innerReject(`Failed to fetch server details: ${error}`); }, }); }); resolve(result); } catch (err) { reject(err); } finally { fetchServerDetails._activeCount--; if (queue.length > 0) { const next = queue.shift(); next(); } } }; if (fetchServerDetails._activeCount < concurrencyLimit) { task(); } else { queue.push(task); } }); } // 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 createServerCountPopup(callback) { const experienced = localStorage.getItem('ROLOCATE_experiencedtime'); if (experienced !== 'true') { const overlay = document.createElement('div'); overlay.className = 'rolocate-overlay-dark'; const popup = document.createElement('div'); popup.className = 'rolocate-popup-dark'; popup.innerHTML = ` <h2>Seems like it's your first time here.</h2> <p> After you select the amount of servers you want and click the confirm button, two Tampermonkey/Violentmonkey tabs will open. Please click <strong>Always Allow</strong> for the script to work. </p> <div class="rolocate-popup-footer"> <button id="rolocate-firstTimeOk" class="rolocate-btn-dark">OK</button> </div> `; document.body.appendChild(overlay); document.body.appendChild(popup); if (!document.getElementById('rolocate-dark-popup-styles')) { const style = document.createElement('style'); style.id = 'rolocate-dark-popup-styles'; style.textContent = ` .rolocate-overlay-dark { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.75); opacity: 0; animation: rolocate-fadeIn 0.3s forwards; z-index: 999999; } .rolocate-popup-dark { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: #1e1e1e; color: #ddd; padding: 28px 36px; border-radius: 12px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.85); width: 380px; max-width: 90vw; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; opacity: 0; animation: rolocate-fadeIn 0.3s forwards; z-index: 1000000; user-select: none; } .rolocate-popup-dark h2 { margin: 0 0 14px; font-weight: 700; font-size: 1.6rem; letter-spacing: 0.02em; color: #eee; } .rolocate-popup-dark p { margin: 0; font-size: 1rem; line-height: 1.6; color: #ccc; user-select: text; letter-spacing: 0.015em; text-shadow: 0 0 1px rgba(0,0,0,0.3); } .rolocate-popup-dark p strong { display: inline-block; font-weight: 700; color: #fff; background: linear-gradient(to right, #555 0%, #444 100%); padding: 2px 6px; border-radius: 4px; text-shadow: 0 1px 2px rgba(0,0,0,0.4); } .rolocate-popup-footer { margin-top: 24px; text-align: right; } .rolocate-btn-dark { background-color: #333; border: none; border-radius: 8px; padding: 10px 26px; color: #eee; font-weight: 600; font-size: 1rem; cursor: pointer; transition: background-color 0.3s ease, box-shadow 0.3s ease; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4); user-select: none; } .rolocate-btn-dark:hover, .rolocate-btn-dark:focus { background-color: #444; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.6); outline: none; } @keyframes rolocate-fadeIn { to { opacity: 1; } } @keyframes rolocate-fadeOut { to { opacity: 0; } } .rolocate-fade-out { animation: rolocate-fadeOut 0.3s forwards !important; pointer-events: none; } `; document.head.appendChild(style); } function closePopup() { overlay.classList.add('rolocate-fade-out'); popup.classList.add('rolocate-fade-out'); setTimeout(() => { overlay.remove(); popup.remove(); }, 300); } popup.querySelector('#rolocate-firstTimeOk').addEventListener('click', () => { localStorage.setItem('ROLOCATE_experiencedtime', 'true'); closePopup(); createServerCountPopup(callback); // continue to your original popup }); return; // wait for user click } // Normal popup code starts here 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 500 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 > 500) { notifications('Searching over 500 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 with adaptive rate limiting and logging async function fetchPublicServers(gameId, totalLimit) { let servers = []; let cursor = null; let delayTime = 250; // Start with 0.25 seconds let retryingDueToRateLimit = false; let pageCount = 0; const invertPlayerCount = localStorage.getItem("ROLOCATE_invertplayercount") === "true"; ConsoleLogEnabled(`Starting to fetch up to ${totalLimit} public servers for game ${gameId}...`); ConsoleLogEnabled(`Invert player count: ${invertPlayerCount}`); while (servers.length < totalLimit) { const url = `https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=100${invertPlayerCount ? '&sortOrder=1' : ''}${cursor ? `&cursor=${cursor}` : ''}`; pageCount++; ConsoleLogEnabled(`Fetching page ${pageCount}... (Current delay: ${delayTime}ms)`); let responseData; try { responseData = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: url, onload: function(response) { if (response.status === 429 || !response.responseText) { reject({ rateLimited: true }); } else { try { const json = JSON.parse(response.responseText); resolve(json); } catch (err) { reject({ rateLimited: true }); } } }, onerror: function(error) { reject({ rateLimited: false, error }); }, }); }); if (retryingDueToRateLimit) { delayTime = 250; retryingDueToRateLimit = false; ConsoleLogEnabled(`Rate limit cleared. Resuming normal delay (${delayTime}ms).`); } const newServers = responseData.data || []; servers = servers.concat(newServers); ConsoleLogEnabled(`Fetched ${newServers.length} servers (Total: ${servers.length}/${totalLimit})`); if (!responseData.nextPageCursor || servers.length >= totalLimit) { ConsoleLogEnabled("No more pages or reached limit."); break; } cursor = responseData.nextPageCursor; } catch (err) { if (err.rateLimited) { delayTime = 750; retryingDueToRateLimit = true; ConsoleLogEnabled("⚠️ Rate limited. Increasing delay to 0.75s..."); } else { ConsoleLogEnabled("❌ Failed to fetch due to error:", err.error); break; } } await delay(delayTime); } ConsoleLogEnabled(`✅ Done. Fetched ${servers.length} servers in total.`); 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); } const fetchPlayerThumbnails_servers = (() => { const queue = []; let processing = false; // Simple transparent 1x1 base64 PNG const randomBase64Image = () => { const placeholders = [ window.Base64Images.roblox_avatar, window.Base64Images.builderman_avatar, ]; const index = Math.floor(Math.random() * placeholders.length); return placeholders[index]; }; return async function(playerTokens) { ConsoleLogEnabled("Function called with playerTokens:", playerTokens); // Check if fast server mode is enabled if (localStorage.getItem("ROLOCATE_fastservers") === "true") { ConsoleLogEnabled("ROLOCATE_fastservers is enabled. Returning mock base64 images."); const mockData = playerTokens.map(token => ({ requestId: `0:${token}:AvatarHeadshot:150x150:png:regular`, targetId: 0, state: "Completed", imageUrl: randomBase64Image(), })); return mockData; } const waitHalfSecond = (ms = 250) => new Promise(res => setTimeout(res, ms)); return new Promise(resolve => { ConsoleLogEnabled("Pushing to queue:", playerTokens); queue.push({ playerTokens, resolve }); const processQueue = async () => { if (processing) { ConsoleLogEnabled("Already processing, exiting..."); return; } processing = true; ConsoleLogEnabled("Started processing queue..."); while (queue.length > 0) { const { playerTokens, resolve } = queue.shift(); ConsoleLogEnabled("Processing batch:", playerTokens); const body = playerTokens.map(token => ({ requestId: `0:${token}:AvatarHeadshot:150x150:png:regular`, type: "AvatarHeadShot", targetId: 0, token, format: "png", size: "150x150", })); let success = false; let data = []; while (!success) { ConsoleLogEnabled("Sending request to thumbnails.roblox.com..."); const response = await fetch("https://thumbnails.roblox.com/v1/batch", { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, body: JSON.stringify(body), }); ConsoleLogEnabled("Response status:", response.status); if (response.status === 429) { ConsoleLogEnabled("Rate limited. Waiting..."); await waitHalfSecond(); } else { const json = await response.json(); data = json.data || []; success = true; ConsoleLogEnabled("Received data:", data); } } resolve(data); ConsoleLogEnabled("Resolved promise with data"); } processing = false; ConsoleLogEnabled("Finished processing queue."); }; processQueue(); }); }; })(); 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) { const experienced = localStorage.getItem('ROLOCATE_experiencedtime'); if (experienced !== 'true') { const overlay = document.createElement('div'); overlay.className = 'rolocate-overlay-dark'; const popup = document.createElement('div'); popup.className = 'rolocate-popup-dark'; popup.innerHTML = ` <h2>Seems like it's your first time here.</h2> <p> After you click the button below, Two Tampermonkey/Violentmonkey tabs will open. Please click <strong>Always Allow</strong> for the script to work. </p> <div class="rolocate-popup-footer"> <button id="rolocate-firstTimeOk" class="rolocate-btn-dark">OK</button> </div> `; document.body.appendChild(overlay); document.body.appendChild(popup); if (!document.getElementById('rolocate-dark-popup-styles')) { const style = document.createElement('style'); style.id = 'rolocate-dark-popup-styles'; style.textContent = ` .rolocate-overlay-dark { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.75); opacity: 0; animation: rolocate-fadeIn 0.3s forwards; z-index: 999999; } .rolocate-popup-dark { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: #1e1e1e; color: #ddd; padding: 28px 36px; border-radius: 12px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.85); width: 380px; max-width: 90vw; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; opacity: 0; animation: rolocate-fadeIn 0.3s forwards; z-index: 1000000; user-select: none; } .rolocate-popup-dark h2 { margin: 0 0 14px; font-weight: 700; font-size: 1.6rem; letter-spacing: 0.02em; color: #eee; } .rolocate-popup-dark p { margin: 0; font-size: 1rem; line-height: 1.6; color: #ccc; user-select: text; letter-spacing: 0.015em; text-shadow: 0 0 1px rgba(0,0,0,0.3); } .rolocate-popup-dark p strong { display: inline-block; font-weight: 700; color: #fff; background: linear-gradient(to right, #555 0%, #444 100%); padding: 2px 6px; border-radius: 4px; text-shadow: 0 1px 2px rgba(0,0,0,0.4); } .rolocate-popup-footer { margin-top: 24px; text-align: right; } .rolocate-btn-dark { background-color: #333; border: none; border-radius: 8px; padding: 10px 26px; color: #eee; font-weight: 600; font-size: 1rem; cursor: pointer; transition: background-color 0.3s ease, box-shadow 0.3s ease; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4); user-select: none; } .rolocate-btn-dark:hover, .rolocate-btn-dark:focus { background-color: #444; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.6); outline: none; } @keyframes rolocate-fadeIn { to { opacity: 1; } } @keyframes rolocate-fadeOut { to { opacity: 0; } } .rolocate-fade-out { animation: rolocate-fadeOut 0.3s forwards !important; pointer-events: none; } `; document.head.appendChild(style); } function closePopup() { overlay.classList.add('rolocate-fade-out'); popup.classList.add('rolocate-fade-out'); setTimeout(() => { overlay.remove(); popup.remove(); }, 300); } popup.querySelector('#rolocate-firstTimeOk').addEventListener('click', () => { localStorage.setItem('ROLOCATE_experiencedtime', 'true'); closePopup(); rebuildServerList(gameId, 50, true); }); return; // wait for user click } disableLoadMoreButton(true); disableFilterButton(true); notifications("Retrieving Location...", "success", "🌎", '5000'); const userLocation = await getUserLocation(); if (!userLocation) { notifications('Error: Unable to fetch your location. Please enable location access or set it to manual in settings.', 'error', '⚠️', '5000'); disableFilterButton(false); return; } const servers = await fetchPublicServers(gameId, 50); if (servers.length === 0) { notifications('Error: No servers found. Please try again later.', 'error', '⚠️', '5000'); disableFilterButton(false); return; } const isFastServers = localStorage.getItem("ROLOCATE_fastservers") === "true"; let closestServer = null; let minDistance = Infinity; let closestServerLocation = null; if (isFastServers) { // Parallel fetching of all server locations const results = await Promise.allSettled( servers.map(async server => { const { id: serverId, maxPlayers, playing } = server; if (playing >= maxPlayers) return null; try { const location = await fetchServerDetails(gameId, serverId); const distance = calculateDistance( userLocation.latitude, userLocation.longitude, location.latitude, location.longitude ); return { server, location, distance }; } catch (error) { ConsoleLogEnabled(`Error fetching details for server ${serverId}:`, error); return null; } }) ); for (const result of results) { if (result.status === "fulfilled" && result.value) { const { server, location, distance } = result.value; if (distance < minDistance) { minDistance = distance; closestServer = server; closestServerLocation = location; } } } } else { // Sequential fetching (original behavior) for (const server of servers) { const { id: serverId, maxPlayers, playing } = server; if (playing >= maxPlayers) continue; try { const location = await fetchServerDetails(gameId, serverId); const distance = calculateDistance( userLocation.latitude, userLocation.longitude, location.latitude, location.longitude ); if (distance < minDistance) { minDistance = distance; closestServer = server; closestServerLocation = location; } } catch (error) { ConsoleLogEnabled(`Error fetching details for server ${serverId}:`, error); continue; } } } if (closestServer) { showLoadingOverlay(); Roblox.GameLauncher.joinGameInstance(gameId, closestServer.id); notifications(`Joining nearest server! Server ID: ${closestServer.id} Distance: ${(minDistance / 1.609).toFixed(2)} miles | ${minDistance.toFixed(2)} km Location (Country): ${closestServerLocation.country.name}.`, 'success', '🚀', '5000'); disableFilterButton(false); Loadingbar(false); } else { notifications('No valid servers found. Please try again later after refreshing the webpage. Filter button disabled.', 'error', '⚠️', '8000'); Loadingbar(false); } return; } // 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 = []; const useBatching = localStorage.ROLOCATE_fastservers === "true"; if (useBatching) { // Process servers in batches of 100 const batchSize = 100; let processedCount = 0; for (let i = 0; i < servers.length; i += batchSize) { const batch = servers.slice(i, i + batchSize); const batchPromises = batch.map(async (server) => { const { id: serverId, maxPlayers, playing, ping, fps, playerTokens } = server; // Skip full servers early to avoid unnecessary API calls if (playing >= maxPlayers) { skippedServers++; return null; } try { const location = await fetchServerDetails(gameId, serverId); if (location.city === "Unknown") { ConsoleLogEnabled(`Skipping server ${serverId} because location is unknown.`); skippedServers++; return null; } // Fetch player thumbnails const playerThumbnails = playerTokens && playerTokens.length > 0 ? await fetchPlayerThumbnails_servers(playerTokens) : []; return { server, location, playerThumbnails }; } catch (error) { if (error === 'purchase_required') { throw error; // Rethrow specific errors to be caught in the outer try/catch } else if (error === 'subplace_join_restriction') { throw error; } else { ConsoleLogEnabled(error); skippedServers++; return null; } } }); // function to smoothly update the processed count function updateProcessedCountSmoothly(startCount, targetCount) { const increment = 1; // count by 1 server let currentCount = startCount; const interval = setInterval(() => { if (currentCount < targetCount) { currentCount += increment; if (premium_message) { premium_message.textContent = `Filtering servers, please do not leave this page...\n${totalServers} servers found, ${currentCount} server locations found`; } } else { clearInterval(interval); } }, 2); // Update every 2ms ik this is insanly fast god dayum } const batchResults = await Promise.all(batchPromises); const previousProcessedCount = processedCount; // Filter out null results and add valid ones to serverDetails const validResults = batchResults.filter(result => result !== null); serverDetails.push(...validResults); // Gradually update processedCount after processing the batch processedCount += batch.length; updateProcessedCountSmoothly(previousProcessedCount, processedCount); } } else { // Original sequential processing 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; } 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', '⚠️', '8000'); 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\/(?:[a-z]{2}\/)?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 = ((p = window.location.pathname.split('/')) => { const i = p.indexOf('games'); return i !== -1 && p.length > i + 1 ? p[i + 1] : null; })(); // 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 servers with low player count, prob doesnet work with bloxfruits cause bots 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. roblox borke it lmao *********************************************************************************************************************************************************************************************************************************************/ // 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 = ((p = window.location.href.split('/')) => { const i = p.indexOf('games'); return i !== -1 && p.length > i + 1 ? p[i + 1] : null; })(); 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 if (disable) { if (serverCardsContainer) { serverCardsContainer.innerHTML = ''; // Clear contents serverCardsContainer.removeAttribute('style'); // Remove inline styles if present } // no duplicate ones 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); } // make thing look good 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); } // bruh this took way to long to debug const outerDiv = Array.from(document.querySelectorAll('div[style*="position: relative"]')) .find(div => div.querySelector('div[style*="background: radial-gradient(circle, rgba(255, 40, 40, 0.4)"]')); if (outerDiv) { outerDiv.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) { // kill the filter button so it cant be clicked filterButton.disabled = true; filterButton.style.opacity = '0.5'; filterButton.style.cursor = 'not-allowed'; // an invisible overlay on it so no sneaky clicks 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 like a boss parent.style.position = 'relative'; parent.appendChild(overlay); } } else { // bring the filter button back to life filterButton.disabled = false; filterButton.style.opacity = '1'; filterButton.style.cursor = 'pointer'; // remove that annoying overlay 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) { // same overlay trick but for refresh buttons 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'; // no clicks allowed here either refreshParent.style.position = 'relative'; refreshParent.appendChild(refreshOverlay); } } else { // remove overlays and let buttons live again 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 sevrer stautus yea 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'; // invite copy. 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() { // big dark box to show loading cuz why not const content = document.createElement('div'); content.style.position = 'fixed'; content.style.top = 'calc(50% - 15px)'; content.style.left = '50%'; content.style.transform = 'translate(-50%, -50%)'; content.style.width = '400px'; content.style.height = '250px'; content.style.backgroundColor = '#1e1e1e'; 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)'; content.style.borderRadius = '12px'; content.style.zIndex = '1000000'; content.style.opacity = '0'; content.style.transition = 'opacity 0.5s ease'; content.style.fontFamily = 'Arial, sans-serif'; // text that says we’re doing something important (kinda) const loadingText = document.createElement('p'); loadingText.textContent = 'Joining Roblox Game...'; loadingText.style.fontSize = '20px'; loadingText.style.color = '#fff'; loadingText.style.marginTop = '20px'; loadingText.style.fontWeight = 'bold'; // logo image cuz pictures make things fancy const base64Image = document.createElement('img'); base64Image.src = window.Base64Images.logo; base64Image.style.width = '80px'; base64Image.style.height = '80px'; base64Image.style.borderRadius = '8px'; // 3 little pulsing boxes to pretend something’s loading const loadingBoxes = document.createElement('div'); loadingBoxes.style.display = 'flex'; loadingBoxes.style.alignItems = 'center'; loadingBoxes.style.justifyContent = 'center'; loadingBoxes.style.marginTop = '15px'; for (let i = 0; i < 3; i++) { const box = document.createElement('div'); box.style.width = '10px'; box.style.height = '10px'; box.style.backgroundColor = '#fff'; box.style.margin = '0 5px'; box.style.borderRadius = '2px'; box.style.animation = `pulse 1.2s ${i * 0.2}s infinite`; loadingBoxes.appendChild(box); } const style = document.createElement('style'); style.textContent = ` @keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.5); } } `; document.head.appendChild(style); content.appendChild(base64Image); content.appendChild(loadingText); content.appendChild(loadingBoxes); document.body.appendChild(content); // fade it in cuz opacity is cool setTimeout(() => { content.style.opacity = '1'; }, 10); // fade out and remove cuz no one likes clutter setTimeout(() => { content.style.opacity = '0'; setTimeout(() => { document.body.removeChild(content); }, 500); }, 8000); } /********************************************************************************************************************************************************************************************************************************************* 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 }, }; // probably useless but too scared to remove function find_game_id() { ConsoleLogEnabled('Trying to find game id'); const gameIdMatch = window.location.pathname.match(/^\/(?:[a-z]{2}\/)?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/* *******************************************************/ })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址