您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
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 34.3 // @description Adds filter options to roblox server page. Alternative to paid extensions like RoPro, RoGold (Ultimate), RoQol, and RoKit. // @author Oqarshi // @match https://www.roblox.com/* // @license CC-BY-4.0; https://creativecommons.org/licenses/by/4.0/ // @icon data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSgBBwcHCggKEwoKEygaFhooKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKP/AABEIAEAAQAMBEQACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AOE714B+/wDUO9AdQoABQCExxTF0FNIbDHSgAxzQHUTjPSmLQv6HYJqmr21i9zHa+e4QSyA7VJ6Zx6nj8acY8ztc58VX+r0pVVHmsr2W503jH4e3/hWK1uLy4hltJpPLeaJWIiPuPpn8q1qUJU7Ns8vLM+o5i5QpxakleztqdBB8GdRnhSWHV7B4pFDKyqxDA8gjitfqknrc82XF1CDcZUpJr0HH4Kart41SxJ91f/Cj6nLuT/rjhv8An2/wOb8TfDfxBoFu9zNbx3Vqgy8ts2/aPUggHHvjFZTw84anq4LiHBYyShF8sn0en/AOM444rE9vQOM9KA0uL3pD6m34P0N/EWrvp8LbJ2gkkiPYuoyAfrjH41pThzuyODMccsDS9tJaXSfo2e2+CNSh8ceDrzQ9cDfb7Zfs9yrff4+7J9QR+Y9676UlVg4S3Pgs0w8spxscXhvglqu3mvT9GeT+JdV17RI4PDlxdXMEmlySKskUrJ5kbbSvQ8jgkezY7VxzlOHuN7H2OBw2ExbljYxTVRLRpOzV7/8AB9DCXxFrS4K6vqII7i5f/Gs/aS7ne8BhWtaUfuR6r8HfHOpajq/9iazO12ssbNDLJy4KjJUnuCM9fSuvDVpSfLI+R4kyWhQo/WsPHls9UttevlqcN8V9Fh0LxpdQWiBLaZVuI0HRQ2cge2QawxEFCbSPf4fxs8ZgoznrJaP5f8A5DvWB7fUO9Aa3O8+CP/JQLX/rjL/6Ca6ML/ER87xT/wAi+XqvzN/x1qv/AAh3xbTUrGPak0KPdRr0lDEhvx4B+ozWlWXsq3Mjzspwv9qZQ6FR6pvlfa239djf+L3h6HxJ4cg8RaRiWaCISFkH+tgPP5r1/OtcRTU488TzuHMfPAYmWBr6Ju3pL/g7fceERwyyr+6jd+3yqTzXn2P0GU1Fas9Z+Cvg/UoteTW9QtpLW2gRhEJVKtIzDHAPOACefpXZhqUubmZ8fxPm1CWHeFpSUpNq9uiWv3nM/GLVotW8cXJtnDxWyLbBhyCVyW/UkfhWWJkpTdj1eG8LPDYGPPo5Nv79vwOJ71znvdQ70B1O8+CP/JQbX/rjL/6Ca6ML/ER87xT/AMi+XqvzO28T6fb6r8arSxvU328+nMjr7FJOR7jrW84qVdJ9jwsBXnh8jlWpuzU0/wAYlj4a30/hzXb3wVrT7grNJZSN0dTyVHsRzj13CqoycJOlL5GWd0IY7DwzXD9dJLs/+Bt9xHppPw88fNp8hK+HtZbdAT92GT09sE4+hU9qUf3NS3RlVl/beX+2X8alv5r+tfW5ofGvUdd0zQ4ZdImENjIfKuXjX94uenzdgeRxznHPNViZTjH3djm4Yw+ExFdxrq81qu3np3Pnk9eteafpdg70B1E4z2pi0ud78Ecf8LBtf+uMv/oJrfC/xEfPcUW/s+XqvzPQdR/5L5pf/Xkf/QJK6X/vC9D5uj/yT9T/ABfrE5L453Etn49sLm2kMc8VpG6OOqsJHINY4ptVE0ezwpThVy+cJq6cmn9yO8ItPih8O8jYl8o/783Cj+Rz+TetdGlen5nzq9pkGY94fnF/qvzQeA9TTxb4WvdA19CdQtFNrdRv95h0D/UY6+oz3opS9pFwluh5vhnluLhjMK/cl70e3p6foeDeJtGn8P65dabdj95C+A2OHXqrD6ivPnBwk4s/Q8Fi6eNoRrw2f4PqjL4z2qTq0uL3pD6mv4V1+58NazHqVlFFJMisoWUErgjB6EVpTm6b5kcWYYGGPouhUbSdtvI2ZviBqc3jCDxG1tZi8hi8lYwreWRhhyN2c/Me9W68ufn6nDDIqEcHLApvlbvfS/Ty8uxmeMPE934r1OO+v4YIpUiEIWEELgEnuTzyaipUdR3Z15Zl1PLqTo0m2m762/4HYm8GeMNS8JT3EunLFIk6BXimBKkjoeCORz+dOnVlTehnmeU0MyhGNW6a2atctHx5qS+Lh4it7a0t7xk2Sxxq3lzDGPmBbPp0PYVXt5c/OjH+xKDwf1Kbbje6btdemn9XKvjTxbdeLLi3nv7O0hnhUoHgVgWXrg5Y9OcfU1NWq6mrRtlmV08ti4UpNp9Hb9EjnO9ZHqdQ70BrcO9Aa3CgNQFAK4namLWwppDdwoDUO9AdT//Z // @grant GM_xmlhttpRequest // @require https://update.greasyfork.icu/scripts/526611/1574250/Rolocate%20Base64%20Image%20Library.js // ==/UserScript== (function() { 'use strict'; function initializeLocalStorage() { // Define default settings const defaultSettings = { enableLogs: false, // disabled by default removeads: false, // disabled by default togglefilterserversbutton: true, // enable by default toggleserverhopbutton: true, // enable by default AutoRunServerRegions: false, // disabled by default ShowOldGreeting: false, // disabled by default togglerecentserverbutton: true, // enable by default quicknav: false, // disabled by default }; // Loop through default settings and set them in localStorage if they don't exist Object.entries(defaultSettings).forEach(([key, value]) => { const storageKey = `ROLOCATE_${key}`; if (localStorage.getItem(storageKey) === null) { localStorage.setItem(storageKey, value); } }); } function openSettingsMenu() { if (document.getElementById("userscript-settings-menu")) return; // Initialize localStorage with default values if they don't exist initializeLocalStorage(); // Create overlay const overlay = document.createElement("div"); overlay.id = "userscript-settings-menu"; overlay.innerHTML = ` <div class="settings-container"> <button id="close-settings" class="close-hover">✖</button> <div class="settings-sidebar"> <h2>Settings</h2> <ul> <li class="active" data-section="home">🏠 Home</li> <li data-section="general">⚙️ General</li> <li data-section="appearance">🎨 Appearance</li> <li data-section="advanced">🚀 Advanced</li> <li data-section ="help">📙 Help</li> <li data-section="about">ℹ️ About</li> </ul> </div> <div class="settings-content"> <h2 id="settings-title">Home</h2> <div id="settings-body">${getSettingsContent("home")}</div> </div> </div> `; document.body.appendChild(overlay); // Inject styles const style = document.createElement("style"); style.textContent = ` @keyframes fadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } } @keyframes fadeOut { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(0.95); } } @keyframes sectionFade { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } #userscript-settings-menu { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.6); display: flex; align-items: center; justify-content: center; z-index: 10000; animation: fadeIn 0.3s ease-out; } .settings-container { display: flex; position: relative; width: 520px; height: 380px; background: #1e1e1e; border-radius: 14px; overflow: hidden; box-shadow: 0 12px 24px rgba(0,0,0,0.5); font-family: Arial, sans-serif; } #close-settings { position: absolute; top: 12px; right: 12px; background: transparent; border: none; color: white; font-size: 20px; cursor: pointer; z-index: 10001; } .settings-sidebar { width: 35%; background: #272727; padding: 15px; color: white; display: flex; flex-direction: column; align-items: center; } .settings-sidebar ul { list-style: none; padding: 0; width: 100%; } .settings-sidebar li { padding: 12px; text-align: center; cursor: pointer; transition: 0.3s; border-radius: 6px; font-weight: bold; } .settings-sidebar li:hover, .settings-sidebar .active { background: #444; } /* Custom Scrollbar */ .settings-content { flex: 1; padding: 20px; color: white; text-align: center; max-height: 320px; overflow-y: auto; scrollbar-width: auto; scrollbar-color: darkgreen black; } /* Webkit (Chrome, Safari) Scrollbar */ .settings-content::-webkit-scrollbar { width: 14px; /* Increased thickness but it doesent work for some reason */ } .settings-content::-webkit-scrollbar-track { background: black; border-radius: 7px; } .settings-content::-webkit-scrollbar-thumb { background: darkgreen; border-radius: 7px; } .settings-content::-webkit-scrollbar-thumb:hover { background: #006400; /* Darker green on hover */ } .settings-content h2, .settings-content div { animation: sectionFade 0.3s ease-in-out; } .close-hover { position: relative; color: black; transition: color 0.3s ease; background: none; border: none; font-size: 1.5rem; cursor: pointer; } .close-hover::after { content: "" !important; position: absolute !important; left: 0 !important; bottom: -2px !important; width: 0% !important; height: 2px !important; background-color: red !important; transition: width 0.3s ease !important; } .close-hover:hover { color: red !important; } .close-hover:hover::after { width: 100% !important; } /* Toggle Slider Styles */ .toggle-slider { display: flex; align-items: center; margin: 10px 0; cursor: pointer; } .toggle-slider input { display: none; } .toggle-slider .slider { position: relative; display: inline-block; width: 40px; height: 20px; background-color: #A9A9A9; border-radius: 20px; margin-right: 10px; transition: background-color 0.3s; } .toggle-slider .slider::before { content: ""; position: absolute; height: 16px; width: 16px; left: 2px; bottom: 2px; background-color: white; border-radius: 50%; transition: transform 0.3s; } .toggle-slider input:checked + .slider { background-color: #4CAF50; } .toggle-slider input:checked + .slider::before { transform: translateX(20px); } .rolocate-logo { width: 75px !important; /* Force width */ height: 75px !important; /* Ensure proper scaling */ object-fit: contain; /* Prevent distortion */ border-radius: 10px; /* Rounded corners */ display: block; margin: 0 auto 10px auto; /* Center and add spacing */ } .version { font-size: 14px; color: #aaa; margin-bottom: 20px; } .settings-content ul { text-align: left; list-style-type: none; padding: 0; } .settings-content ul li { margin: 10px 0; } .settings-content ul li a { color: #4CAF50; text-decoration: none; } .settings-content ul li a:hover { text-decoration: underline; } .warning_advanced { font-size: 14px; /* Adjust size as needed */ color: red; font-weight: bold; } .average_text { font-size: 16px; color: grey; font-weight: bold; } h2 { text-decoration: underline; } .quicknav-container { display: flex; align-items: center; gap: 12px; } .edit-nav-button { padding: 6px 14px; /* Keep the original padding */ background-color: #4CAF50; /* Premium green color */ color: white; border: none; border-radius: 8px; /* Less rounded corners for a sleeker look */ cursor: pointer; font-family: 'Inter', 'Helvetica', sans-serif; font-size: 9px; /* Keep the original font size */ font-weight: 600; /* Bold text */ letter-spacing: 1px; /* Refined letter-spacing */ text-transform: uppercase; /* Uppercase for a modern feel */ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08); /* Elegant shadow */ transition: all 0.3s ease-in-out; /* Smooth transition for hover effect */ height: auto; /* Allow height to adjust based on content */ line-height: 1.5; /* Better vertical alignment */ margin-top: -10px; /* Compensates for the unwanted 1px offset */ } .edit-nav-button:hover { background: linear-gradient(135deg, #1e8449 0%, #196f3d 100%); transform: translateY(-2px); /* Slight lift effect on hover */ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15), 0 2px 5px rgba(0, 0, 0, 0.1); /* Deeper shadow on hover */ } .edit-nav-button:active { background-color: #2C6B3D; /* Darker green for active/clicked state */ transform: translateY(1px); /* Subtle "pressed" effect */ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); /* Softer shadow on click */ } .slider-element { margin-top: -1px; /* Compensates for the unwanted 5px offset */ } `; document.head.appendChild(style); // Sidebar logic with animation document.querySelectorAll(".settings-sidebar li").forEach(li => { li.addEventListener("click", function() { const currentActive = document.querySelector(".settings-sidebar .active"); if (currentActive) currentActive.classList.remove("active"); this.classList.add("active"); const section = this.getAttribute("data-section"); const settingsBody = document.getElementById("settings-body"); const settingsTitle = document.getElementById("settings-title"); // Apply fade-out first settingsBody.style.animation = "fadeOut 0.2s ease-in forwards"; settingsTitle.style.animation = "fadeOut 0.2s ease-in forwards"; setTimeout(() => { // Update content settingsTitle.textContent = section.charAt(0).toUpperCase() + section.slice(1); settingsBody.innerHTML = getSettingsContent(section); // Apply fade-in animation settingsBody.style.animation = "sectionFade 0.3s ease-in-out forwards"; settingsTitle.style.animation = "sectionFade 0.3s ease-in-out forwards"; applyStoredSettings(); }, 200); }); }); // Close button with fade-out animation document.getElementById("close-settings").addEventListener("click", function() { overlay.style.animation = "fadeOut 0.3s ease-in forwards"; setTimeout(() => overlay.remove(), 300); }); // Apply stored settings on open applyStoredSettings(); } function getSettingsContent(section) { if (section === "home") { return ` <img class="rolocate-logo" src="${window.Base64Images.logo}" alt="ROLOCATE Logo"> <span class="average_text">Rolocate Settings Menu.</span> `; } if (section === "appearance") { return ` <label class="toggle-slider"> <input type="checkbox" id="ShowOldGreeting"> <span class="slider"></span> Show Old Greeting </label> `; } if (section === "advanced") { return ` <span class="warning_advanced">For Experienced Users Only 🧠🙃</span> <label class="toggle-slider"> <input type="checkbox" id="enableLogs"> <span class="slider"></span> Enable Console Logs </label> <label class="toggle-slider"> <input type="checkbox" id="togglefilterserversbutton"> <span class="slider"></span> Enable Server Filters </label> <label class="toggle-slider"> <input type="checkbox" id="toggleserverhopbutton"> <span class="slider"></span> Enable Server Hop Button </label> `; } if (section === "about") { return ` <div class="version">Rolocate: Version 34.3</div> <h2>Credits</h2> <p>This project was created by:</p> <ul> <li><strong>Developer:</strong> <a href="https://www.roblox.com/users/545334824/profile" target="_blank">Oqarshi</a></li> <li><strong>Rolocate Source Code:</strong> <a href="http://greasyfork.icu/en/scripts/523727-rolocate" target="_blank">GreasyFork</a></li> <li><strong>Invite & FAQ Source Code:</strong> <a href="https://github.com/Oqarshi/Invite" target="_blank">GitHub</a></li> <li><strong>FAQ Website:</strong> <a href="https://oqarshi.github.io/Invite/rolocate/index.html" target="_blank">RoLocate FAQ</a></li> <li><strong>Suggest Features:</strong> <a href="http://greasyfork.icu/en/scripts/523727-rolocate/feedback" target="_blank">Submit Feedback</a></li> <li><strong>Inspiration:</strong> <a href="https://chromewebstore.google.com/detail/btroblox-making-roblox-be/hbkpclpemjeibhioopcebchdmohaieln" target="_blank">Btroblox Team</a></li> </ul> `; } // the help if (section === "help") { return ` <h2>General Tab:</h2> <ul> <li>Auto Run Server Regions: <a>Replaces Roblox's 8 default servers with at least 8 servers, providing detailed info such as location and ping.</a></li> <li>Remove All Roblox Ads: <a>Blocks most ads on the roblox site. Still experimental.</a></li> <li>Recent Servers: <a>Shows the most recent servers you have join in the past 3 days.</a></li> <li>Quick Navigation: <a>Ability to add quick navigations to the leftside panel of the Roblox page.</a></li> </ul> <h2>Appearance Tab:</h2> <ul> <li>Show Old Greeting: <a>Shows the old greeting Roblox had on their home page.</a></li> </ul> <h2>Advanced Tab:</h2> <ul> <li>Enable Console Logs: <a>Enables ConsoleLogEnabled messages from the script.</a></li> <li>Enable Server Filters: <a>Enables server filter features on the game page.</a></li> <li>Enable Server Hop Button: <a>Enables server hop feature on the game page.</a></li> </ul> `; } // the general return ` <label class="toggle-slider"> <input type="checkbox" id="AutoRunServerRegions"> <span class="slider"></span> Auto Run Server Regions </label> <label class="toggle-slider"> <input type="checkbox" id="removeads"> <span class="slider"></span> Remove All Roblox Ads </label> <label class="toggle-slider"> <input type="checkbox" id="togglerecentserverbutton"> <span class="slider"></span> Recent Servers </label> <div class="quicknav-container"> <label class="toggle-slider slider-element"> <input type="checkbox" id="quicknav"> <span class="slider"></span> Quick Navigation </label> <button id="edit-quicknav-btn" style="display: none;" class="edit-nav-button"> Edit Quick Nav </button> </div> `; } 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() { 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"; } } }); // Show button on load if enabled if (checkbox.id === "quicknav" && checkbox.checked) { const editBtn = document.getElementById("edit-quicknav-btn"); if (editBtn) { editBtn.style.display = "inline-block"; } } }); // Button click const editQuickNavBtn = document.getElementById("edit-quicknav-btn"); if (editQuickNavBtn) { editQuickNavBtn.addEventListener("click", () => { showQuickNavPopup(); }); } } function AddSettingsButton() { const base64Logo = window.Base64Images.logo; const navbarGroup = document.querySelector('.nav.navbar-right.rbx-navbar-icon-group'); if (!navbarGroup || document.getElementById('custom-logo')) return; const li = document.createElement('li'); li.id = 'custom-logo-container'; li.style.position = 'relative'; li.innerHTML = ` <img id="custom-logo" style=" margin-top: 6px; margin-left: 6px; width: 26px; cursor: pointer; border-radius: 4px; transition: all 0.2s ease-in-out; " src="${base64Logo}"> <span id="custom-tooltip" style=" visibility: hidden; background-color: black; color: white; text-align: center; padding: 5px; border-radius: 5px; position: absolute; top: 35px; left: 50%; transform: translateX(-50%); white-space: nowrap; font-size: 12px; opacity: 0; transition: opacity 0.2s ease-in-out; "> Settings </span> `; const logo = li.querySelector('#custom-logo'); const tooltip = li.querySelector('#custom-tooltip'); logo.addEventListener('click', () => openSettingsMenu()); logo.addEventListener('mouseover', () => { logo.style.width = '30px'; logo.style.border = '2px solid white'; tooltip.style.visibility = 'visible'; tooltip.style.opacity = '1'; }); logo.addEventListener('mouseout', () => { logo.style.width = '26px'; logo.style.border = 'none'; tooltip.style.visibility = 'hidden'; tooltip.style.opacity = '0'; }); navbarGroup.appendChild(li); } /************************************************************************* Premium Notification System *************************************************************************/ function notifications(message, type = 'info', emoji = '', duration = 3000) { // Helper function to manipulate colors - supports hex, rgb, and rgba function adjustColor(color, percent) { // Handle hex colors if (color.startsWith('#')) { let num = parseInt(color.slice(1), 16), amt = Math.round(2.55 * percent), R = (num >> 16) + amt, G = ((num >> 8) & 0xFF) + amt, B = (num & 0xFF) + amt; R = Math.max(Math.min(255, R), 0); G = Math.max(Math.min(255, G), 0); B = Math.max(Math.min(255, B), 0); return "#" + ((1 << 24) + (R << 16) + (G << 8) + B).toString(16).slice(1); } // Handle rgb/rgba colors else if (color.startsWith('rgb')) { const isRGBA = color.startsWith('rgba'); const parts = color.match(/\d+(\.\d+)?/g).map(Number); for (let i = 0; i < 3; i++) { parts[i] = Math.max(0, Math.min(255, parts[i] + (2.55 * percent))); } return isRGBA ? `rgba(${parts[0]}, ${parts[1]}, ${parts[2]}, ${parts[3]})` : `rgb(${parts[0]}, ${parts[1]}, ${parts[2]})`; } return color; // Return original if format not recognized } // Inject CSS styles for the toast system once if (!document.getElementById('premium-toast-styles')) { const style = document.createElement('style'); style.id = 'premium-toast-styles'; style.innerHTML = ` @keyframes toast-slide-in { 0% { opacity: 0; transform: translateX(50px); } 100% { opacity: 1; transform: translateX(0); } } @keyframes toast-slide-out { 0% { opacity: 1; transform: translateX(0); } 100% { opacity: 0; transform: translateX(50px); } } @keyframes progress-shrink { 0% { width: 100%; } 100% { width: 0%; } } @keyframes emoji-pop { 0% { transform: scale(0.8); opacity: 0.7; } 40% { transform: scale(1.3); opacity: 1; } 60% { transform: scale(0.9); opacity: 0.95; } 80% { transform: scale(1.1); opacity: 1; } 100% { transform: scale(1); opacity: 1; } } @keyframes emoji-float { 0% { transform: translateY(0); } 50% { transform: translateY(-4px); } 100% { transform: translateY(0); } } @keyframes emoji-glow { 0% { text-shadow: 0 0 5px rgba(255,255,255,0); } 50% { text-shadow: 0 0 10px rgba(255,255,255,0.5); } 100% { text-shadow: 0 0 5px rgba(255,255,255,0); } } #toast-container { position: fixed; top: 24px; right: 24px; z-index: 999999; display: flex; flex-direction: column; gap: 12px; pointer-events: none; } .toast { position: relative; min-width: 320px; max-width: 420px; padding: 16px 20px; border-radius: 12px; box-shadow: 0 8px 20px rgba(0,0,0,0.18), 0 2px 8px rgba(0,0,0,0.15), 0 0 1px rgba(255,255,255,0.2); animation: toast-slide-in 0.5s cubic-bezier(0.25, 1, 0.5, 1) forwards; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; backdrop-filter: blur(10px); word-wrap: break-word; pointer-events: auto; overflow: hidden; display: flex; flex-direction: column; } .toast.removing { animation: toast-slide-out 0.5s cubic-bezier(0.55, 0, 0.1, 1) forwards; } .toast .toast-content { display: flex; align-items: center; gap: 12px; color: white; font-size: 15px; line-height: 1.5; font-weight: 500; letter-spacing: 0.2px; } .toast-emoji-wrapper { position: relative; display: flex; justify-content: center; align-items: center; width: 32px; height: 32px; } .toast-emoji { font-size: 22px; position: relative; display: inline-block; animation: emoji-pop 0.6s ease-out, emoji-float 3s ease-in-out infinite, emoji-glow 2s ease-in-out infinite; transform-origin: center; z-index: 2; } .toast .message { flex: 1; } .toast-close-btn { position: absolute; top: 12px; right: 12px; width: 20px; height: 20px; cursor: pointer; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: rgba(255, 255, 255, 0.15); transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); border: 1px solid rgba(255, 255, 255, 0.2); } .toast-close-btn:before, .toast-close-btn:after { content: ''; position: absolute; width: 12px; height: 2px; background: rgba(255, 255, 255, 0.9); border-radius: 1px; transition: all 0.3s ease; } .toast-close-btn:before { transform: rotate(45deg); } .toast-close-btn:after { transform: rotate(-45deg); } .toast-close-btn:hover { background: rgba(255, 255, 255, 0.25); transform: scale(1.1) rotate(90deg); box-shadow: 0 0 10px rgba(255, 255, 255, 0.3); } .toast-close-btn:hover:before, .toast-close-btn:hover:after { background: rgba(255, 255, 255, 1); } .toast .progress-bar-container { position: absolute; bottom: 0; left: 0; height: 4px; width: 100%; background-color: rgba(255, 255, 255, 0.2); overflow: hidden; } .toast .progress-bar { height: 100%; width: 100%; background: linear-gradient(90deg, rgba(255,255,255,0.5), rgba(255,255,255,0.9)); animation-name: progress-shrink; animation-timing-function: linear; animation-fill-mode: forwards; box-shadow: 0 0 8px rgba(255, 255, 255, 0.5); } .toast-icon { width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; border-radius: 50%; background: rgba(255, 255, 255, 0.25); flex-shrink: 0; box-shadow: 0 0 8px rgba(255, 255, 255, 0.2); } .toast.success { background: linear-gradient(135deg, #43A047, #66BB6A); border-left: 4px solid #2E7D32; } .toast.error { background: linear-gradient(135deg, #E53935, #EF5350); border-left: 4px solid #C62828; } .toast.info { background: linear-gradient(135deg, #1E88E5, #42A5F5); border-left: 4px solid #1565C0; } .toast.warning { background: linear-gradient(135deg, #FB8C00, #FFA726); border-left: 4px solid #EF6C00; } `; document.head.appendChild(style); } // Create or get the container let container = document.getElementById('toast-container'); if (!container) { container = document.createElement('div'); container.id = 'toast-container'; document.body.appendChild(container); } // Create toast element const toast = document.createElement('div'); toast.className = `toast ${type.toLowerCase()}`; // Create content wrapper with optional emoji and icon const content = document.createElement('div'); content.className = 'toast-content'; // Add type-specific icon const icon = document.createElement('div'); icon.className = 'toast-icon'; // Set icon content based on type let iconContent = ''; switch (type.toLowerCase()) { case 'success': iconContent = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>'; break; case 'error': iconContent = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>'; break; case 'warning': iconContent = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>'; break; case 'info': default: iconContent = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>'; break; } icon.innerHTML = iconContent; content.appendChild(icon); // Add emoji if provided with enhanced animations if (emoji) { const emojiWrapper = document.createElement('div'); emojiWrapper.className = 'toast-emoji-wrapper'; const emojiSpan = document.createElement('span'); emojiSpan.className = 'toast-emoji'; emojiSpan.textContent = emoji; emojiWrapper.appendChild(emojiSpan); content.appendChild(emojiWrapper); } // Add message const messageSpan = document.createElement('span'); messageSpan.className = 'message'; messageSpan.textContent = message; content.appendChild(messageSpan); toast.appendChild(content); // Create the enhanced close button (X) const closeBtn = document.createElement('div'); closeBtn.className = 'toast-close-btn'; closeBtn.addEventListener('click', () => removeToast(toast)); toast.appendChild(closeBtn); // Create progress bar container and progress bar const progressBarContainer = document.createElement('div'); progressBarContainer.className = 'progress-bar-container'; const progressBar = document.createElement('div'); progressBar.className = 'progress-bar'; progressBar.style.animationDuration = `${duration}ms`; progressBarContainer.appendChild(progressBar); toast.appendChild(progressBarContainer); // Append toast to container container.appendChild(toast); // Auto-remove toast after the specified duration const removeTimeout = setTimeout(() => removeToast(toast), duration); let removeTimeoutRef = removeTimeout; // Add hover pause functionality toast.addEventListener('mouseenter', () => { // Pause the progress bar animation progressBar.style.animationPlayState = 'paused'; clearTimeout(removeTimeoutRef); // Subtle scale effect on hover toast.style.transform = 'scale(1.02)'; toast.style.transition = 'transform 0.3s ease'; }); toast.addEventListener('mouseleave', () => { // Resume the progress bar animation progressBar.style.animationPlayState = 'running'; // Reset scale toast.style.transform = 'scale(1)'; // Calculate remaining time based on progress bar width percentage const remainingPercentage = progressBar.offsetWidth / progressBarContainer.offsetWidth; const remainingTime = duration * remainingPercentage; // Set new timeout with remaining time clearTimeout(removeTimeoutRef); removeTimeoutRef = setTimeout(() => removeToast(toast), remainingTime); }); // Function to fade out and remove toast function removeToast(toastEl) { clearTimeout(removeTimeoutRef); toastEl.classList.add('removing'); setTimeout(() => toastEl.remove(), 500); } // Return an object with methods to control the toast return { remove: () => removeToast(toast), update: (newMessage) => { messageSpan.textContent = newMessage; }, setType: (newType) => { toast.className = `toast ${newType.toLowerCase()}`; }, setDuration: (newDuration) => { clearTimeout(removeTimeoutRef); // Reset the progress bar animation progressBar.style.animation = 'none'; setTimeout(() => { progressBar.style.animation = `progress-shrink ${newDuration}ms linear forwards`; removeTimeoutRef = setTimeout(() => removeToast(toast), newDuration); }, 10); }, updateEmoji: (newEmoji) => { if (emoji) { const emojiElement = toast.querySelector('.toast-emoji'); if (emojiElement) { // Reset animation by cloning and replacing const parent = emojiElement.parentNode; const newEmojiElement = emojiElement.cloneNode(true); newEmojiElement.textContent = newEmoji; parent.replaceChild(newEmojiElement, emojiElement); } } } }; } function Update_Popup() { const VERSION = "V34.3"; const PREV_VERSION = "V33.3"; // Check if a version other than V34.3 exists and show the popup const currentVersion = localStorage.getItem('version') || "V0.0"; // Get saved version or default to "V0.0" if (currentVersion !== VERSION) { localStorage.setItem('version', VERSION); // Set the new version } else { return; // If the current version is the latest, do not show the popup } // Remove any previous version flag if present if (localStorage.getItem(PREV_VERSION)) { localStorage.removeItem(PREV_VERSION); } const css = ` .first-time-popup { display: flex; position: fixed; inset: 0; background: rgba(0, 0, 0, 0.7); justify-content: center; align-items: center; z-index: 1000; opacity: 0; animation: fadeIn 0.4s ease-in-out forwards; } .first-time-popup-content { background: rgba(25, 25, 25, 0.95); border-radius: 18px; padding: 30px; width: 420px; max-width: 90%; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); text-align: center; color: #fff; transform: scale(0.85); animation: scaleUp 0.5s ease-out forwards; } .popup-header { font-size: 22px; font-weight: bold; color: #4da6ff; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 5px; text-align: center; /* Centering the header text */ } .popup-version { font-size: 18px; font-weight: bold; color: #ffcc00; margin-bottom: 15px; } .popup-info { font-size: 15px; color: #ccc; margin-bottom: 20px; line-height: 1.6; padding: 10px; border-radius: 10px; background: rgba(255, 255, 255, 0.05); } .popup-info a { color: #4da6ff; text-decoration: none; font-weight: bold; transition: color 0.3s ease; } .popup-info a:hover { color: #80bfff; text-decoration: underline; } .popup-footer { font-size: 14px; color: #aaa; font-weight: bold; margin-top: 10px; transition: opacity 0.3s ease-out; } .popup-footer.hidden { opacity: 0; visibility: hidden; } .popup-note { font-size: 13px; font-weight: bold; color: #ff6666; margin-top: 8px; } .popup-logo { display: block; margin: 0 auto 15px; width: 80px; height: auto; border-radius: 10px; } .first-time-popup-close { position: absolute; top: 10px; right: 15px; font-size: 24px; font-weight: bold; cursor: pointer; color: #fff; opacity: 0.4; transition: opacity 0.3s ease; pointer-events: none; } .first-time-popup-close.active { opacity: 1; pointer-events: auto; } .first-time-popup-close:hover { color: #ff4d4d; } .first-time-popup-close::after { content: ""; display: block; width: 0%; height: 2px; background: #ff4d4d; transition: width 0.3s ease-out; } .first-time-popup-close:hover::after { width: 100%; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes scaleUp { 0% { transform: scale(0.85); } 60% { transform: scale(1.05); } 100% { transform: scale(1); } } `; const style = document.createElement('style'); style.type = 'text/css'; style.innerHTML = css; document.head.appendChild(style); const popupHTML = ` <div class="first-time-popup"> <div class="first-time-popup-content"> <span class="first-time-popup-close">×</span> <img class="popup-logo" src="${window.Base64Images.logo}" alt="Rolocate Logo"> <div class="popup-header"><b>Rolocate Update</b></div> <div class="popup-version"><b>Version: ${VERSION}</b></div> <div class="popup-info"> <p><strong style="color: green;">This is a major update. Please report any issues on Greasyfork if something breaks! Thx :) - Developer.</strong> You can now enable the "Show old Roblox Header," new "Quick Navigation" setting, and UI enhancements – all of these are off by default, so make sure to check the settings. The new "Recent Servers" feature is already active. As always, check out the <a href="https://oqarshi.github.io/Invite/rolocate/index.html" target="_blank">FAQ page</a>! This message will not appear again until the next update.</p> <div class="popup-footer">Closing enabled in <span id="countdown-timer"><strong>5</strong></span> seconds...</div> </div> </div> </div> `; const popupContainer = document.createElement('div'); popupContainer.innerHTML = popupHTML; document.body.appendChild(popupContainer); const closeButton = document.querySelector('.first-time-popup-close'); const popup = document.querySelector('.first-time-popup'); const countdownTimer = document.getElementById('countdown-timer'); const footer = document.querySelector('.popup-footer'); let countdown = 5; const countdownInterval = setInterval(() => { countdown--; countdownTimer.innerHTML = `<strong>${countdown}</strong>`; if (countdown <= 0) { clearInterval(countdownInterval); closeButton.classList.add('active'); footer.classList.add('hidden'); } }, 1000); closeButton.addEventListener('click', () => { popup.style.animation = 'fadeOut 0.4s ease-in-out forwards'; document.querySelector('.first-time-popup-content').style.animation = 'scaleDown 0.4s ease-in-out forwards'; setTimeout(() => { popup.remove(); }, 400); }); } function removeAds() { if (localStorage.getItem("ROLOCATE_removeads") !== "true") { return; } const iframeSelector = `.ads-container iframe,.abp iframe,.abp-spacer iframe,.abp-container iframe,.top-abp-container iframe, #AdvertisingLeaderboard iframe,#AdvertisementRight iframe,#MessagesAdSkyscraper iframe,.Ads_WideSkyscraper iframe, .profile-ads-container iframe, #ad iframe, iframe[src*="roblox.com/user-sponsorship/"]`; const iframes = document.getElementsByTagName("iframe"); const scripts = document.getElementsByTagName("script"); const doneMap = new WeakMap(); function removeElements() { // Remove Iframes for (let i = iframes.length; i--;) { const iframe = iframes[i]; if (!doneMap.get(iframe) && iframe.matches(iframeSelector)) { iframe.remove(); doneMap.set(iframe, true); } } // Remove Scripts for (let i = scripts.length; i--;) { const script = scripts[i]; if (doneMap.get(script)) { continue; } doneMap.set(script, true); if (script.src && ( script.src.includes("imasdk.googleapis.com") || script.src.includes("googletagmanager.com") || script.src.includes("radar.cedexis.com") || script.src.includes("ns1p.net") )) { script.remove(); } else { const cont = script.textContent; if (!cont.includes("ContentJS") && ( cont.includes("scorecardresearch.com") || cont.includes("cedexis.com") || cont.includes("pingdom.net") || cont.includes("ns1p.net") || cont.includes("Roblox.Hashcash") || cont.includes("Roblox.VideoPreRollDFP") || cont.includes("Roblox.AdsHelper=") || cont.includes("googletag.enableServices()") || cont.includes("gtag('config'") )) { script.remove(); } else if (cont.includes("Roblox.EventStream.Init")) { script.textContent = cont.replace(/"[^"]*"/g, "\"\""); } } } // Hide Sponsored Game Cards (existing method) document.querySelectorAll(".game-card-native-ad").forEach(ad => { const gameCard = ad.closest(".game-card-container"); if (gameCard) { gameCard.style.display = "none"; } }); // New: Block Sponsored Ads Game Card document.querySelectorAll("div.gamecardcontainer").forEach(container => { if (container.querySelector("div.game-card-native-ad")) { container.style.display = "none"; } }); // New: Block Sponsored Section On HomePage document.querySelectorAll(".game-sort-carousel-wrapper").forEach(wrapper => { const sponsoredLink = wrapper.querySelector('a[href*="Sponsored"]'); if (sponsoredLink) { wrapper.style.display = "none"; } }); } // Observe DOM for dynamically added elements new MutationObserver(removeElements).observe(document.body, { childList: true, subtree: true }); removeElements(); // Initial run } function ConsoleLogEnabled(...args) { if (localStorage.getItem("ROLOCATE_enableLogs") === "true") { console.log("[ROLOCATE]", ...args); } } async function showOldRobloxGreeting() { ConsoleLogEnabled("Function showOldRobloxGreeting() started."); // Check if the URL is roblox.com/home if (!window.location.href.includes("roblox.com/home")) { ConsoleLogEnabled("Not on roblox.com/home. Exiting function."); return; // ⛔ Stops execution if not on the home page } // Check LocalStorage before proceeding if (localStorage.getItem("ROLOCATE_ShowOldGreeting") !== "true") { ConsoleLogEnabled("ShowOldGreeting is disabled. Exiting function."); return; // ⛔ Stops execution if setting is off } ConsoleLogEnabled("Waiting 500ms before proceeding."); await new Promise(r => setTimeout(r, 500)); function observeElement(selector) { ConsoleLogEnabled(`Observing element: ${selector}`); return new Promise((resolve) => { const observer = new MutationObserver(() => { const element = document.querySelector(selector); if (element) { ConsoleLogEnabled(`Element found: ${selector}`); observer.disconnect(); resolve(element); } }); observer.observe(document.body, { childList: true, subtree: true }); }); } async function fetchAvatar(selector, fallbackImage) { ConsoleLogEnabled(`Fetching avatar from selector: ${selector}`); for (let attempt = 0; attempt < 3; attempt++) { ConsoleLogEnabled(`Attempt ${attempt + 1} to fetch avatar.`); const imgElement = document.querySelector(selector); if (imgElement && imgElement.src !== fallbackImage) { ConsoleLogEnabled(`Avatar found: ${imgElement.src}`); return imgElement.src; } await new Promise(r => setTimeout(r, 1500)); } ConsoleLogEnabled("Avatar not found, using fallback image."); return fallbackImage; } let homeContainer = await observeElement("#HomeContainer .section:first-child"); ConsoleLogEnabled("Home container located."); let userNameElement = document.querySelector("#navigation.rbx-left-col > ul > li > a .font-header-2"); ConsoleLogEnabled(`User name found: ${userNameElement ? userNameElement.innerText : "Unknown"}`); let user = { name: userNameElement ? `Hello, ${userNameElement.innerText}!` : "Hello, Roblox User!", avatar: await fetchAvatar("#navigation.rbx-left-col > ul > li > a img", window.Base64Images.image_place_holder) }; ConsoleLogEnabled(`Final user details: Name - ${user.name}, Avatar - ${user.avatar}`); let headerContainer = document.createElement("div"); headerContainer.classList.add("new-header"); headerContainer.style.opacity = "0"; let profileFrame = document.createElement("div"); profileFrame.classList.add("profile-frame"); let profileImage = document.createElement("img"); profileImage.src = user.avatar; profileImage.classList.add("profile-img"); profileFrame.appendChild(profileImage); let userDetails = document.createElement("div"); userDetails.classList.add("user-details"); let userName = document.createElement("h1"); userName.classList.add("user-name"); userName.textContent = user.name; userDetails.appendChild(userName); headerContainer.appendChild(profileFrame); headerContainer.appendChild(userDetails); ConsoleLogEnabled("Replacing old home container with new header."); homeContainer.replaceWith(headerContainer); let styleTag = document.createElement("style"); styleTag.textContent = ` .new-header { display: flex; align-items: center; margin-bottom: 30px; transition: opacity 1.5s ease-in-out; } .profile-frame { width: 150px; height: 150px; border-radius: 50%; overflow: hidden; border: 3px solid #121215; display: flex; justify-content: center; align-items: center; } .profile-img { width: 100%; height: 100%; object-fit: cover; } .user-details { margin-left: 20px; display: flex; align-items: center; } .user-name { font-size: 1.2em; font-weight: bold; color: white; } `; document.head.appendChild(styleTag); ConsoleLogEnabled("Style tag added."); setTimeout(() => { ConsoleLogEnabled("Fading in new header."); headerContainer.style.opacity = "1"; }, 50); } let lastUrl = window.location.href.split("#")[0]; // Store only the base URL function observeURLChanges() { const observer = new MutationObserver(() => { let currentUrl = window.location.href.split("#")[0]; // Ignore fragment changes if (currentUrl !== lastUrl) { ConsoleLogEnabled(`URL changed from ${lastUrl} to ${currentUrl}`); lastUrl = currentUrl; // Update the stored URL // Re-run functions when going back to home if (currentUrl.includes("roblox.com/home")) { ConsoleLogEnabled("Detected return to home page. Reloading greeting."); showOldRobloxGreeting(); } } }); observer.observe(document.body, { childList: true, subtree: true }); } function quicknavbutton() { if (localStorage.getItem('ROLOCATE_quicknav') === 'true') { const settingsRaw = localStorage.getItem('ROLOCATE_quicknav_settings'); if (!settingsRaw) return; let settings; try { settings = JSON.parse(settingsRaw); } catch (e) { console.error('Failed to parse ROLOCATE_quicknav_settings:', e); return; } const sidebar = document.querySelector('.left-col-list'); if (!sidebar) return; const premiumButton = sidebar.querySelector('.rbx-upgrade-now'); const style = document.createElement('style'); style.textContent = ` .rolocate-icon-custom { display: inline-block; width: 24px; height: 24px; margin-left: 3px; background-image: url("${window.Base64Images.quicknav}"); background-size: contain; background-repeat: no-repeat; } `; document.head.appendChild(style); settings.forEach(({ name, link }) => { const li = document.createElement('li'); const a = document.createElement('a'); a.className = 'dynamic-overflow-container text-nav'; a.href = link; a.target = '_self'; const divIcon = document.createElement('div'); const spanIcon = document.createElement('span'); spanIcon.className = 'rolocate-icon-custom'; divIcon.appendChild(spanIcon); const spanText = document.createElement('span'); spanText.className = 'font-header-2 dynamic-ellipsis-item'; spanText.title = name; spanText.textContent = name; a.appendChild(divIcon); a.appendChild(spanText); li.appendChild(a); if (premiumButton && premiumButton.parentElement === sidebar) { sidebar.insertBefore(li, premiumButton); } else { sidebar.appendChild(li); } }); } } // 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!"); // 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 occured! No icons will show. Please refresh the page.', 'error', '⚠️', '8000') } } else { if (callback) callback(); } })(); } /******************************************************* The code for the random hop button and the filter button on roblox.com/games/* *******************************************************/ if (window.location.href.startsWith("https://www.roblox.com/games/") && (localStorage.getItem("ROLOCATE_togglefilterserversbutton") === "true" || localStorage.getItem("ROLOCATE_toggleserverhopbutton") === "true")) { let Isongamespage = false; // Initially false /********************************************************************************************************************************************************************************************************************************************* This is all of the functions for the filter button and the popup for the 7 buttons does not include the functions for the 8 buttons *********************************************************************************************************************************************************************************************************************************************/ //Testing //HandleRecentServersAddGames("126884695634066", "853e79a5-1a2b-4178-94bf-a242de1aecd6"); //HandleRecentServersAddGames("126884695634066", "a08849f1-40e32-4b3215c-31231231a268-e948519caf39"); //HandleRecentServersAddGames("126884695634066", "a08849f1-40e32-4b5c-31236541231a268-e948519caf39"); //HandleRecentServersAddGames("126884695634066", "a08849f1-40e32-4b5c-31231287631a268-e948519caf39"); //HandleRecentServersAddGames("126884695634066", "a08849f1-40e32-4b5c-31231231a268-87e948519caf39"); //HandleRecentServersAddGames("126884695634066", "a08849f1-40e32-4b5c-31231231a268089-e948519caf39"); //document.querySelector('.recent-servers-section')?.remove(); // remove old list //HandleRecentServers(); // re-render with updated order function InitRobloxLaunchHandler() { if (!window.location.href.startsWith('https://www.roblox.com/games/')) return; if (window._robloxJoinInterceptorInitialized) return; window._robloxJoinInterceptorInitialized = true; const originalJoin = Roblox.GameLauncher.joinGameInstance; Roblox.GameLauncher.joinGameInstance = function(gameId, serverId) { ConsoleLogEnabled(`Intercepted join: Game ID = ${gameId}, Server ID = ${serverId}`); HandleRecentServersAddGames(gameId, serverId); document.querySelector('.recent-servers-section')?.remove(); // remove old list HandleRecentServers(); // re-render with updated order return originalJoin.apply(this, arguments); }; } function HandleRecentServersAddGames(gameId, serverId) { const storageKey = "ROLOCATE_recentservers_button"; const stored = JSON.parse(localStorage.getItem(storageKey) || "{}"); const key = `${gameId}_${serverId}`; stored[key] = Date.now(); // Always update timestamp localStorage.setItem(storageKey, JSON.stringify(stored)); } function HandleRecentServersURL() { // Static-like variable to remember if we've already found an invalid URL if (HandleRecentServersURL.alreadyInvalid) { return; // Skip if previously marked as invalid } const url = window.location.href; // Regex pattern to match ROLOCATE_GAMEID and SERVERID from the hash const match = url.match(/ROLOCATE_GAMEID=(\d+)_SERVERID=([a-f0-9-]+)/i); if (match && match.length === 3) { const gameId = match[1]; const serverId = match[2]; // Call the handler with extracted values HandleRecentServersAddGames(gameId, serverId); InitRobloxLaunchHandler(); } else { ConsoleLogEnabled("No gameId and serverId found in URL."); InitRobloxLaunchHandler(); HandleRecentServersURL.alreadyInvalid = true; // Set internal flag } } function HandleRecentServers() { const serverList = document.querySelector('.server-list-options'); if (!serverList || document.querySelector('.recent-servers-section')) return; const match = window.location.href.match(/\/games\/(\d+)\//); if (!match) return; const currentGameId = match[1]; const allHeaders = document.querySelectorAll('.server-list-header'); let friendsSectionHeader = null; allHeaders.forEach(header => { if (header.textContent.trim() === 'Servers My Friends Are In') { friendsSectionHeader = header.closest('.container-header'); } }); if (!friendsSectionHeader) return; // Custom premium dark theme CSS variables const theme = { bgDark: '#14161a', bgCard: '#1c1f25', bgCardHover: '#22262e', bgGradient: 'linear-gradient(145deg, #1e2228, #18191e)', bgGradientHover: 'linear-gradient(145deg, #23272f, #1c1f25)', accentPrimary: '#4d85ee', accentSecondary: '#3464c9', accentGradient: 'linear-gradient(to bottom, #4d85ee, #3464c9)', accentGradientHover: 'linear-gradient(to bottom, #5990ff, #3b6fdd)', textPrimary: '#e8ecf3', textSecondary: '#a0a8b8', textMuted: '#6c7484', borderLight: 'rgba(255, 255, 255, 0.06)', borderLightHover: 'rgba(255, 255, 255, 0.12)', shadow: '0 5px 15px rgba(0, 0, 0, 0.25)', shadowHover: '0 8px 25px rgba(0, 0, 0, 0.3)', dangerColor: '#ff5b5b', dangerColorHover: '#ff7575', dangerGradient: 'linear-gradient(to bottom, #ff5b5b, #e04444)', dangerGradientHover: 'linear-gradient(to bottom, #ff7575, #f55)' }; const recentSection = document.createElement('div'); recentSection.className = 'recent-servers-section premium-dark'; recentSection.style.marginBottom = '24px'; const headerContainer = document.createElement('div'); headerContainer.className = 'container-header'; const headerInner = document.createElement('div'); headerInner.className = 'server-list-container-header'; headerInner.style.padding = '0 4px'; const headerTitle = document.createElement('h2'); headerTitle.className = 'server-list-header'; headerTitle.textContent = 'Recent Servers'; headerTitle.style.cssText = ` font-weight: 600; color: ${theme.textPrimary}; letter-spacing: 0.5px; position: relative; display: inline-block; padding-bottom: 4px; `; // Add premium underline accent to header const headerAccent = document.createElement('span'); headerAccent.style.cssText = ` position: absolute; bottom: 0; left: 0; width: 40px; height: 2px; background: ${theme.accentGradient}; border-radius: 2px; `; headerTitle.appendChild(headerAccent); headerInner.appendChild(headerTitle); headerContainer.appendChild(headerInner); const contentContainer = document.createElement('div'); contentContainer.className = 'section-content-off empty-game-instances-container'; contentContainer.style.padding = '8px 4px'; const storageKey = "ROLOCATE_recentservers_button"; let stored = JSON.parse(localStorage.getItem(storageKey) || "{}"); // Auto-remove servers older than 3 days const currentTime = Date.now(); const threeDaysInMs = 3 * 24 * 60 * 60 * 1000; // 3days in miliseconds let storageUpdated = false; Object.keys(stored).forEach(key => { const serverTime = stored[key]; if (currentTime - serverTime > threeDaysInMs) { delete stored[key]; storageUpdated = true; } }); if (storageUpdated) { localStorage.setItem(storageKey, JSON.stringify(stored)); } const keys = Object.keys(stored).filter(key => key.startsWith(`${currentGameId}_`)); if (keys.length === 0) { const emptyMessage = document.createElement('div'); emptyMessage.className = 'no-servers-message'; emptyMessage.innerHTML = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="opacity: 0.7; margin-right: 10px;"> <path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="${theme.accentPrimary}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M9.09 9C9.3251 8.33167 9.78915 7.76811 10.4 7.40913C11.0108 7.05016 11.7289 6.91894 12.4272 7.03871C13.1255 7.15849 13.7588 7.52152 14.2151 8.06353C14.6713 8.60553 14.9211 9.29152 14.92 10C14.92 12 11.92 13 11.92 13" stroke="${theme.accentPrimary}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M12 17H12.01" stroke="${theme.accentPrimary}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg>No Recent Servers Found`; emptyMessage.style.cssText = ` color: ${theme.textSecondary}; text-align: center; padding: 28px 0; font-size: 14px; letter-spacing: 0.3px; font-weight: 500; display: flex; align-items: center; justify-content: center; background: rgba(20, 22, 26, 0.4); backdrop-filter: blur(5px); border-radius: 12px; border: 1px solid rgba(77, 133, 238, 0.15); box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.2); `; contentContainer.appendChild(emptyMessage); } else { keys.sort((a, b) => stored[b] - stored[a]); // Create server cards wrapper const cardsWrapper = document.createElement('div'); cardsWrapper.style.cssText = ` display: flex; flex-direction: column; gap: 12px; margin: 2px 0; `; keys.forEach((key, index) => { const [gameId, serverId] = key.split("_"); const timeStored = stored[key]; const date = new Date(timeStored); const formattedTime = date.toLocaleString(undefined, { hour: '2-digit', minute: '2-digit', year: 'numeric', month: 'short', day: 'numeric' }); const serverCard = document.createElement('div'); serverCard.className = 'recent-server-card premium-dark'; serverCard.dataset.serverKey = key; serverCard.style.cssText = ` display: flex; justify-content: space-between; align-items: center; padding: 16px 22px; height: 76px; border-radius: 14px; background: ${theme.bgGradient}; box-shadow: ${theme.shadow}; color: ${theme.textPrimary}; font-family: 'Segoe UI', 'Helvetica Neue', sans-serif; font-size: 14px; box-sizing: border-box; width: 100%; position: relative; overflow: hidden; border: 1px solid ${theme.borderLight}; transition: all 0.2s ease-out; `; // Add hover effect serverCard.onmouseover = function() { this.style.boxShadow = theme.shadowHover; this.style.transform = 'translateY(-2px)'; this.style.borderColor = theme.borderLightHover; this.style.background = theme.bgGradientHover; }; serverCard.onmouseout = function() { this.style.boxShadow = theme.shadow; this.style.transform = 'translateY(0)'; this.style.borderColor = theme.borderLight; this.style.background = theme.bgGradient; }; // Add glass effect overlay const glassOverlay = document.createElement('div'); glassOverlay.style.cssText = ` position: absolute; left: 0; top: 0; right: 0; height: 50%; background: linear-gradient(to bottom, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0)); border-radius: 14px 14px 0 0; pointer-events: none; `; serverCard.appendChild(glassOverlay); // Server icon with glow const serverIconWrapper = document.createElement('div'); serverIconWrapper.style.cssText = ` position: absolute; left: 14px; display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; `; const serverIcon = document.createElement('div'); serverIcon.innerHTML = ` <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M2 17L12 22L22 17" stroke="${theme.accentPrimary}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M2 12L12 17L22 12" stroke="${theme.accentPrimary}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M2 7L12 12L22 7L12 2L2 7Z" stroke="${theme.accentPrimary}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> </svg> `; serverIconWrapper.appendChild(serverIcon); // Add subtle glow to the server icon const iconGlow = document.createElement('div'); iconGlow.style.cssText = ` position: absolute; width: 24px; height: 24px; border-radius: 50%; background: ${theme.accentPrimary}; opacity: 0.15; filter: blur(8px); z-index: -1; `; serverIconWrapper.appendChild(iconGlow); const left = document.createElement('div'); left.style.cssText = ` display: flex; flex-direction: column; justify-content: center; margin-left: 12px; `; const lastPlayed = document.createElement('div'); lastPlayed.textContent = `Last Played: ${formattedTime}`; lastPlayed.style.cssText = ` font-weight: 600; font-size: 14px; color: ${theme.textPrimary}; line-height: 1.3; letter-spacing: 0.3px; margin-left: 40px; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); `; const metaInfo = document.createElement('div'); metaInfo.innerHTML = `<span style="color: ${theme.accentPrimary};">Game ID:</span> ${gameId} <span style="color: ${theme.textMuted};">•</span> <span style="color: ${theme.accentPrimary};">Server ID:</span> ${serverId}`; metaInfo.style.cssText = ` font-size: 12px; color: ${theme.textSecondary}; margin-top: 5px; opacity: 0.9; margin-left: 40px; `; left.appendChild(lastPlayed); left.appendChild(metaInfo); serverCard.appendChild(serverIconWrapper); const buttonGroup = document.createElement('div'); buttonGroup.style.cssText = ` display: flex; gap: 12px; align-items: center; z-index: 2; `; // Create the smaller remove button to be positioned on the left const removeButton = document.createElement('button'); removeButton.innerHTML = ` <svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> `; removeButton.className = 'btn-control-xs remove-button'; removeButton.style.cssText = ` background: ${theme.dangerGradient}; color: white; border: none; padding: 6px; border-radius: 8px; font-size: 13px; font-weight: 600; cursor: pointer; transition: all 0.15s ease; letter-spacing: 0.4px; box-shadow: 0 2px 8px rgba(255, 91, 91, 0.3); display: flex; align-items: center; justify-content: center; width: 30px; height: 30px; `; // Add remove button hover effect removeButton.onmouseover = function() { this.style.background = theme.dangerGradientHover; this.style.boxShadow = '0 4px 10px rgba(255, 91, 91, 0.4)'; this.style.transform = 'translateY(-1px)'; }; removeButton.onmouseout = function() { this.style.background = theme.dangerGradient; this.style.boxShadow = '0 2px 8px rgba(255, 91, 91, 0.3)'; this.style.transform = 'translateY(0)'; }; // Add remove button functionality removeButton.addEventListener('click', function(e) { e.stopPropagation(); const serverKey = this.closest('.recent-server-card').dataset.serverKey; // Animate removal serverCard.style.transition = 'all 0.3s ease-out'; serverCard.style.opacity = '0'; serverCard.style.height = '0'; serverCard.style.margin = '0'; serverCard.style.padding = '0'; setTimeout(() => { serverCard.remove(); // Update localStorage const storedData = JSON.parse(localStorage.getItem(storageKey) || "{}"); delete storedData[serverKey]; localStorage.setItem(storageKey, JSON.stringify(storedData)); // If no servers left, show empty message if (document.querySelectorAll('.recent-server-card').length === 0) { const emptyMessage = document.createElement('div'); emptyMessage.className = 'no-servers-message'; emptyMessage.innerHTML = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="opacity: 0.7; margin-right: 10px;"> <path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="${theme.accentPrimary}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M9.09 9C9.3251 8.33167 9.78915 7.76811 10.4 7.40913C11.0108 7.05016 11.7289 6.91894 12.4272 7.03871C13.1255 7.15849 13.7588 7.52152 14.2151 8.06353C14.6713 8.60553 14.9211 9.29152 14.92 10C14.92 12 11.92 13 11.92 13" stroke="${theme.accentPrimary}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M12 17H12.01" stroke="${theme.accentPrimary}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg>No Recent Servers Found`; emptyMessage.style.cssText = ` color: ${theme.textSecondary}; text-align: center; padding: 28px 0; font-size: 14px; letter-spacing: 0.3px; font-weight: 500; display: flex; align-items: center; justify-content: center; background: rgba(20, 22, 26, 0.4); backdrop-filter: blur(5px); border-radius: 12px; border: 1px solid rgba(77, 133, 238, 0.15); box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.2); `; cardsWrapper.appendChild(emptyMessage); } }, 300); }); // Create a separator element const separator = document.createElement('div'); separator.style.cssText = ` height: 24px; width: 1px; background-color: rgba(255, 255, 255, 0.15); margin: 0 2px; `; const joinButton = document.createElement('button'); joinButton.innerHTML = ` <svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-right: 6px;"> <path d="M5 12H19" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M12 5L19 12L12 19" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> Join `; joinButton.className = 'btn-control-xs join-button'; joinButton.style.cssText = ` background: ${theme.accentGradient}; color: white; border: none; padding: 8px 18px; border-radius: 10px; font-size: 13px; font-weight: 600; cursor: pointer; transition: all 0.15s ease; letter-spacing: 0.4px; box-shadow: 0 2px 10px rgba(52, 100, 201, 0.3); display: flex; align-items: center; justify-content: center; `; // Add join button functionality joinButton.addEventListener('click', function() { try { Roblox.GameLauncher.joinGameInstance(gameId, serverId); } catch (error) { ConsoleLogEnabled("Error joining game:", error); } }); // Add hover effect for join button joinButton.onmouseover = function() { this.style.background = theme.accentGradientHover; this.style.boxShadow = '0 4px 12px rgba(77, 133, 238, 0.4)'; this.style.transform = 'translateY(-1px)'; }; joinButton.onmouseout = function() { this.style.background = theme.accentGradient; this.style.boxShadow = '0 2px 10px rgba(52, 100, 201, 0.3)'; this.style.transform = 'translateY(0)'; }; const inviteButton = document.createElement('button'); inviteButton.innerHTML = ` <svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-right: 6px;"> <path d="M16 18L18 20L22 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M20 12V13.4C20 13.4 19.5 13 19 13C18.5 13 18 13.5 18 14C18 14.5 18.5 15 19 15C19.5 15 20 14.6 20 14.6V16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M4 20C4 17 7 17 8 17C9 17 13 17 13 17" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> <path d="M9.5 10C10.8807 10 12 8.88071 12 7.5C12 6.11929 10.8807 5 9.5 5C8.11929 5 7 6.11929 7 7.5C7 8.88071 8.11929 10 9.5 10Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> </svg> Invite `; inviteButton.className = 'btn-control-xs invite-button'; inviteButton.style.cssText = ` background: rgba(28, 31, 37, 0.6); color: ${theme.textPrimary}; border: 1px solid rgba(255, 255, 255, 0.12); padding: 8px 18px; border-radius: 10px; font-size: 13px; font-weight: 500; cursor: pointer; transition: all 0.15s ease; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(4px); `; // Add invite button functionality inviteButton.addEventListener('click', function() { const inviteUrl = `https://oqarshi.github.io/Invite/?placeid=${gameId}&serverid=${serverId}`; // Copy to clipboard navigator.clipboard.writeText(inviteUrl).then( function() { // Show feedback that URL was copied const originalText = inviteButton.innerHTML; inviteButton.innerHTML = ` <svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-right: 6px;"> <path d="M20 6L9 17L4 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> Copied! `; ConsoleLogEnabled(`Invite link copied to clipboard`); notifications('Success! Invite link copied to clipboard!', 'success', '🎉', ' 2000'); // Reset button after 2 seconds setTimeout(() => { inviteButton.innerHTML = originalText; }, 2000); }, function(err) { ConsoleLogEnabled('Could not copy text: ', err); } ); }); // Add hover effect for invite button inviteButton.onmouseover = function() { this.style.background = 'rgba(35, 39, 46, 0.8)'; this.style.borderColor = 'rgba(255, 255, 255, 0.18)'; this.style.transform = 'translateY(-1px)'; }; inviteButton.onmouseout = function() { this.style.background = 'rgba(28, 31, 37, 0.6)'; this.style.borderColor = 'rgba(255, 255, 255, 0.12)'; this.style.transform = 'translateY(0)'; }; // MODIFIED: Now add buttons in the new order: Remove, Separator, Join, Invite buttonGroup.appendChild(removeButton); buttonGroup.appendChild(separator); buttonGroup.appendChild(joinButton); buttonGroup.appendChild(inviteButton); serverCard.appendChild(left); serverCard.appendChild(buttonGroup); cardsWrapper.appendChild(serverCard); // Add subtle line accent const lineAccent = document.createElement('div'); lineAccent.style.cssText = ` position: absolute; left: 0; top: 16px; bottom: 16px; width: 3px; background: ${theme.accentGradient}; border-radius: 0 2px 2px 0; `; serverCard.appendChild(lineAccent); // Add subtle corner accent if (index === 0) { const cornerAccent = document.createElement('div'); cornerAccent.style.cssText = ` position: absolute; right: 0; top: 0; width: 40px; height: 40px; overflow: hidden; pointer-events: none; `; const cornerInner = document.createElement('div'); cornerInner.style.cssText = ` position: absolute; right: -20px; top: -20px; width: 40px; height: 40px; background: ${theme.accentPrimary}; transform: rotate(45deg); opacity: 0.15; `; cornerAccent.appendChild(cornerInner); serverCard.appendChild(cornerAccent); } }); contentContainer.appendChild(cardsWrapper); } recentSection.appendChild(headerContainer); recentSection.appendChild(contentContainer); friendsSectionHeader.parentNode.insertBefore(recentSection, friendsSectionHeader); } /******************************************************* name of function: createPopup description: Creates a popup with server filtering options and interactive buttons. *******************************************************/ function createPopup() { const popup = document.createElement('div'); popup.className = 'server-filters-dropdown-box'; // Unique class name popup.style.cssText = ` position: absolute; width: 210px; height: 382px; right: 0px; top: 30px; z-index: 1000; border-radius: 5px; background-color: rgb(30, 32, 34); display: flex; flex-direction: column; padding: 5px; `; // Create the header section const header = document.createElement('div'); header.style.cssText = ` display: flex; align-items: center; padding: 10px; border-bottom: 1px solid #444; margin-bottom: 5px; `; // Add the logo (base64 image) const logo = document.createElement('img'); logo.src = window.Base64Images.logo; logo.style.cssText = ` width: 24px; height: 24px; margin-right: 10px; `; // Add the title const title = document.createElement('span'); title.textContent = 'RoLocate'; title.style.cssText = ` color: white; font-size: 18px; font-weight: bold; `; // Append logo and title to the header header.appendChild(logo); header.appendChild(title); // Append the header to the popup popup.appendChild(header); // Define unique names, tooltips, experimental status, and explanations for each button const buttonData = [{ name: "Smallest Servers", tooltip: "**Reverses the order of the server list.** The emptiest servers will be displayed first.", experimental: false }, { name: "Available Space", tooltip: "**Filters out servers which are full.** Servers with space will only be shown.", experimental: false }, { name: "Player Count", tooltip: "**Rolocate will find servers with your specified player count or fewer.** Searching for up to 3 minutes. If no exact match is found, it shows servers closest to the target.", experimental: false }, { name: "Random Shuffle", tooltip: "**Display servers in a completely random order.** Shows servers with space and servers with low player counts in a randomized order.", experimental: false }, { name: "Server Region", tooltip: "**Filters servers by region.** Offering more accuracy than 'Best Connection' in areas with fewer Roblox servers, like India, or in games with high player counts.", experimental: true, experimentalExplanation: "**Experimental**: Still in development and testing. Sometimes user location cannot be detected." }, { name: "Best Connection", tooltip: "**Automatically joins the fastest servers for you.** However, it may be less accurate in regions with fewer Roblox servers, like India, or in games with large player counts.", experimental: true, experimentalExplanation: "**Experimental**: Still in development and testing. it may be less accurate in regions with fewer Roblox servers" }, { name: "Join Small Server", tooltip: "**Automatically tries to join a server with a very low population.** On popular games servers may fill up very fast so you might not always get in alone.", experimental: false }, { name: "Locate Player", tooltip: "**Finds and joins the server a user is playing on if they are playing this particular game.** Note: May take a while for very popular games.", experimental: false, disabled: true, disabledExplanation: "**Disabled**: Due to the recent Roblox update, this feature no longer works. :(" } ]; // Create buttons with unique names, tooltips, experimental status, and explanations buttonData.forEach((data, index) => { const buttonContainer = document.createElement('div'); buttonContainer.className = 'server-filter-option'; buttonContainer.classList.add(data.disabled ? "disabled" : "enabled"); // Create a wrapper for the button content that can have opacity applied const buttonContentWrapper = document.createElement('div'); buttonContentWrapper.style.cssText = ` width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; ${data.disabled ? 'opacity: 0.7;' : ''} `; buttonContainer.style.cssText = ` width: 190px; height: 30px; background-color: ${data.disabled ? '#2c2c2c' : '#393B3D'}; margin: 5px; border-radius: 5px; padding: 3.5px; position: relative; cursor: ${data.disabled ? 'not-allowed' : 'pointer'}; display: flex; align-items: center; justify-content: center; transition: background-color 0.3s ease; `; const tooltip = document.createElement('div'); tooltip.className = 'filter-tooltip'; tooltip.style.cssText = ` display: none; position: absolute; top: -10px; left: 200px; width: auto; inline-size: 200px; height: auto; background-color: #191B1D; color: white; padding: 5px; border-radius: 5px; white-space: pre-wrap; font-size: 14px; opacity: 1; z-index: 1001; `; // Parse tooltip text and replace **...** with bold HTML tags tooltip.innerHTML = data.tooltip.replace(/\*\*(.*?)\*\*/g, "<b style='color: #068f00;'>$1</b>"); const buttonText = document.createElement('p'); buttonText.style.cssText = ` margin: 0; color: white; font-size: 16px; `; buttonText.textContent = data.name; // Add "DISABLED" style if the button is disabled if (data.disabled) { // Show explanation tooltip (left side like experimental) const disabledTooltip = document.createElement('div'); disabledTooltip.className = 'disabled-tooltip'; disabledTooltip.style.cssText = ` display: none; position: absolute; top: 0; right: 200px; width: 200px; background-color: #191B1D; color: white; padding: 5px; border-radius: 5px; font-size: 14px; white-space: pre-wrap; z-index: 1001; opacity: 1; `; disabledTooltip.innerHTML = data.disabledExplanation.replace(/\*\*(.*?)\*\*/g, '<span style="font-weight: bold; color: #ff5555;">$1</span>'); buttonContainer.appendChild(disabledTooltip); // Add disabled indicator const disabledIndicator = document.createElement('span'); disabledIndicator.textContent = 'DISABLED'; disabledIndicator.style.cssText = ` margin-left: 8px; color: #ff5555; font-size: 10px; font-weight: bold; background-color: rgba(255, 85, 85, 0.1); padding: 1px 4px; border-radius: 3px; `; buttonText.appendChild(disabledIndicator); // Show on hover buttonContainer.addEventListener('mouseenter', () => { disabledTooltip.style.display = 'block'; }); buttonContainer.addEventListener('mouseleave', () => { disabledTooltip.style.display = 'none'; }); } // Add "EXP" label if the button is experimental if (data.experimental) { const expLabel = document.createElement('span'); expLabel.textContent = 'EXP'; expLabel.style.cssText = ` margin-left: 8px; color: gold; font-size: 12px; font-weight: bold; background-color: rgba(255, 215, 0, 0.1); padding: 2px 6px; border-radius: 3px; `; buttonText.appendChild(expLabel); } // Add experimental explanation tooltip (left side) let experimentalTooltip = null; if (data.experimental) { experimentalTooltip = document.createElement('div'); experimentalTooltip.className = 'experimental-tooltip'; experimentalTooltip.style.cssText = ` display: none; position: absolute; top: 0; right: 200px; width: 200px; background-color: #191B1D; color: white; padding: 5px; border-radius: 5px; font-size: 14px; white-space: pre-wrap; z-index: 1001; opacity: 1; `; // Function to replace **text** with bold and gold styled text const formatText = (text) => { return text.replace(/\*\*(.*?)\*\*/g, '<span style="font-weight: bold; color: gold;">$1</span>'); }; // Apply the formatting to the experimental explanation experimentalTooltip.innerHTML = formatText(data.experimentalExplanation); buttonContainer.appendChild(experimentalTooltip); } // Append tooltip directly to button container so it won't inherit opacity buttonContainer.appendChild(tooltip); // Append button text to content wrapper buttonContentWrapper.appendChild(buttonText); // Append content wrapper to button container buttonContainer.appendChild(buttonContentWrapper); buttonContainer.addEventListener('mouseover', () => { tooltip.style.display = 'block'; if (data.experimental) { experimentalTooltip.style.display = 'block'; } // Only change background color on hover if the button is not disabled if (!data.disabled) { buttonContainer.style.backgroundColor = '#4A4C4E'; // Hover effect } }); buttonContainer.addEventListener('mouseout', () => { tooltip.style.display = 'none'; if (data.experimental) { experimentalTooltip.style.display = 'none'; } // Only revert background color if the button is not disabled if (!data.disabled) { buttonContainer.style.backgroundColor = '#393B3D'; // Revert to original color } }); buttonContainer.addEventListener('click', () => { // Prevent click functionality for disabled buttons if (data.disabled) { return; } switch (index) { case 0: smallest_servers(); break; case 1: available_space_servers(); break; case 2: player_count_tab(); break; case 3: random_servers(); break; case 4: createServerCountPopup((totalLimit) => { rebuildServerList(gameId, totalLimit); }); break; case 5: rebuildServerList(gameId, 50, true); break; case 6: auto_join_small_server(); break; case 7: find_user_server_tab(); break; } }); popup.appendChild(buttonContainer); }); return popup; } /******************************************************* name of function: ServerHop description: Handles server hopping by fetching and joining a random server, excluding recently joined servers. *******************************************************/ // Main function to handle the server hopping function ServerHop() { ConsoleLogEnabled("Starting server hop..."); showLoadingOverlay(); // Extract the game ID from the URL const url = window.location.href; const gameId = url.split("/")[4]; // Extracts the game ID, assuming URL is in the format: /games/{gameId}/Title ConsoleLogEnabled(`Game ID: ${gameId}`); // Array to store server IDs let serverIds = []; let nextPageCursor = null; let pagesRequested = 0; // Get the list of all recently joined servers in localStorage const allStoredServers = Object.keys(localStorage) .filter(key => key.startsWith("ROLOCATE_recentServers_")) .map(key => JSON.parse(localStorage.getItem(key))); // Remove any expired servers for all games (older than 15 minutes) const currentTime = new Date().getTime(); allStoredServers.forEach(storedServers => { const validServers = storedServers.filter(server => { const lastJoinedTime = new Date(server.timestamp).getTime(); return (currentTime - lastJoinedTime) <= 15 * 60 * 1000; // 15 minutes }); // Update localStorage with the valid (non-expired) servers localStorage.setItem(`ROLOCATE_recentServers_${gameId}`, JSON.stringify(validServers)); }); // Get the list of recently joined servers for the current game const storedServers = JSON.parse(localStorage.getItem(`ROLOCATE_recentServers_${gameId}`)) || []; // Check if there are any recently joined servers and exclude them from selection const validServers = storedServers.filter(server => { const lastJoinedTime = new Date(server.timestamp).getTime(); return (currentTime - lastJoinedTime) <= 15 * 60 * 1000; // 15 minutes }); if (validServers.length > 0) { ConsoleLogEnabled(`Excluding servers joined in the last 15 minutes: ${validServers.map(s => s.serverId).join(', ')}`); } else { ConsoleLogEnabled("No recently joined servers within the last 15 minutes. Proceeding to pick a new server."); } // Function to fetch servers function fetchServers(cursor) { const url = `https://games.roblox.com/v1/games/${gameId}/servers/0?sortOrder=2&excludeFullGames=true&limit=100${cursor ? `&cursor=${cursor}` : ""}`; GM_xmlhttpRequest({ method: "GET", url: url, onload: function(response) { ConsoleLogEnabled("API Response:", response.responseText); try { const data = JSON.parse(response.responseText); // If there's an error, log it and return without processing if (data.errors) { ConsoleLogEnabled("Skipping unreadable response:", data.errors[0].message); return; } // After a successful request, wait 0.15 seconds before proceeding setTimeout(() => { if (!data || !data.data) { ConsoleLogEnabled("Invalid response structure: 'data' is missing or undefined", data); return; } data.data.forEach(server => { if (validServers.some(vs => vs.serverId === server.id)) { ConsoleLogEnabled(`Skipping previously joined server ${server.id}.`); } else { serverIds.push(server.id); } }); // Fetch next page if available and within limit if (data.nextPageCursor && pagesRequested < 4) { pagesRequested++; ConsoleLogEnabled(`Fetching page ${pagesRequested}...`); fetchServers(data.nextPageCursor); } else { pickRandomServer(); } }, 150); } catch (error) { ConsoleLogEnabled("Error parsing response:", error); } }, onerror: function(error) { ConsoleLogEnabled("Error fetching server data:", error); } }); } // Function to pick a random server and join it function pickRandomServer() { if (serverIds.length > 0) { const randomServerId = serverIds[Math.floor(Math.random() * serverIds.length)]; ConsoleLogEnabled(`Joining server: ${randomServerId}`); // Join the game instance with the selected server ID Roblox.GameLauncher.joinGameInstance(gameId, randomServerId); // Store the selected server ID with the time and date in localStorage const timestamp = new Date().toISOString(); const newServer = { serverId: randomServerId, timestamp }; validServers.push(newServer); // Save the updated list of recently joined servers to localStorage localStorage.setItem(`ROLOCATE_recentServers_${gameId}`, JSON.stringify(validServers)); ConsoleLogEnabled(`Server ${randomServerId} stored with timestamp ${timestamp}`); } else { ConsoleLogEnabled("No servers found to join."); notifications("You have joined all the servers recently. No servers found to join.", "error", "⚠️", "5000"); } } // Start the fetching process fetchServers(); } if (window.location.href.startsWith("https://www.roblox.com/games/")) { window.addEventListener("load", () => { // Extract game ID from URL function findGameId() { const match = window.location.href.match(/games\/(\d+)/); return match ? match[1] : null; } // Auto-click "Servers" tab if enabled in localStorage if (localStorage.ROLOCATE_AutoRunServerRegions === "true") { setTimeout(() => { const serversTab = document.querySelector("#tab-game-instances a"); if (serversTab) { serversTab.click(); } }, 1000); } // Auto-run server regions if enabled in localStorage if (localStorage.ROLOCATE_AutoRunServerRegions === "true") { setTimeout(() => { const gameId = findGameId(); if (gameId) { Loadingbar(true); disableFilterButton(true); disableLoadMoreButton(); rebuildServerList(gameId, 16); } }, 2000); } }); Isongamespage = true; const observer = new MutationObserver((mutations, obs) => { const serverListOptions = document.querySelector('.server-list-options'); const playButton = document.querySelector('.btn-common-play-game-lg.btn-primary-md'); if (serverListOptions && !document.querySelector('.RL-filter-button') && localStorage.getItem("ROLOCATE_togglefilterserversbutton") === "true") { ConsoleLogEnabled("Added Filter Button"); const filterButton = document.createElement('a'); filterButton.className = 'RL-filter-button'; filterButton.style.cssText = ` color: white; font-weight: bold; text-decoration: none; cursor: pointer; margin-left: 10px; padding: 5px 10px; display: flex; align-items: center; gap: 5px; position: relative; margin-top: 4px; `; filterButton.addEventListener('mouseover', () => { filterButton.style.textDecoration = 'underline'; }); filterButton.addEventListener('mouseout', () => { filterButton.style.textDecoration = 'none'; }); const buttonText = document.createElement('span'); buttonText.className = 'RL-filter-text'; buttonText.textContent = 'Filters'; filterButton.appendChild(buttonText); const icon = document.createElement('span'); icon.className = 'RL-filter-icon'; icon.textContent = '≡'; icon.style.cssText = `font-size: 18px;`; filterButton.appendChild(icon); serverListOptions.appendChild(filterButton); let popup = null; filterButton.addEventListener('click', (event) => { event.stopPropagation(); if (popup) { popup.remove(); popup = null; } else { popup = createPopup(); popup.style.top = `${filterButton.offsetHeight}px`; popup.style.left = '0'; filterButton.appendChild(popup); } }); document.addEventListener('click', (event) => { if (popup && !filterButton.contains(event.target)) { popup.remove(); popup = null; } }); } // new condition to trigger recent server logic if (localStorage.getItem("ROLOCATE_togglerecentserverbutton") === "true") { HandleRecentServers(); HandleRecentServersURL(); } if (playButton && !document.querySelector('.custom-play-button') && localStorage.getItem("ROLOCATE_toggleserverhopbutton") === "true") { ConsoleLogEnabled("Added Server Hop Button"); const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = ` display: flex; gap: 10px; align-items: center; width: 100%; `; playButton.style.cssText += ` flex: 3; padding: 10px 12px; text-align: center; `; const serverHopButton = document.createElement('button'); serverHopButton.className = 'custom-play-button'; serverHopButton.style.cssText = ` background-color: #335fff; color: white; border: none; padding: 7.5px 12px; cursor: pointer; font-weight: bold; border-radius: 8px; flex: 1; text-align: center; display: flex; align-items: center; justify-content: center; position: relative; `; const tooltip = document.createElement('div'); tooltip.textContent = 'Join Random Server / Server Hop'; tooltip.style.cssText = ` position: absolute; background-color: rgba(51, 95, 255, 0.8); color: white; padding: 5px 10px; border-radius: 5px; font-size: 12px; visibility: hidden; opacity: 0; transition: opacity 0.2s ease-in-out; bottom: 100%; left: 50%; transform: translateX(-50%); white-space: nowrap; `; serverHopButton.appendChild(tooltip); serverHopButton.addEventListener('mouseover', () => { tooltip.style.visibility = 'visible'; tooltip.style.opacity = '1'; }); serverHopButton.addEventListener('mouseout', () => { tooltip.style.visibility = 'hidden'; tooltip.style.opacity = '0'; }); const logo = document.createElement('img'); logo.src = window.Base64Images.icon_serverhop; logo.style.cssText = ` width: 45px; height: 45px; `; serverHopButton.appendChild(logo); playButton.parentNode.insertBefore(buttonContainer, playButton); buttonContainer.appendChild(playButton); buttonContainer.appendChild(serverHopButton); serverHopButton.addEventListener('click', () => { ServerHop(); }); } const filterEnabled = localStorage.getItem("ROLOCATE_togglefilterserversbutton") === "true"; const hopEnabled = localStorage.getItem("ROLOCATE_toggleserverhopbutton") === "true"; const recentEnabled = localStorage.getItem("ROLOCATE_togglerecentserverbutton") === "true"; const filterPresent = !filterEnabled || document.querySelector('.RL-filter-button'); const hopPresent = !hopEnabled || document.querySelector('.custom-play-button'); const recentPresent = !recentEnabled || document.querySelector('.recent-servers-section'); if (filterPresent && hopPresent && recentPresent) { obs.disconnect(); ConsoleLogEnabled("Disconnected Observer"); } }); observer.observe(document.body, { childList: true, subtree: true }); } /********************************************************************************************************************************************************************************************************************************************* The End of: This is all of the functions for the filter button and the popup for the 8 buttons does not include the functions for the 8 buttons *********************************************************************************************************************************************************************************************************************************************/ /********************************************************************************************************************************************************************************************************************************************* Functions for the 1st button *********************************************************************************************************************************************************************************************************************************************/ /******************************************************* name of function: smallest_servers description: Fetches the smallest servers, disables the "Load More" button, shows a loading bar, and recreates the server cards. *******************************************************/ async function smallest_servers() { // Disable the "Load More" button and show the loading bar Loadingbar(true); disableFilterButton(true); disableLoadMoreButton(); notifications("Finding small servers...", "success", "🧐"); // Get the game ID from the URL const gameId = window.location.pathname.split('/')[2]; // Retry mechanism let retries = 3; let success = false; while (retries > 0 && !success) { try { // Use GM_xmlhttpRequest to fetch server data from the Roblox API const response = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: `https://games.roblox.com/v1/games/${gameId}/servers/0?sortOrder=1&excludeFullGames=true&limit=100`, onload: function(response) { if (response.status === 429) { reject(new Error('429: Too Many Requests')); } else if (response.status >= 200 && response.status < 300) { resolve(response); } else { reject(new Error(`HTTP error! status: ${response.status}`)); } }, onerror: function(error) { reject(error); } }); }); const data = JSON.parse(response.responseText); // Process each server for (const server of data.data) { const { id: serverId, playerTokens, maxPlayers, playing } = server; // Pass the server data to the card creation function await rbx_card(serverId, playerTokens, maxPlayers, playing, gameId); } success = true; // Mark as successful if no errors occurred } catch (error) { retries--; // Decrement the retry count if (error.message === '429: Too Many Requests' && retries > 0) { ConsoleLogEnabled('Encountered a 429 error. Retrying in 5 seconds...'); await new Promise(resolve => setTimeout(resolve, 5000)); // Wait for 5 seconds } else { ConsoleLogEnabled('Error fetching server data:', error); notifications('Error: Failed to fetch server data. Please try again later.', 'error', '⚠️', '5000'); Loadingbar(false); break; // Exit the loop if it's not a 429 error or no retries left } } finally { if (success || retries === 0) { // Hide the loading bar and enable the filter button Loadingbar(false); disableFilterButton(false); } } } } /********************************************************************************************************************************************************************************************************************************************* Functions for the 2nd button *********************************************************************************************************************************************************************************************************************************************/ /******************************************************* name of function: available_space_servers description: Fetches servers with available space, disables the "Load More" button, shows a loading bar, and recreates the server cards. *******************************************************/ async function available_space_servers() { // Disable the "Load More" button and show the loading bar Loadingbar(true); disableLoadMoreButton(); disableFilterButton(true); notifications("Finding servers with space...", "success", "🧐"); // Get the game ID from the URL const gameId = window.location.pathname.split('/')[2]; // Retry mechanism let retries = 3; let success = false; while (retries > 0 && !success) { try { // Use GM_xmlhttpRequest to fetch server data from the Roblox API const response = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: `https://games.roblox.com/v1/games/${gameId}/servers/0?sortOrder=2&excludeFullGames=true&limit=100`, onload: function(response) { if (response.status === 429) { reject(new Error('429: Too Many Requests')); } else if (response.status >= 200 && response.status < 300) { resolve(response); } else { reject(new Error(`HTTP error! status: ${response.status}`)); } }, onerror: function(error) { reject(error); } }); }); const data = JSON.parse(response.responseText); // Process each server for (const server of data.data) { const { id: serverId, playerTokens, maxPlayers, playing } = server; // Pass the server data to the card creation function await rbx_card(serverId, playerTokens, maxPlayers, playing, gameId); } success = true; // Mark as successful if no errors occurred } catch (error) { retries--; // Decrement the retry count if (error.message === '429: Too Many Requests' && retries > 0) { ConsoleLogEnabled('Encountered a 429 error. Retrying in 10 seconds...'); await new Promise(resolve => setTimeout(resolve, 10000)); // Wait for 10 seconds } else { ConsoleLogEnabled('Error fetching server data:', error); break; // Exit the loop if it's not a 429 error or no retries left } } finally { if (success || retries === 0) { // Hide the loading bar and enable the filter button Loadingbar(false); disableFilterButton(false); } } } } /********************************************************************************************************************************************************************************************************************************************* Functions for the 3rd button *********************************************************************************************************************************************************************************************************************************************/ /******************************************************* name of function: player_count_tab description: Opens a popup for the user to select the max player count using a slider and filters servers accordingly. *******************************************************/ function player_count_tab() { // Check if the max player count has already been determined if (!player_count_tab.maxPlayers) { // Try to find the element containing the player count information const playerCountElement = document.querySelector('.text-info.rbx-game-status.rbx-game-server-status.text-overflow'); if (playerCountElement) { const playerCountText = playerCountElement.textContent.trim(); const match = playerCountText.match(/(\d+) of (\d+) people max/); if (match) { const maxPlayers = parseInt(match[2], 10); if (!isNaN(maxPlayers) && maxPlayers > 1) { player_count_tab.maxPlayers = maxPlayers; ConsoleLogEnabled("Found text element with max playercount"); } } } else { // If the element is not found, extract the gameId from the URL const gameIdMatch = window.location.href.match(/games\/(\d+)/); if (gameIdMatch && gameIdMatch[1]) { const gameId = gameIdMatch[1]; // Send a request to the Roblox API to get server information GM_xmlhttpRequest({ method: 'GET', url: `https://games.roblox.com/v1/games/${gameId}/servers/public?sortOrder=1&excludeFullGames=true&limit=100`, onload: function(response) { try { if (response.status === 429) { // Rate limit error, default to 100 ConsoleLogEnabled("Rate limited defaulting to 100."); player_count_tab.maxPlayers = 100; } else { ConsoleLogEnabled("Valid api response"); const data = JSON.parse(response.responseText); if (data.data && data.data.length > 0) { const maxPlayers = data.data[0].maxPlayers; if (!isNaN(maxPlayers) && maxPlayers > 1) { player_count_tab.maxPlayers = maxPlayers; } } } // Update the slider range if the popup is already created const slider = document.querySelector('.player-count-popup input[type="range"]'); if (slider) { slider.max = player_count_tab.maxPlayers ? (player_count_tab.maxPlayers - 1).toString() : '100'; slider.style.background = ` linear-gradient( to right, #00A2FF 0%, #00A2FF ${slider.value}%, #444 ${slider.value}%, #444 100% ); `; } } catch (error) { ConsoleLogEnabled('Failed to parse API response:', error); // Default to 100 if parsing fails player_count_tab.maxPlayers = 100; const slider = document.querySelector('.player-count-popup input[type="range"]'); if (slider) { slider.max = '100'; slider.style.background = ` linear-gradient( to right, #00A2FF 0%, #00A2FF ${slider.value}%, #444 ${slider.value}%, #444 100% ); `; } } }, onerror: function(error) { ConsoleLogEnabled('Failed to fetch server information:', error); ConsoleLogEnabled('Fallback to 100 players.'); // Default to 100 if the request fails player_count_tab.maxPlayers = 100; const slider = document.querySelector('.player-count-popup input[type="range"]'); if (slider) { slider.max = '100'; slider.style.background = ` linear-gradient( to right, #00A2FF 0%, #00A2FF ${slider.value}%, #444 ${slider.value}%, #444 100% ); `; } } }); } } } // Create the overlay (backdrop) const overlay = document.createElement('div'); overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 9999; opacity: 0; transition: opacity 0.3s ease; `; document.body.appendChild(overlay); // Create the popup container const popup = document.createElement('div'); popup.className = 'player-count-popup'; popup.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: rgb(30, 32, 34); padding: 20px; border-radius: 10px; z-index: 10000; box-shadow: 0 0 15px rgba(0, 0, 0, 0.7); display: flex; flex-direction: column; align-items: center; gap: 15px; width: 300px; opacity: 0; transition: opacity 0.3s ease, transform 0.3s ease; `; // Add a close button in the top-right corner (bigger size) const closeButton = document.createElement('button'); closeButton.innerHTML = '×'; // Using '×' for the close icon closeButton.style.cssText = ` position: absolute; top: 10px; right: 10px; background: transparent; border: none; color: #ffffff; font-size: 24px; /* Increased font size */ cursor: pointer; width: 36px; /* Increased size */ height: 36px; /* Increased size */ border-radius: 50%; display: flex; align-items: center; justify-content: center; transition: background-color 0.3s ease, color 0.3s ease; `; closeButton.addEventListener('mouseenter', () => { closeButton.style.backgroundColor = 'rgba(255, 255, 255, 0.1)'; closeButton.style.color = '#ff4444'; }); closeButton.addEventListener('mouseleave', () => { closeButton.style.backgroundColor = 'transparent'; closeButton.style.color = '#ffffff'; }); // Add a title const title = document.createElement('h3'); title.textContent = 'Select Max Player Count'; title.style.cssText = ` color: white; margin: 0; font-size: 18px; font-weight: 500; `; popup.appendChild(title); // Add a slider with improved functionality and styling const slider = document.createElement('input'); slider.type = 'range'; slider.min = '1'; slider.max = player_count_tab.maxPlayers ? (player_count_tab.maxPlayers - 1).toString() : '100'; slider.value = '1'; // Default value slider.step = '1'; // Step for better accuracy slider.style.cssText = ` width: 80%; cursor: pointer; margin: 10px 0; -webkit-appearance: none; /* Remove default styling */ background: transparent; `; // Custom slider track slider.style.background = ` linear-gradient( to right, #00A2FF 0%, #00A2FF ${slider.value}%, #444 ${slider.value}%, #444 100% ); border-radius: 5px; height: 6px; `; // Custom slider thumb slider.style.setProperty('--thumb-size', '20px'); /* Larger thumb */ slider.style.setProperty('--thumb-color', '#00A2FF'); slider.style.setProperty('--thumb-hover-color', '#0088cc'); slider.style.setProperty('--thumb-border', '2px solid #fff'); slider.style.setProperty('--thumb-shadow', '0 0 5px rgba(0, 0, 0, 0.5)'); slider.addEventListener('input', () => { slider.style.background = ` linear-gradient( to right, #00A2FF 0%, #00A2FF ${slider.value}%, #444 ${slider.value}%, #444 100% ); `; sliderValue.textContent = slider.value; // Update the displayed value }); // Keyboard support for better accuracy (fixed to increment/decrement by 1) slider.addEventListener('keydown', (e) => { e.preventDefault(); // Prevent default behavior (which might cause jumps) let newValue = parseInt(slider.value, 10); if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') { newValue = Math.max(1, newValue - 1); // Decrease by 1 } else if (e.key === 'ArrowRight' || e.key === 'ArrowUp') { newValue = Math.min(100, newValue + 1); // Increase by 1 } slider.value = newValue; slider.dispatchEvent(new Event('input')); // Trigger input event to update UI }); popup.appendChild(slider); // Add a display for the slider value const sliderValue = document.createElement('span'); sliderValue.textContent = slider.value; sliderValue.style.cssText = ` color: white; font-size: 16px; font-weight: bold; `; popup.appendChild(sliderValue); // Add a submit button with dark, blackish style const submitButton = document.createElement('button'); submitButton.textContent = 'Search'; submitButton.style.cssText = ` padding: 8px 20px; font-size: 16px; background-color: #1a1a1a; /* Dark blackish color */ color: white; border: none; border-radius: 5px; cursor: pointer; transition: background-color 0.3s ease, transform 0.2s ease; `; submitButton.addEventListener('mouseenter', () => { submitButton.style.backgroundColor = '#333'; /* Slightly lighter on hover */ submitButton.style.transform = 'scale(1.05)'; }); submitButton.addEventListener('mouseleave', () => { submitButton.style.backgroundColor = '#1a1a1a'; submitButton.style.transform = 'scale(1)'; }); // Add a yellow box with a tip under the submit button const tipBox = document.createElement('div'); tipBox.style.cssText = ` width: 100%; padding: 10px; background-color: rgba(255, 204, 0, 0.15); border-radius: 5px; text-align: center; font-size: 14px; color: #ffcc00; transition: background-color 0.3s ease; `; tipBox.textContent = 'Tip: Click the slider and use the arrow keys for more accuracy.'; tipBox.addEventListener('mouseenter', () => { tipBox.style.backgroundColor = 'rgba(255, 204, 0, 0.25)'; }); tipBox.addEventListener('mouseleave', () => { tipBox.style.backgroundColor = 'rgba(255, 204, 0, 0.15)'; }); popup.appendChild(tipBox); // Append the popup to the body document.body.appendChild(popup); // Fade in the overlay and popup setTimeout(() => { overlay.style.opacity = '1'; popup.style.opacity = '1'; popup.style.transform = 'translate(-50%, -50%) scale(1)'; }, 10); /******************************************************* name of function: fadeOutAndRemove description: Fades out and removes the popup and overlay. *******************************************************/ function fadeOutAndRemove(popup, overlay) { popup.style.opacity = '0'; popup.style.transform = 'translate(-50%, -50%) scale(0.9)'; overlay.style.opacity = '0'; setTimeout(() => { popup.remove(); overlay.remove(); }, 300); // Match the duration of the transition } // Close the popup when clicking outside overlay.addEventListener('click', () => { fadeOutAndRemove(popup, overlay); }); // Close the popup when the close button is clicked closeButton.addEventListener('click', () => { fadeOutAndRemove(popup, overlay); }); // Handle submit button click submitButton.addEventListener('click', () => { const maxPlayers = parseInt(slider.value, 10); if (!isNaN(maxPlayers) && maxPlayers > 0) { filterServersByPlayerCount(maxPlayers); fadeOutAndRemove(popup, overlay); } else { notifications('Error: Please enter a number greater than 0', 'error', '⚠️', '5000'); } }); popup.appendChild(submitButton); popup.appendChild(closeButton); } /******************************************************* name of function: fetchServersWithRetry description: Fetches server data with retry logic and a delay between requests to avoid rate-limiting. Uses GM_xmlhttpRequest instead of fetch. *******************************************************/ async function fetchServersWithRetry(url, retries = 15, currentDelay = 750) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) { // Check for 429 Rate Limit error if (response.status === 429) { if (retries > 0) { const newDelay = currentDelay * 1; // Exponential backoff ConsoleLogEnabled(`[DEBUG] Rate limited. Waiting ${newDelay / 1000} seconds before retrying...`); setTimeout(() => { resolve(fetchServersWithRetry(url, retries - 1, newDelay)); // Retry with increased delay }, newDelay); } else { ConsoleLogEnabled('[DEBUG] Rate limit retries exhausted.'); notifications('Error: Rate limited please try again later.', 'error', '⚠️', '5000') reject(new Error('RateLimit')); } return; } // Handle other HTTP errors if (response.status < 200 || response.status >= 300) { ConsoleLogEnabled('[DEBUG] HTTP error:', response.status, response.statusText); reject(new Error(`HTTP error: ${response.status}`)); return; } // Parse and return the JSON data try { const data = JSON.parse(response.responseText); ConsoleLogEnabled('[DEBUG] Fetched data successfully:', data); resolve(data); } catch (error) { ConsoleLogEnabled('[DEBUG] Error parsing JSON:', error); reject(error); } }, onerror: function(error) { ConsoleLogEnabled('[DEBUG] Error in GM_xmlhttpRequest:', error); reject(error); } }); }); } /******************************************************* name of function: filterServersByPlayerCount description: Filters servers to show only those with a player count equal to or below the specified max. If no exact matches are found, prioritizes servers with player counts lower than the input. Keeps fetching until at least 8 servers are found, with a dynamic delay between requests. *******************************************************/ async function filterServersByPlayerCount(maxPlayers) { // Validate maxPlayers before proceeding if (isNaN(maxPlayers) || maxPlayers < 1 || !Number.isInteger(maxPlayers)) { ConsoleLogEnabled('[DEBUG] Invalid input for maxPlayers.'); notifications('Error: Please input a valid whole number greater than or equal to 1.', 'error', '⚠️', '5000'); return; } // Disable UI elements and clear the server list Loadingbar(true); disableLoadMoreButton(); disableFilterButton(true); const serverList = document.querySelector('#rbx-public-game-server-item-container'); serverList.innerHTML = ''; const gameId = window.location.pathname.split('/')[2]; let cursor = null; let serversFound = 0; let serverMaxPlayers = null; let isCloserToOne = null; let topDownServers = []; // Servers collected during top-down search let bottomUpServers = []; // Servers collected during bottom-up search let currentDelay = 500; // Initial delay of 0.5 seconds const timeLimit = 3 * 60 * 1000; // 3 minutes in milliseconds const startTime = Date.now(); // Record the start time notifications('Will search for a maximum of 3 minutes to find a server.', 'success', '🔎', '5000'); try { while (serversFound < 16) { // Check if the time limit has been exceeded if (Date.now() - startTime > timeLimit) { ConsoleLogEnabled('[DEBUG] Time limit reached. Proceeding to fallback servers.'); notifications('Warning: Time limit reached. Proceeding to fallback servers.', 'warning', '❗', '5000'); break; } // Fetch initial data to determine serverMaxPlayers and isCloserToOne if (!serverMaxPlayers) { const initialUrl = cursor ? `https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=100&cursor=${cursor}` : `https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=100`; const initialData = await fetchServersWithRetry(initialUrl); if (initialData.data.length > 0) { serverMaxPlayers = initialData.data[0].maxPlayers; isCloserToOne = maxPlayers <= (serverMaxPlayers / 2); } else { notifications("No servers found in initial fetch.", "error", "⚠️", "5000") ConsoleLogEnabled('[DEBUG] No servers found in initial fetch.', 'warning', '❗'); break; } } // Validate maxPlayers against serverMaxPlayers if (maxPlayers >= serverMaxPlayers) { ConsoleLogEnabled('[DEBUG] Invalid input: maxPlayers is greater than or equal to serverMaxPlayers.'); notifications(`Error: Please input a number between 1 through ${serverMaxPlayers - 1}`, 'error', '⚠️', '5000'); return; } // Adjust the URL based on isCloserToOne const baseUrl = isCloserToOne ? `https://games.roblox.com/v1/games/${gameId}/servers/public?sortOrder=1&excludeFullGames=true&limit=100` : `https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=100`; // why does this work lmao const url = cursor ? `${baseUrl}&cursor=${cursor}` : baseUrl; const data = await fetchServersWithRetry(url); // Safety check: Ensure the server list is valid and iterable if (!Array.isArray(data.data)) { ConsoleLogEnabled('[DEBUG] Invalid server list received. Waiting 1 second before retrying...'); await delay(1000); // Wait 1 second before retrying continue; // Skip the rest of the loop and retry } // Filter and process servers for (const server of data.data) { if (server.playing === maxPlayers) { await rbx_card(server.id, server.playerTokens, server.maxPlayers, server.playing, gameId); serversFound++; if (serversFound >= 16) { break; } } else if (!isCloserToOne && server.playing > maxPlayers) { topDownServers.push(server); // Add to top-down fallback list } else if (isCloserToOne && server.playing < maxPlayers) { bottomUpServers.push(server); // Add to bottom-up fallback list } } // Exit if no more servers are available if (!data.nextPageCursor) { break; } cursor = data.nextPageCursor; // Adjust delay dynamically if (currentDelay > 150) { currentDelay = Math.max(150, currentDelay / 2); // Gradually reduce delay } ConsoleLogEnabled(`[DEBUG] Waiting ${currentDelay / 1000} seconds before next request...`); await delay(currentDelay); } // If no exact matches were found or time limit reached, use fallback servers if (serversFound === 0 && (topDownServers.length > 0 || bottomUpServers.length > 0)) { notifications(`There are no servers with ${maxPlayers} players. Showing servers closest to ${maxPlayers} players.`, 'warning', '😔', '8000'); // Sort top-down servers by player count (ascending) topDownServers.sort((a, b) => a.playing - b.playing); // Sort bottom-up servers by player count (descending) bottomUpServers.sort((a, b) => b.playing - a.playing); // Combine both fallback lists (prioritize top-down servers first) const combinedFallback = [...topDownServers, ...bottomUpServers]; for (const server of combinedFallback) { await rbx_card(server.id, server.playerTokens, server.maxPlayers, server.playing, gameId); serversFound++; if (serversFound >= 16) { break; } } } if (serversFound <= 0) { notifications('No Servers Found Within The Provided Criteria', 'info', '🔎', '5000'); } } catch (error) { ConsoleLogEnabled('[DEBUG] Error in filterServersByPlayerCount:', error); } finally { Loadingbar(false); disableFilterButton(false); } } /********************************************************************************************************************************************************************************************************************************************* Functions for the 4th button *********************************************************************************************************************************************************************************************************************************************/ /******************************************************* name of function: random_servers description: Fetches servers from two different URLs, combines the results, ensures no duplicates, shuffles the list, and passes the server information to the rbx_card function in a random order. Handles 429 errors with retries. *******************************************************/ async function random_servers() { notifications('Finding Random Servers. Please wait 2-5 seconds', 'success', '🔎', '5000'); // Disable the "Load More" button and show the loading bar Loadingbar(true); disableFilterButton(true); disableLoadMoreButton(); // Get the game ID from the URL const gameId = window.location.pathname.split('/')[2]; try { // Fetch servers from the first URL with retry logic const firstUrl = `https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=10`; const firstData = await fetchWithRetry(firstUrl, 10); // Retry up to 3 times // Wait for 5 seconds await delay(1500); // Fetch servers from the second URL with retry logic const secondUrl = `https://games.roblox.com/v1/games/${gameId}/servers/public?sortOrder=1&excludeFullGames=true&limit=10`; const secondData = await fetchWithRetry(secondUrl, 10); // Retry up to 3 times // Combine the servers from both URLs const combinedServers = [...firstData.data, ...secondData.data]; // Remove duplicates by server ID const uniqueServers = []; const seenServerIds = new Set(); for (const server of combinedServers) { if (!seenServerIds.has(server.id)) { seenServerIds.add(server.id); uniqueServers.push(server); } } // Shuffle the unique servers array const shuffledServers = shuffleArray(uniqueServers); // Get the first 16 shuffled servers const selectedServers = shuffledServers.slice(0, 16); // Process each server in random order for (const server of selectedServers) { const { id: serverId, playerTokens, maxPlayers, playing } = server; // Pass the server data to the card creation function await rbx_card(serverId, playerTokens, maxPlayers, playing, gameId); } } catch (error) { ConsoleLogEnabled('Error fetching server data:', error); notifications('Error: Failed to fetch server data. Please try again later.', 'error', '⚠️', '5000'); } finally { // Hide the loading bar and enable the filter button Loadingbar(false); disableFilterButton(false); } } /******************************************************* name of function: fetchWithRetry description: Fetches data from a URL with retry logic for 429 errors using GM_xmlhttpRequest. *******************************************************/ function fetchWithRetry(url, retries) { return new Promise((resolve, reject) => { const attemptFetch = (attempt = 0) => { GM_xmlhttpRequest({ method: "GET", url: url, onload: function(response) { if (response.status === 429) { if (attempt < retries) { ConsoleLogEnabled(`Rate limited. Retrying in 2.5 seconds... (Attempt ${attempt + 1}/${retries})`); setTimeout(() => attemptFetch(attempt + 1), 1500); // Wait 1.5 seconds and retry } else { reject(new Error('Rate limit exceeded after retries')); } } else if (response.status >= 200 && response.status < 300) { try { const data = JSON.parse(response.responseText); resolve(data); } catch (error) { reject(new Error('Failed to parse JSON response')); } } else { reject(new Error(`HTTP error: ${response.status}`)); } }, onerror: function(error) { if (attempt < retries) { ConsoleLogEnabled(`Error occurred. Retrying in 10 seconds... (Attempt ${attempt + 1}/${retries})`); setTimeout(() => attemptFetch(attempt + 1), 10000); // Wait 10 seconds and retry } else { reject(error); } } }); }; attemptFetch(); }); } /******************************************************* name of function: shuffleArray description: Shuffles an array using the Fisher-Yates algorithm. *******************************************************/ function shuffleArray(array) { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); // Random index from 0 to i [array[i], array[j]] = [array[j], array[i]]; // Swap elements } return array; } /********************************************************************************************************************************************************************************************************************************************* Functions for the 5th button. taken from my other project *********************************************************************************************************************************************************************************************************************************************/ if (Isongamespage) { // Create a <style> element const style = document.createElement('style'); style.textContent = ` /* Overlay for the modal background */ .overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.85); /* Solid black overlay */ z-index: 1000; /* Ensure overlay is below the popup */ opacity: 0; /* Start invisible */ animation: fadeIn 0.3s ease forwards; /* Fade-in animation */ } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } /* Popup Container for the server region */ .filter-popup { background-color: #1e1e1e; /* Darker background */ color: #ffffff; /* White text */ padding: 25px; border-radius: 12px; box-shadow: 0 8px 20px rgba(0, 0, 0, 0.5); width: 320px; max-width: 90%; position: fixed; /* Fixed positioning */ top: 50%; /* Center vertically */ left: 50%; /* Center horizontally */ transform: translate(-50%, -50%); /* Offset to truly center */ text-align: center; z-index: 1001; /* Ensure popup is above the overlay */ border: 1px solid #444; /* Subtle border */ opacity: 0; /* Start invisible */ animation: fadeInPopup 0.3s ease 0.1s forwards; /* Fade-in animation with delay */ } @keyframes fadeInPopup { from { opacity: 0; transform: translate(-50%, -55%); /* Slight upward offset */ } to { opacity: 1; transform: translate(-50%, -50%); /* Center position */ } } /* Fade-out animation for overlay and popup */ .overlay.fade-out { animation: fadeOut 0.3s ease forwards; } .filter-popup.fade-out { animation: fadeOutPopup 0.3s ease forwards; } @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } @keyframes fadeOutPopup { from { opacity: 1; transform: translate(-50%, -50%); /* Center position */ } to { opacity: 0; transform: translate(-50%, -55%); /* Slight upward offset */ } } /* Close Button for the server selector */ #closePopup { position: absolute; top: 5px; /* Reduced from 12px to 5px */ right: 1px; /* Reduced from 12px to 5px */ background: transparent; /* Transparent background */ border: none; color: #ffffff; /* White color */ font-size: 20px; cursor: pointer; width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; transition: background-color 0.3s ease, color 0.3s ease; } #closePopup:hover { background-color: rgba(255, 255, 255, 0.1); /* Light hover effect */ color: #ff4444; /* Red color on hover */ } /* Label */ .filter-popup label { display: block; margin-bottom: 12px; font-size: 16px; color: #ffffff; font-weight: 500; /* Slightly bolder text */ } /* Dropdown */ .filter-popup select { background-color: #333; /* Darker gray background */ color: #ffffff; /* White text */ padding: 10px; border-radius: 6px; border: 1px solid #555; /* Darker border */ width: 100%; margin-bottom: 12px; font-size: 14px; transition: border-color 0.3s ease; } .filter-popup select:focus { border-color: #888; /* Lighter border on focus */ outline: none; } /* Custom Input */ .filter-popup input[type="number"] { background-color: #333; /* Darker gray background */ color: #ffffff; /* White text */ padding: 10px; border-radius: 6px; border: 1px solid #555; /* Darker border */ width: 100%; margin-bottom: 12px; font-size: 14px; transition: border-color 0.3s ease; } .filter-popup input[type="number"]:focus { border-color: #888; /* Lighter border on focus */ outline: none; } /* Confirm Button */ #confirmServerCount { background-color: #444; /* Dark gray background */ color: #ffffff; /* White text */ padding: 10px 20px; border: 1px solid #666; /* Gray border */ border-radius: 6px; cursor: pointer; font-size: 14px; width: 100%; transition: background-color 0.3s ease, transform 0.2s ease; } #confirmServerCount:hover { background-color: #555; /* Lighter gray on hover */ transform: translateY(-1px); /* Slight lift effect */ } #confirmServerCount:active { transform: translateY(0); /* Reset lift effect on click */ } /* Highlighted server item */ .rbx-game-server-item.highlighted { border: 2px solid #4caf50; /* Green border */ border-radius: 8px; background-color: rgba(76, 175, 80, 0.1); /* Subtle green background */ } /* Disabled fetch button */ .fetch-button:disabled { opacity: 0.5; cursor: not-allowed; } /* Popup Header */ .popup-header { margin-bottom: 24px; text-align: left; padding: 16px; background-color: rgba(255, 255, 255, 0.05); /* Subtle background for contrast */ border-radius: 8px; border: 1px solid rgba(255, 255, 255, 0.1); /* Subtle border */ transition: background-color 0.3s ease, border-color 0.3s ease; } .popup-header:hover { background-color: rgba(255, 255, 255, 0.08); /* Slightly brighter on hover */ border-color: rgba(255, 255, 255, 0.2); } .popup-header h3 { margin: 0 0 12px 0; font-size: 22px; color: #ffffff; font-weight: 700; /* Bolder for emphasis */ letter-spacing: -0.5px; /* Tighter letter spacing for modern look */ } .popup-header p { margin: 0; font-size: 14px; color: #cccccc; line-height: 1.6; /* Improved line height for readability */ opacity: 0.9; /* Slightly transparent for a softer look */ } /* Popup Footer */ .popup-footer { margin-top: 20px; text-align: left; font-size: 14px; color: #ffcc00; /* Yellow color for warnings */ background-color: rgba(255, 204, 0, 0.15); /* Lighter yellow background */ padding: 12px; border-radius: 8px; border: 1px solid rgba(255, 204, 0, 0.15); /* Subtle border */ transition: background-color 0.3s ease, border-color 0.3s ease; } .popup-footer:hover { background-color: rgba(255, 204, 0, 0.25); /* Slightly brighter on hover */ border-color: rgba(255, 204, 0, 0.25); } .popup-footer p { margin: 0; line-height: 1.5; font-weight: 500; /* Slightly bolder for emphasis */ } /* Label */ .filter-popup label { display: block; margin-bottom: 12px; font-size: 15px; color: #ffffff; font-weight: 500; text-align: left; opacity: 0.9; /* Slightly transparent for a softer look */ transition: opacity 0.3s ease; } .filter-popup label:hover { opacity: 1; /* Fully opaque on hover */ } select:hover, select:focus { border-color: #ffffff; outline: none; } `; // Append the <style> element to the document head document.head.appendChild(style); } function showMessage(message) { const loadMoreButtonContainer = document.querySelector('.rbx-public-running-games-footer'); if (!loadMoreButtonContainer) { ConsoleLogEnabled("Error: 'Load More' button container not found! Ensure the element exists in the DOM."); return; } const existingMessage = loadMoreButtonContainer.querySelector('.premium-message-container'); // If message is "END", remove any existing message and exit if (message === "END") { if (existingMessage) { existingMessage.remove(); ConsoleLogEnabled("Message container removed."); } else { ConsoleLogEnabled("No message container found to remove."); } return; } // Remove existing message if present before showing a new one if (existingMessage) { existingMessage.remove(); ConsoleLogEnabled("Warning: An existing message was found and replaced."); } // Inject CSS only once if (!document.getElementById('premium-message-styles')) { const style = document.createElement('style'); style.id = 'premium-message-styles'; style.textContent = ` .premium-message-container { margin-top: 20px; padding: 18px 26px; background: linear-gradient(145deg, #2b0000, #1a0000); border-radius: 14px; box-shadow: 0 6px 20px rgba(255, 0, 0, 0.2); font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-size: 16px; color: #ffdddd; transition: all 0.3s ease-in-out, transform 0.3s ease, box-shadow 0.3s ease; opacity: 0; animation: fadeIn 0.6s ease forwards; border: 1px solid #440000; backdrop-filter: blur(6px); display: flex; align-items: center; gap: 16px; cursor: default; user-select: none; } .premium-message-container:hover { transform: scale(1.015); box-shadow: 0 8px 24px rgba(255, 0, 0, 0.25); background: linear-gradient(145deg, #330000, #220000); color: #ffe5e5; } .premium-message-logo { width: 28px; height: 28px; border-radius: 6px; object-fit: contain; box-shadow: 0 0 8px rgba(255, 0, 0, 0.2); background-color: #000; } .premium-message-text { flex: 1; text-align: left; font-weight: 500; letter-spacing: 0.3px; } @keyframes fadeIn { to { opacity: 1; } } `; document.head.appendChild(style); } // Create the message container const container = document.createElement('div'); container.className = 'premium-message-container'; // Create and insert the logo const logo = document.createElement('img'); logo.className = 'premium-message-logo'; logo.src = window.Base64Images.logo; // Create and insert the message text const messageText = document.createElement('div'); messageText.className = 'premium-message-text'; messageText.textContent = message; // Build the full component container.appendChild(logo); container.appendChild(messageText); loadMoreButtonContainer.appendChild(container); ConsoleLogEnabled("Message displayed successfully:", message); return container; } // Function to show the popup for random stuff function showPopup() { const overlay = document.createElement('div'); overlay.className = 'overlay'; const popup = document.createElement('div'); popup.className = 'filter-popup'; popup.textContent = 'Uh somethings wrong if you see this message. Please report to the greasyfork issues page!'; document.body.appendChild(overlay); document.body.appendChild(popup); return popup; } // Function to hide the popup for the stuff function hidePopup() { const popup = document.querySelector('.filter-popup'); const overlay = document.querySelector('.overlay'); if (popup) popup.remove(); if (overlay) overlay.remove(); } // Function to fetch server details so game id and job id. yea! async function fetchServerDetails(gameId, jobId) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "POST", url: "https://gamejoin.roblox.com/v1/join-game-instance", // url for game id headers: { // doesent need cookie cuase of magic "Content-Type": "application/json", "User-Agent": "Roblox/WinInet", }, data: JSON.stringify({ placeId: gameId, gameId: jobId }), onload: function(response) { const json = JSON.parse(response.responseText); ConsoleLogEnabled("API Response:", json); // This prints the full response // Check if the response indicates that the user needs to purchase the game if (json.status === 12 && json.message === 'You need to purchase access to this game before you can play.') { // yea error message! reject('purchase_required'); // Special error code for this case yea! return; } // Check if the response indicates that the user needs to purchase the game if (json.status === 12 && json.message === 'Cannot join this non-root place due to join restrictions') { // yea error message! reject('subplace_join_restriction'); // Special error code for this case yea! return; } const address = json?.joinScript?.UdmuxEndpoints?.[0]?.Address ?? json?.joinScript?.MachineAddress; if (!address) { ConsoleLogEnabled("API Response (Unknown Location) Which means Full Server!:", json); // Log the API response for debug reject(`Unable to fetch server location: Status ${json.status}`); // debug return; } const location = serverRegionsByIp[address.replace(/^(128\.116\.\d+)\.\d+$/, "$1.0")]; // lmao all servers atart with this so yea dont argue with me if (!location) { ConsoleLogEnabled("API Response (Unknown Location):", json); // Log the API response into the chat. might remove it from production but idc rn reject(`Unknown server address ${address}`); return; } resolve(location); }, onerror: function(error) { ConsoleLogEnabled("API Request Failed:", error); // damn if this happpens idk what to tell u reject(`Failed to fetch server details: ${error}`); }, }); }); } // cusomt delay also known as sleep fucntion in js cause this language sucks and doesent have a default function function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // Function to create a popup for selecting the number of servers // basically yea thats what it doesent function createServerCountPopup(callback) { const overlay = document.createElement('div'); overlay.className = 'overlay'; const popup = document.createElement('div'); popup.className = 'filter-popup'; // Inject styles for dropdown icon const style = document.createElement('style'); style.textContent = ` .dropdown-wrapper { position: relative; display: inline-block; width: 100%; } .dropdown-wrapper select { width: 100%; padding-right: 30px; appearance: none; -webkit-appearance: none; -moz-appearance: none; } .dropdown-wrapper .dropdown-icon { position: absolute; right: 10px; top: 40%; transform: translateY(-50%); pointer-events: none; font-size: 12px; color: #fff; } `; document.head.appendChild(style); popup.innerHTML = ` <button id="closePopup">X</button> <div class="popup-header"> <h3>Select Number of Servers</h3> <p>Choose how many servers you want to search. Higher values will provide more location variety but may take longer to process.</p> <div class="popup-footer"> <p><strong>Note:</strong> Searching over 100 servers may take longer and could result in rate limiting.</p> </div> </div> <label for="serverCount">Select Number of Servers:</label> <div class="dropdown-wrapper"> <select id="serverCount"> <option value="10">10 Servers</option> <option value="25" selected>25 Servers</option> <option value="50">50 Servers</option> <option value="100">100 Servers</option> <option value="200">200 Servers</option> <option value="500">500 Servers</option> <option value="1000">1000 Servers</option> <option value="custom">Custom</option> </select> <span class="dropdown-icon">▼</span> </div> <input id="customServerCount" type="number" min="1" max="1000" placeholder="Enter a number (1-1000)" style="display: none;"> <button id="confirmServerCount">Confirm</button> `; document.body.appendChild(overlay); document.body.appendChild(popup); const serverCountDropdown = popup.querySelector('#serverCount'); const customServerCountInput = popup.querySelector('#customServerCount'); const confirmButton = popup.querySelector('#confirmServerCount'); const closeButton = popup.querySelector('#closePopup'); serverCountDropdown.addEventListener('change', () => { if (serverCountDropdown.value === 'custom') { customServerCountInput.style.display = 'block'; } else { customServerCountInput.style.display = 'none'; } }); confirmButton.addEventListener('click', () => { let serverCount; if (serverCountDropdown.value === 'custom') { serverCount = parseInt(customServerCountInput.value); if (isNaN(serverCount) || serverCount < 1 || serverCount > 1000) { notifications('Error: Please enter a valid number between 1 and 1000.', 'error', '⚠️', '5000'); return; } } else { serverCount = parseInt(serverCountDropdown.value); } if (serverCount > 100) { notifications('Warning: Searching over 100 servers may take some time and you might get rate limited!', 'warning', '❗', '8000'); } callback(serverCount); disableFilterButton(true); disableLoadMoreButton(true); hidePopup(); Loadingbar(true); }); closeButton.addEventListener('click', () => { hidePopup(); }); function hidePopup() { const overlay = document.querySelector('.overlay'); const popup = document.querySelector('.filter-popup'); overlay.classList.add('fade-out'); popup.classList.add('fade-out'); setTimeout(() => { overlay.remove(); popup.remove(); }, 300); } } // Function to fetch public servers // totallimit is amount of sevrers to fetch async function fetchPublicServers(gameId, totalLimit) { let servers = []; let cursor = null; while (servers.length < totalLimit) { // too lazy to comment any of this. hopefully i remember what this does in the future const url = `https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=100${cursor ? `&cursor=${cursor}` : ''}`; const response = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: url, onload: function(response) { resolve(JSON.parse(response.responseText)); }, onerror: function(error) { reject(`Failed to fetch public servers: ${error}`); }, }); }); servers = servers.concat(response.data); if (!response.nextPageCursor || servers.length >= totalLimit) { break; } cursor = response.nextPageCursor; await delay(3000); // wait 3 seconds before each page request. if u think this is slow i tried 1 second i got rate limited :| } return servers.slice(0, totalLimit); } function createFilterDropdowns(servers) { // Create the main filter container const filterContainer = document.createElement('div'); Object.assign(filterContainer.style, { display: 'flex', gap: '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 sophisticated 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 refined CSS for animations and scrollbar styling const style = document.createElement('style'); style.textContent = ` @keyframes gradientFlow { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(255, 40, 40, 0.4); } 70% { box-shadow: 0 0 0 12px rgba(255, 40, 40, 0); } 100% { box-shadow: 0 0 0 0 rgba(255, 40, 40, 0); } } select::-webkit-scrollbar { width: 8px; } select::-webkit-scrollbar-track { background: rgba(20,20,20,0.6); border-radius: 8px; } select::-webkit-scrollbar-thumb { background: rgba(255,40,40,0.5); border-radius: 8px; border: 2px solid rgba(20,20,20,0.6); } select::-webkit-scrollbar-thumb:hover { background: rgba(255,40,40,0.7); } .logo-pulse { animation: pulse 2s infinite; } `; document.head.appendChild(style); // Enhanced logo with more sophisticated hover effects const logoWrapper = document.createElement('div'); Object.assign(logoWrapper.style, { position: 'relative', marginRight: '24px', }); const logo = document.createElement('img'); logo.src = window.Base64Images.logo; Object.assign(logo.style, { width: '60px', height: '60px', borderRadius: '16px', transition: 'all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1)', filter: 'drop-shadow(0 8px 16px rgba(255,40,40,0.3))', border: '2px solid rgba(255,40,40,0.3)', }); const logoGlow = document.createElement('div'); Object.assign(logoGlow.style, { position: 'absolute', inset: '-4px', borderRadius: '20px', background: 'radial-gradient(circle at center, rgba(255,40,40,0.4) 0%, rgba(255,40,40,0) 70%)', opacity: '0', transition: 'opacity 0.5s ease', pointerEvents: 'none', zIndex: '-1', }); logo.addEventListener('mouseover', () => { logo.style.transform = 'rotate(-8deg) scale(1.15)'; logo.style.filter = 'drop-shadow(0 12px 24px rgba(255,40,40,0.5))'; logo.style.border = '2px solid rgba(255,40,40,0.6)'; logoGlow.style.opacity = '1'; logo.classList.add('logo-pulse'); }); logo.addEventListener('mouseout', () => { logo.style.transform = 'rotate(0) scale(1)'; logo.style.filter = 'drop-shadow(0 8px 16px rgba(255,40,40,0.3))'; logo.style.border = '2px solid rgba(255,40,40,0.3)'; logoGlow.style.opacity = '0'; logo.classList.remove('logo-pulse'); }); logoWrapper.appendChild(logoGlow); logoWrapper.appendChild(logo); filterContainer.appendChild(logoWrapper); // Function to create a premium dropdown with enhanced styling const createDropdown = (id, placeholder) => { const wrapper = document.createElement('div'); Object.assign(wrapper.style, { position: 'relative', minWidth: '240px', flex: '1' }); // Label for the dropdown with subtle animation const label = document.createElement('div'); label.textContent = placeholder.replace('All ', ''); Object.assign(label.style, { color: 'rgba(255,255,255,0.7)', marginBottom: '10px', fontSize: '14px', fontWeight: '500', letterSpacing: '0.5px', transform: 'translateX(2px)', transition: 'color 0.3s ease', textTransform: 'uppercase' }); wrapper.appendChild(label); const dropdown = document.createElement('select'); dropdown.id = id; dropdown.innerHTML = `<option value="">${placeholder}</option>`; Object.assign(dropdown.style, { width: '100%', padding: '18px 56px 18px 24px', fontSize: '16px', fontWeight: '500', background: 'linear-gradient(145deg, rgba(35,35,35,0.9), rgba(20,20,20,0.9))', color: '#FF2828', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '14px', boxShadow: '0 8px 16px rgba(0,0,0,0.25), inset 0 1px 0 rgba(255,255,255,0.05)', appearance: 'none', cursor: 'pointer', transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)', opacity: '0', transform: 'translateY(-20px)', letterSpacing: '0.3px' }); // Enhanced chevron icon with SVG const chevron = document.createElement('div'); chevron.innerHTML = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>`; Object.assign(chevron.style, { position: 'absolute', right: '22px', bottom: '18px', transform: 'translateY(0)', pointerEvents: 'none', transition: 'transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1)', color: 'rgba(255,40,40,0.9)', display: 'flex', alignItems: 'center', justifyContent: 'center' }); // Refined dropdown interactions dropdown.addEventListener('mouseover', () => { dropdown.style.background = 'linear-gradient(145deg, rgba(50,50,50,0.9), rgba(35,35,35,0.9))'; dropdown.style.boxShadow = '0 12px 24px rgba(0,0,0,0.35), inset 0 1px 0 rgba(255,255,255,0.08)'; dropdown.style.borderColor = 'rgba(255,40,40,0.3)'; label.style.color = 'rgba(255,40,40,0.9)'; chevron.style.transform = 'translateY(0) rotate(180deg)'; }); dropdown.addEventListener('mouseout', () => { if (document.activeElement !== dropdown) { dropdown.style.background = 'linear-gradient(145deg, rgba(35,35,35,0.9), rgba(20,20,20,0.9))'; dropdown.style.boxShadow = '0 8px 16px rgba(0,0,0,0.25), inset 0 1px 0 rgba(255,255,255,0.05)'; dropdown.style.borderColor = 'rgba(255,255,255,0.1)'; label.style.color = 'rgba(255,255,255,0.7)'; chevron.style.transform = 'translateY(0) rotate(0deg)'; } }); dropdown.addEventListener('focus', () => { dropdown.style.outline = 'none'; dropdown.style.borderColor = 'rgba(255,40,40,0.5)'; dropdown.style.boxShadow = '0 12px 24px rgba(0,0,0,0.35), 0 0 0 3px rgba(255,40,40,0.2), inset 0 1px 0 rgba(255,255,255,0.08)'; label.style.color = 'rgba(255,40,40,0.9)'; chevron.style.transform = 'translateY(0) rotate(180deg)'; }); dropdown.addEventListener('blur', () => { dropdown.style.borderColor = 'rgba(255,255,255,0.1)'; dropdown.style.boxShadow = '0 8px 16px rgba(0,0,0,0.25), inset 0 1px 0 rgba(255,255,255,0.05)'; label.style.color = 'rgba(255,255,255,0.7)'; chevron.style.transform = 'translateY(0) rotate(0deg)'; }); dropdown.addEventListener('change', () => { dropdown.style.transform = 'scale(0.97)'; setTimeout(() => dropdown.style.transform = 'scale(1)', 150); // Add a subtle flash effect on change const flash = document.createElement('div'); Object.assign(flash.style, { position: 'absolute', inset: '0', borderRadius: '14px', backgroundColor: 'rgba(255,40,40,0.15)', pointerEvents: 'none', opacity: '0', transition: 'opacity 0.3s ease' }); wrapper.appendChild(flash); flash.style.opacity = '1'; setTimeout(() => { flash.style.opacity = '0'; setTimeout(() => wrapper.removeChild(flash), 300); }, 50); }); // Enhanced fade-in animation with staggered timing setTimeout(() => { dropdown.style.opacity = '1'; dropdown.style.transform = 'translateY(0)'; }, 500); wrapper.appendChild(dropdown); wrapper.appendChild(chevron); return wrapper; }; // Create enhanced dropdowns const countryDropdown = createDropdown('countryFilter', 'All Countries'); const cityDropdown = createDropdown('cityFilter', 'All Cities'); // Populate dropdowns with server data const countryCounts = {}; servers.forEach(server => { const country = server.location.country.name; countryCounts[country] = (countryCounts[country] || 0) + 1; }); // Sort countries alphabetically const sortedCountries = Object.keys(countryCounts).sort(); sortedCountries.forEach(country => { const option = document.createElement('option'); option.value = country; option.textContent = `${country} (${countryCounts[country]})`; countryDropdown.querySelector('select').appendChild(option); }); // Add a subtle visual indicator const separator = document.createElement('div'); Object.assign(separator.style, { height: '65px', width: '1px', background: 'linear-gradient(to bottom, rgba(255,255,255,0), rgba(255,40,40,0.3), rgba(255,255,255,0))', margin: '0 4px' }); countryDropdown.querySelector('select').addEventListener('change', () => { const selectedCountry = countryDropdown.querySelector('select').value; cityDropdown.querySelector('select').innerHTML = '<option value="">All Cities</option>'; if (selectedCountry) { const cityCounts = {}; servers .filter(server => server.location.country.name === selectedCountry) .forEach(server => { const city = server.location.city; const region = server.location.region?.name; const cityKey = region ? `${city}, ${region}` : city; cityCounts[cityKey] = (cityCounts[cityKey] || 0) + 1; }); // Sort cities alphabetically const sortedCities = Object.keys(cityCounts).sort(); sortedCities.forEach(city => { const option = document.createElement('option'); option.value = city; option.textContent = `${city} (${cityCounts[city]})`; cityDropdown.querySelector('select').appendChild(option); }); // Enhanced animation for city dropdown update cityDropdown.querySelector('select').style.opacity = '0.3'; cityDropdown.querySelector('select').style.transform = 'translateY(-10px)'; setTimeout(() => { cityDropdown.querySelector('select').style.opacity = '1'; cityDropdown.querySelector('select').style.transform = 'translateY(0)'; }, 150); // Visual indicator that cities were updated cityDropdown.style.position = 'relative'; const updateFlash = document.createElement('div'); Object.assign(updateFlash.style, { position: 'absolute', inset: '30px 0 0 0', borderRadius: '14px', background: 'radial-gradient(circle at center, rgba(255,40,40,0.2) 0%, rgba(255,40,40,0) 70%)', pointerEvents: 'none', opacity: '1', transition: 'opacity 0.8s ease' }); cityDropdown.appendChild(updateFlash); setTimeout(() => { updateFlash.style.opacity = '0'; setTimeout(() => cityDropdown.removeChild(updateFlash), 800); }, 100); } }); // Append dropdowns to container with separator filterContainer.appendChild(countryDropdown); filterContainer.appendChild(separator); filterContainer.appendChild(cityDropdown); // Enhanced fade-in container animation setTimeout(() => { filterContainer.style.opacity = '1'; filterContainer.style.transform = 'translateY(0) scale(1)'; }, 300); return filterContainer; } // Function to filter servers based on selected country and city cause im lazy function filterServers(servers, country, city) { return servers.filter(server => { const matchesCountry = !country || server.location.country.name === country; const matchesCity = !city || `${server.location.city}${server.location.region?.name ? `, ${server.location.region.name}` : ''}` === city; return matchesCountry && matchesCity; }); } // Function to sort servers by ping. maybe inaccurate but thats roblox's problem not mine function sortServersByPing(servers) { return servers.sort((a, b) => a.server.ping - b.server.ping); } async function fetchPlayerThumbnails_servers(playerTokens) { const body = playerTokens.map(token => ({ requestId: `0:${token}:AvatarHeadshot:150x150:png:regular`, type: "AvatarHeadShot", targetId: 0, token, format: "png", size: "150x150", })); const response = await fetch("https://thumbnails.roblox.com/v1/batch", { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, body: JSON.stringify(body), }); const data = await response.json(); return data.data || []; } async function rebuildServerList(gameId, totalLimit, best_connection) { const serverListContainer = document.getElementById("rbx-public-game-server-item-container"); // If "Best Connection" is enabled // FUNCTION FOR THE 6TH BUTTON! if (best_connection === true) { disableLoadMoreButton(true); disableFilterButton(true); notifications("Retrieving Location...", "success", "🌎", '5000') // Ask for the user's location const userLocation = await getUserLocation(); if (!userLocation) { notifications('Error: Unable to fetch your location. Please enable location access.', 'error', '⚠️', '5000'); disableFilterButton(false); return; } // Fetch 50 servers const servers = await fetchPublicServers(gameId, 50); if (servers.length === 0) { notifications('Error: No servers found. Please try again later.', 'error', '⚠️', '5000'); disableFilterButton(false); return; } // Calculate distances and find the closest server let closestServer = null; let minDistance = Infinity; let closestServerLocation = null; for (const server of servers) { const { id: serverId, maxPlayers, playing } = server; // Skip full servers if (playing >= maxPlayers) { continue; } try { // Fetch server location const location = await fetchServerDetails(gameId, serverId); // Calculate distance const distance = calculateDistance( userLocation.latitude, userLocation.longitude, location.latitude, location.longitude ); // Update closest server if (distance < minDistance) { minDistance = distance; closestServer = server; closestServerLocation = location; } } catch (error) { ConsoleLogEnabled(`Error fetching details for server ${serverId}:`, error); // Skip this server and continue with the next one continue; } } if (closestServer) { // Automatically join the closest server showLoadingOverlay(); Roblox.GameLauncher.joinGameInstance(gameId, closestServer.id); notifications(`Joining nearest server! Server ID: ${closestServer.id} Distance: ${(minDistance / 1.609).toFixed(2)} miles | ${minDistance.toFixed(2)} km Location (Country): ${closestServerLocation.country.name}.`, 'success', '🚀', '5000'); disableFilterButton(false); Loadingbar(false); } else { notifications('No valid servers found. Please try again later after refreshing the webpage. Filter button disabled.', 'error', '⚠️', '8000'); Loadingbar(false); } return; // Exit the function after joining the best server } // Rest of the original function (for non-"Best Connection" mode) if (!serverListContainer) { ConsoleLogEnabled("Server list container not found!"); notifications('Error: No Servers found. There is nobody playing this game. Please refresh the page.', 'error', '⚠️', '8000'); Loadingbar(false); return; } const messageElement = showMessage("Just a moment — to detect your location accurately, please stay on this page..."); const premium_message = messageElement.querySelector('.premium-message-text'); try { // Retrieve user's location for distance calculations const userLocation = await getUserLocation(); if (!userLocation) { notifications('Error: Unable to fetch your location. Please enable location access.', 'error', '⚠️', '5000'); disableFilterButton(false); return; } const servers = await fetchPublicServers(gameId, totalLimit); const totalServers = servers.length; let skippedServers = 0; if (premium_message) { premium_message.textContent = `Filtering servers... Please stay on this page to ensure a faster and more accurate search. ${totalServers} servers found, 0 loaded so far.`; } notifications(`Please do not leave this page as it slows down the search. \nFound a total of ${totalServers} servers.`, 'success', '👍', '8000'); const serverDetails = []; for (let i = 0; i < servers.length; i++) { const server = servers[i]; const { id: serverId, maxPlayers, playing, ping, fps, playerTokens } = server; let location; try { location = await fetchServerDetails(gameId, serverId); } catch (error) { if (error === 'purchase_required') { if (premium_message) { premium_message.textContent = "Error: Cannot access server regions because you have not purchased the game."; } notifications('Cannot access server regions because you have not purchased the game.', 'error', '⚠️', '15000'); Loadingbar(false); // disable loading bar return; return; } else if (error === 'subplace_join_restriction') { if (premium_message) { premium_message.textContent = "Error: This game requires users to teleport to a subplace. As a result, server regions cannot be retrieved."; } notifications('Error: This game requires users to teleport to a subplace. As a result, server regions cannot be retrieved.', 'error', '⚠️', '15000'); Loadingbar(false); // disable loading bar return; } else { ConsoleLogEnabled(error); location = { city: "Unknown", country: { name: "Unknown", code: "??" } }; } } if (location.city === "Unknown" || playing >= maxPlayers) { ConsoleLogEnabled(`Skipping server ${serverId} because it is full or location is unknown.`); skippedServers++; continue; } // Fetch player thumbnails const playerThumbnails = playerTokens && playerTokens.length > 0 ? await fetchPlayerThumbnails_servers(playerTokens) : []; serverDetails.push({ server, location, playerThumbnails }); if (premium_message) { premium_message.textContent = `Filtering servers, please do not leave this page...\n${totalServers} servers found, ${i + 1} server locations found`; } } if (serverDetails.length === 0) { showMessage("END"); notifications('Error: No servers found. Please try again with an increase in the number of servers to search for.', 'error', '⚠️', '15000'); Loadingbar(false); // disable loading bar return; } const loadedServers = totalServers - skippedServers; notifications(`Filtering complete!\n${totalServers} servers found, ${loadedServers} servers loaded, ${skippedServers} servers skipped (full).`, 'success', '👍', '5000'); showMessage("END"); Loadingbar(false); // disable loading bar // Add filter dropdowns const filterContainer = createFilterDropdowns(serverDetails); serverListContainer.parentNode.insertBefore(filterContainer, serverListContainer); // Style the server list container to use a grid layout serverListContainer.style.display = "grid"; serverListContainer.style.gridTemplateColumns = "repeat(4, 1fr)"; // 4 columns serverListContainer.style.gap = "0px"; // Set gap to 0px to remove the gap between grid items const displayFilteredServers = (country, city) => { serverListContainer.innerHTML = ""; const filteredServers = filterServers(serverDetails, country, city); // Sort servers by distance from the user (fastest first) const sortedServers = filteredServers.sort((a, b) => { const distanceA = calculateDistance(userLocation.latitude, userLocation.longitude, a.location.latitude, a.location.longitude); const distanceB = calculateDistance(userLocation.latitude, userLocation.longitude, b.location.latitude, b.location.longitude); return distanceA - distanceB; }); sortedServers.forEach(({ server, location, playerThumbnails }) => { const serverCard = document.createElement("li"); serverCard.className = "rbx-game-server-item col-md-3 col-sm-4 col-xs-6"; serverCard.style.width = "100%"; serverCard.style.minHeight = "400px"; serverCard.style.display = "flex"; serverCard.style.flexDirection = "column"; serverCard.style.justifyContent = "space-between"; serverCard.style.boxSizing = "border-box"; serverCard.style.outline = 'none'; serverCard.style.padding = '6px'; serverCard.style.borderRadius = '8px'; // Create label (now based on distance) const pingLabel = document.createElement("div"); pingLabel.style.marginBottom = "5px"; // Adds spacing above label pingLabel.style.padding = "5px 10px"; pingLabel.style.borderRadius = "8px"; pingLabel.style.fontWeight = "bold"; pingLabel.style.textAlign = "center"; // Centers the label // Calculate distance from user to server const distance = calculateDistance( userLocation.latitude, userLocation.longitude, location.latitude, location.longitude ); // Calculate ping using the advanced formula const calculatedPing = 2 * (distance / 180000) * 1000 * 1.8 + (20 + 0.04 * distance); if (distance < 1250) { pingLabel.textContent = "⚡ Fast"; pingLabel.style.backgroundColor = "#014737"; pingLabel.style.color = "#73e1bc"; } else if (distance < 5000) { pingLabel.textContent = "⏳ OK"; pingLabel.style.backgroundColor = "#c75a00"; pingLabel.style.color = "#ffe8c2"; } else { pingLabel.textContent = "🐌 Slow"; pingLabel.style.backgroundColor = "#771d1d"; pingLabel.style.color = "#fcc468"; } // Create a container for player thumbnails const thumbnailsContainer = document.createElement("div"); thumbnailsContainer.className = "player-thumbnails-container"; thumbnailsContainer.style.display = "grid"; thumbnailsContainer.style.gridTemplateColumns = "repeat(3, 60px)"; thumbnailsContainer.style.gridTemplateRows = "repeat(2, 60px)"; thumbnailsContainer.style.gap = "5px"; thumbnailsContainer.style.marginBottom = "10px"; // Add player thumbnails to the container (max 5) const maxThumbnails = 5; const displayedThumbnails = playerThumbnails.slice(0, maxThumbnails); displayedThumbnails.forEach(thumb => { if (thumb && thumb.imageUrl) { const img = document.createElement("img"); img.src = thumb.imageUrl; img.className = "avatar-card-image"; img.style.width = "60px"; img.style.height = "60px"; img.style.borderRadius = "50%"; thumbnailsContainer.appendChild(img); } }); // Add a placeholder for hidden players const hiddenPlayers = server.playing - displayedThumbnails.length; if (hiddenPlayers > 0) { const placeholder = document.createElement("div"); placeholder.className = "avatar-card-image"; placeholder.style.width = "60px"; placeholder.style.height = "60px"; placeholder.style.borderRadius = "50%"; placeholder.style.backgroundColor = "#6a6f81"; placeholder.style.display = "flex"; placeholder.style.alignItems = "center"; placeholder.style.justifyContent = "center"; placeholder.style.color = "#fff"; placeholder.style.fontSize = "14px"; placeholder.textContent = `+${hiddenPlayers}`; thumbnailsContainer.appendChild(placeholder); } // Server card content with both distance and calculated ping, with line spacers between each info const cardItem = document.createElement("div"); cardItem.className = "card-item"; cardItem.style.display = "flex"; cardItem.style.flexDirection = "column"; cardItem.style.justifyContent = "space-between"; cardItem.style.height = "100%"; cardItem.innerHTML = ` ${thumbnailsContainer.outerHTML} <div class="rbx-game-server-details game-server-details"> <div class="text-info rbx-game-status rbx-game-server-status text-overflow"> ${server.playing} of ${server.maxPlayers} people max </div> <div class="server-player-count-gauge border"> <div class="gauge-inner-bar border" style="width: ${(server.playing / server.maxPlayers) * 100}%;"></div> </div> <span data-placeid="${gameId}"> <button type="button" class="btn-full-width btn-control-xs rbx-game-server-join game-server-join-btn btn-primary-md btn-min-width">Join</button> </span> </div> <div style="margin-top: 10px; text-align: center;"> ${pingLabel.outerHTML} <div class="info-lines" style="margin-top: 8px;"> <div class="ping-info">Ping: ${calculatedPing.toFixed(2)} ms</div> <hr style="margin: 6px 0;"> <div class="ping-info">Distance: ${distance.toFixed(2)} km</div> <hr style="margin: 6px 0;"> <div class="location-info">${location.city}, ${location.country.name}</div> <hr style="margin: 6px 0;"> <div class="fps-info">FPS: ${Math.round(server.fps)}</div> </div> </div> `; const joinButton = cardItem.querySelector(".rbx-game-server-join"); joinButton.addEventListener("click", () => { ConsoleLogEnabled(`Roblox.GameLauncher.joinGameInstance(${gameId}, "${server.id}")`); showLoadingOverlay(); Roblox.GameLauncher.joinGameInstance(gameId, server.id); }); const container = adjustJoinButtonContainer(joinButton); const inviteButton = createInviteButton(gameId, server.id); container.appendChild(inviteButton); serverCard.appendChild(cardItem); serverListContainer.appendChild(serverCard); }); }; // Add event listeners to dropdowns const countryFilter = document.getElementById('countryFilter'); const cityFilter = document.getElementById('cityFilter'); countryFilter.addEventListener('change', () => { displayFilteredServers(countryFilter.value, cityFilter.value); }); cityFilter.addEventListener('change', () => { displayFilteredServers(countryFilter.value, cityFilter.value); }); // Display all servers initially displayFilteredServers("", ""); } catch (error) { ConsoleLogEnabled("Error rebuilding server list:", error); notifications('Filtering error. Try again or refresh to enable the filter button.', 'error', '⚠️ ', '8000'); if (premium_message) { premium_message.textContent = "An error occurred while filtering servers. Please try again."; } Loadingbar(false); // enable loading bar } finally { Loadingbar(false); // omg bruh i just realzed i could put this here but now im too lazy to thorugh the code to remove all of the loading bar disabl functions disableFilterButton(false); // beta test filter button //notifications('Note: The filter button is disabled while using server regions. Refresh to enable it.', 'info', '🔄', '15000') // old stuff not needed anymore } } // Function to extract the game ID from the URL function extractGameId() { const url = window.location.href; const match = url.match(/roblox\.com\/games\/(\d+)/); if (match && match[1]) { return match[1]; // Return the game ID } return null; // Return null if no game ID is found } // Log the game ID to the console const gameId = extractGameId(); // Function to create and append the Invite button function createInviteButton(placeId, serverId) { // too lazy to comment this function tbh just ready the name const inviteButton = document.createElement('button'); inviteButton.textContent = 'Invite'; inviteButton.className = 'btn-control-xs btn-primary-md btn-min-width btn-full-width'; inviteButton.style.width = '25%'; inviteButton.style.marginLeft = '5px'; inviteButton.style.padding = '4px 8px'; inviteButton.style.fontSize = '12px'; inviteButton.style.borderRadius = '8px'; inviteButton.style.backgroundColor = '#3b3e49'; inviteButton.style.borderColor = '#3b3e49'; inviteButton.style.color = '#ffffff'; inviteButton.style.cursor = 'pointer'; inviteButton.style.fontWeight = '500'; inviteButton.style.textAlign = 'center'; inviteButton.style.whiteSpace = 'nowrap'; inviteButton.style.verticalAlign = 'middle'; inviteButton.style.lineHeight = '100%'; inviteButton.style.fontFamily = 'Builder Sans, Helvetica Neue, Helvetica, Arial, Lucida Grande, sans-serif'; inviteButton.style.textRendering = 'auto'; inviteButton.style.webkitFontSmoothing = 'antialiased'; inviteButton.style.mozOsxFontSmoothing = 'grayscale'; inviteButton.addEventListener('click', () => { const inviteLink = `https://oqarshi.github.io/Invite/?placeid=${placeId}&serverid=${serverId}`; navigator.clipboard.writeText(inviteLink).then(() => { ConsoleLogEnabled(`Invite link copied to clipboard: ${inviteLink}`); notifications('Success! Invite link copied to clipboard!', 'success', '🎉', ' 2000'); }).catch(() => { ConsoleLogEnabled('Failed to copy invite link.'); notifications('Error: Failed to copy invite link', 'error', '😔', '2000'); }); }); return inviteButton; } // Function to adjust the Join button and its container function adjustJoinButtonContainer(joinButton) { const container = document.createElement('div'); container.style.display = 'flex'; container.style.width = '100%'; joinButton.style.width = '75%'; joinButton.parentNode.insertBefore(container, joinButton); container.appendChild(joinButton); return container; } /********************************************************************************************************************************************************************************************************************************************* Functions for the 6th button. *********************************************************************************************************************************************************************************************************************************************/ function calculateDistance(lat1, lon1, lat2, lon2) { const R = 6371; // Radius of the Earth in kilometers const dLat = (lat2 - lat1) * (Math.PI / 180); const dLon = (lon2 - lon1) * (Math.PI / 180); const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(lat1 * (Math.PI / 180)) * Math.cos(lat2 * (Math.PI / 180)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; // Distance in kilometers } function getUserLocation() { return new Promise((resolve, reject) => { if (!navigator.geolocation) { ConsoleLogEnabled("Geolocation is not supported by this browser."); return fetchFallbackLocation(resolve); } // Step 1: Try Standard Geolocation navigator.geolocation.getCurrentPosition( (position) => resolveSuccess(position, resolve), async (error) => { ConsoleLogEnabled("Geolocation error:", error); // Step 2: Check Permission Status (if supported) if (navigator.permissions) { try { const permissionStatus = await navigator.permissions.query({ name: "geolocation" }); ConsoleLogEnabled("Geolocation permission status:", permissionStatus.state); if (permissionStatus.state === "denied") { ConsoleLogEnabled("User denied location access."); return fetchFallbackLocation(resolve); } } catch (permError) { ConsoleLogEnabled("Error checking permission status:", permError); } } // Step 3: Try Alternative Geolocation API (If Available) try { navigator.geolocation.getCurrentPosition( (position) => resolveSuccess(position, resolve), // ✅ Corrected to use resolveSuccess () => fetchFallbackLocation(resolve), { maximumAge: 5000 } ); return; } catch (altError) { ConsoleLogEnabled("Alternative Geolocation API failed:", altError); } // Step 4: If all else fails, fallback to server fetchFallbackLocation(resolve); }, { timeout: 10000, maximumAge: 0, } ); }); } // 🎯 Success Handler function resolveSuccess(position, resolve) { notifications("We successfully detected your location.", "success", "🌎", "5000"); disableLoadMoreButton(true); disableFilterButton(true); Loadingbar(true); resolve({ latitude: position.coords.latitude, longitude: position.coords.longitude, }); } // 🌍 Fetches fallback location from a callback server function fetchFallbackLocation(resolve) { ConsoleLogEnabled("Fetching location from the fallback server..."); fetch("https://ipapi.co/json/") // Example fallback API .then((response) => response.json()) .then((data) => { ConsoleLogEnabled("Fallback location received:", data); notifications("Using approximate location from IP address.", "info", "📍", "5000"); resolve({ latitude: data.latitude || 40.7128, // Default to New York if API fails longitude: data.longitude || -74.0060, }); }) .catch((error) => { ConsoleLogEnabled("Error fetching fallback location:", error); notifications("Could not determine location. Please report and issue on Greasyfork. Assuming New York.", "error", "⚠️", "15000"); resolve({ latitude: 40.7128, longitude: -74.0060, }); }); } /********************************************************************************************************************************************************************************************************************************************* Functions for the 7th button. *********************************************************************************************************************************************************************************************************************************************/ async function auto_join_small_server() { // Disable the "Load More" button and show the loading bar Loadingbar(true); disableFilterButton(true); disableLoadMoreButton(); // Get the game ID from the URL const gameId = window.location.pathname.split('/')[2]; // Retry mechanism for 429 errors let retries = 3; // Number of retries let success = false; while (retries > 0 && !success) { try { // Fetch server data using GM_xmlhttpRequest const data = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: `https://games.roblox.com/v1/games/${gameId}/servers/public?sortOrder=1&excludeFullGames=true&limit=100`, onload: function(response) { if (response.status === 429) { reject('429: Too Many Requests'); } else if (response.status >= 200 && response.status < 300) { resolve(JSON.parse(response.responseText)); } else { reject(`HTTP error: ${response.status}`); } }, onerror: function(error) { reject(error); }, }); }); // Find the server with the lowest player count let minPlayers = Infinity; let targetServer = null; for (const server of data.data) { if (server.playing < minPlayers) { minPlayers = server.playing; targetServer = server; } } if (targetServer) { // Join the server with the lowest player count showLoadingOverlay(); Roblox.GameLauncher.joinGameInstance(gameId, targetServer.id); notifications(`Joining a server with ${targetServer.playing} player(s).`, 'success', '🚀'); success = true; // Mark as successful } else { notifications('No available servers found.', 'error', '⚠️'); break; // Exit the loop if no servers are found } } catch (error) { if (error === '429: Too Many Requests' && retries > 0) { ConsoleLogEnabled('Rate limited. Retrying in 10 seconds...'); notifications('Rate limited. Retrying in 10 seconds...', 'warning', '⏳', '10000'); await delay(10000); // Wait 10 seconds before retrying retries--; } else { ConsoleLogEnabled('Error fetching server data:', error); notifications('Error: Failed to fetch server data. Please try again later.', 'error', '⚠️', '5000'); Loadingbar(false); break; // Exit the loop if it's not a 429 error or no retries left } } } // Hide the loading bar and enable the filter button Loadingbar(false); disableFilterButton(false); } /********************************************************************************************************************************************************************************************************************************************* Functions for the 8th button. *********************************************************************************************************************************************************************************************************************************************/ // Enhanced popup styling for the username input dialog function find_user_server_tab() { // Create the overlay (backdrop) with improved animation const overlay = document.createElement('div'); overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.6); z-index: 9999; opacity: 0; transition: opacity 0.4s ease; backdrop-filter: blur(3px); `; document.body.appendChild(overlay); // Create the popup container with improved styling const popup = document.createElement('div'); popup.className = 'player-count-popup'; popup.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0.95); background-color: rgb(30, 32, 34); padding: 30px 25px; border-radius: 12px; z-index: 10000; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.8); display: flex; flex-direction: column; align-items: center; gap: 20px; width: 340px; opacity: 0; transition: opacity 0.4s ease, transform 0.4s ease; border: 1px solid rgba(255, 255, 255, 0.1); `; // Improved close button with better hover effects const closeButton = document.createElement('button'); closeButton.innerHTML = '×'; closeButton.style.cssText = ` position: absolute; top: 12px; right: 12px; background: transparent; border: none; color: #999999; font-size: 26px; cursor: pointer; width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; `; closeButton.addEventListener('mouseenter', () => { closeButton.style.backgroundColor = 'rgba(255, 255, 255, 0.1)'; closeButton.style.color = '#ff4444'; closeButton.style.transform = 'rotate(90deg)'; }); closeButton.addEventListener('mouseleave', () => { closeButton.style.backgroundColor = 'transparent'; closeButton.style.color = '#999999'; closeButton.style.transform = 'rotate(0deg)'; }); // Enhanced title with icon const titleContainer = document.createElement('div'); titleContainer.style.cssText = ` display: flex; align-items: center; gap: 10px; margin-bottom: 5px; `; const titleIcon = document.createElement('span'); titleIcon.innerHTML = '🔍'; titleIcon.style.cssText = `font-size: 22px;`; const title = document.createElement('h3'); title.textContent = 'Locate User'; title.style.cssText = ` color: white; margin: 0; font-size: 20px; font-weight: 600; `; titleContainer.appendChild(titleIcon); titleContainer.appendChild(title); popup.appendChild(titleContainer); // Add subtitle with instructions const subtitle = document.createElement('p'); subtitle.textContent = 'Enter the exact Roblox username to find their server'; subtitle.style.cssText = ` color: #aaaaaa; margin: -10px 0 0 0; font-size: 14px; text-align: center; `; popup.appendChild(subtitle); // Improved input box with focus styling const usernameInput = document.createElement('input'); usernameInput.type = 'text'; usernameInput.placeholder = 'Username'; usernameInput.style.cssText = ` width: 100%; padding: 12px 15px; font-size: 16px; border: 2px solid #444; border-radius: 8px; background-color: #252729; color: white; outline: none; transition: all 0.3s ease; box-sizing: border-box; `; usernameInput.addEventListener('focus', () => { usernameInput.style.borderColor = '#b71c1c'; usernameInput.style.boxShadow = '0 0 5px rgba(211, 47, 47, 0.5)'; }); usernameInput.addEventListener('blur', () => { usernameInput.style.borderColor = '#444'; usernameInput.style.boxShadow = 'none'; }); popup.appendChild(usernameInput); // Enhanced confirm button with better styling and effects const confirmButton = document.createElement('button'); confirmButton.textContent = 'Search'; confirmButton.style.cssText = ` padding: 12px 0; width: 100%; font-size: 16px; font-weight: 600; background-color: #b71c1c; color: white; border: none; border-radius: 8px; cursor: pointer; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; gap: 8px; `; // Add icon to button const searchIcon = document.createElement('span'); searchIcon.textContent = '🔍'; searchIcon.style.fontSize = '18px'; confirmButton.prepend(searchIcon); confirmButton.addEventListener('mouseenter', () => { confirmButton.style.backgroundColor = '#d32f2f'; confirmButton.style.transform = 'translateY(-2px)'; confirmButton.style.boxShadow = '0 5px 15px rgba(211, 47, 47, 0.5)'; }); confirmButton.addEventListener('mouseleave', () => { confirmButton.style.backgroundColor = '#b71c1c'; confirmButton.style.transform = 'translateY(0)'; confirmButton.style.boxShadow = 'none'; }); confirmButton.addEventListener('mousedown', () => { confirmButton.style.transform = 'translateY(1px)'; }); confirmButton.addEventListener('mouseup', () => { confirmButton.style.transform = 'translateY(-2px)'; }); // Handle keyboard enter for better UX usernameInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') { confirmButton.click(); } }); // Handle confirm button click with enhanced validation feedback confirmButton.addEventListener('click', () => { const username = usernameInput.value.trim(); if (username) { if (username.length >= 3 && username.length <= 20) { FindPlayerGameServer(username); notifications("Searching for the user's server...", "info", "🔍", "5000"); fadeOutAndRemove_7th(popup, overlay); } else { usernameInput.style.borderColor = '#ff4444'; usernameInput.style.boxShadow = '0 0 5px rgba(255, 68, 68, 0.5)'; notifications('Username must be between 3 and 20 characters', 'error', '⚠️', '3000'); // Reset input styling after delay setTimeout(() => { if (document.body.contains(usernameInput)) { usernameInput.style.borderColor = '#444'; usernameInput.style.boxShadow = 'none'; } }, 2000); } } else { usernameInput.style.borderColor = '#ff4444'; usernameInput.style.boxShadow = '0 0 5px rgba(255, 68, 68, 0.5)'; notifications('Please enter a username', 'error', '⚠️', '2500'); } }); // Append elements to popup popup.appendChild(confirmButton); popup.appendChild(closeButton); // Append popup to body document.body.appendChild(popup); // Focus the input field automatically setTimeout(() => { usernameInput.focus(); }, 300); // Fade in the overlay and popup with improved animation setTimeout(() => { overlay.style.opacity = '1'; popup.style.opacity = '1'; popup.style.transform = 'translate(-50%, -50%) scale(1)'; }, 10); // Keep the same fadeOutAndRemove function name for compatibility function fadeOutAndRemove_7th(popup, overlay) { popup.style.opacity = '0'; popup.style.transform = 'translate(-50%, -50%) scale(0.9)'; overlay.style.opacity = '0'; setTimeout(() => { popup.remove(); overlay.remove(); }, 300); } // Close the popup when clicking outside overlay.addEventListener('click', () => { fadeOutAndRemove_7th(popup, overlay); }); // Close the popup when the close button is clicked closeButton.addEventListener('click', () => { fadeOutAndRemove_7th(popup, overlay); }); } // Add this helper function somewhere inside or above your main function const checkIfUserOnline = async (userId) => { try { const response = await fetch("https://presence.roblox.com/v1/presence/users", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userIds: [parseInt(userId)] }) }); const data = await response.json(); const presenceType = data.userPresences?.[0]?.userPresenceType ?? 0; return presenceType !== 0; // true if online or in-game/studio } catch (error) { ConsoleLogEnabled("Presence check failed:", error); return false; // fail-safe: treat as offline } }; async function FindPlayerGameServer(playerName) { disableLoadMoreButton(); Loadingbar(true); disableFilterButton(true); const wait = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds)); const fetchData = async (url, options = {}) => { if (!options.headers) options.headers = {}; return fetch(url, options) .then(response => response.json()) .catch(error => ConsoleLogEnabled("Fetch error:", error)); }; const fetchDataGM = (url, options = {}) => { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: options.method || 'GET', url: url, headers: options.headers || {}, anonymous: true, // Prevents sending cookies nocache: true, // Prevents caching onload: function(response) { try { const parsedData = JSON.parse(response.responseText); resolve(parsedData); } catch (error) { ConsoleLogEnabled("JSON parsing error:", error); reject(error); } }, onerror: function(error) { ConsoleLogEnabled("Request error:", error); reject(error); } }); }); }; ConsoleLogEnabled(`Initiating search for player: ${playerName}`); const gameId = window.location.href.split("/")[4]; ConsoleLogEnabled(`Game ID identified: ${gameId}`); let userId; try { ConsoleLogEnabled(`Retrieving user ID for player: ${playerName}`); const userProfile = await fetch(`https://www.roblox.com/users/profile?username=${playerName}`); if (!userProfile.ok) { notifications("Error: User does not exist on Roblox!", "error", "⚠️", "2500"); Loadingbar(false); disableFilterButton(false); throw `Player "${playerName}" not found`; } userId = userProfile.url.match(/\d+/)[0]; const isOnline = await checkIfUserOnline(userId); if (!isOnline) { notifications("User is currently offline.", "error", "📴", "4000"); Loadingbar(false); disableFilterButton(false); fadeOutAndRemove_7th_progress(progressPopup.popup, progressPopup.overlay); return `User "${playerName}" is offline.`; } ConsoleLogEnabled(`User is online — proceeding with server scan.`); ConsoleLogEnabled(`User ID retrieved: ${userId}`); } catch (error) { ConsoleLogEnabled("Error:", error); return `Error: ${error}`; } ConsoleLogEnabled(`Fetching avatar thumbnail for user ID: ${userId}`); const avatarThumbnail = (await fetchData(`https://thumbnails.roblox.com/v1/users/avatar-headshot?userIds=${userId}&format=Png&size=150x150`)).data[0].imageUrl; ConsoleLogEnabled(`Avatar thumbnail URL: ${avatarThumbnail}`); let pageCursor = null; let playerFound = false; let totalServersChecked = 0; let startTime = Date.now(); // Show the search progress popup with the player's thumbnail const progressPopup = showSearchProgressPopup(avatarThumbnail); while (true) { let apiUrl = `https://games.roblox.com/v1/games/${gameId}/servers/0?limit=100`; if (pageCursor) apiUrl += "&cursor=" + pageCursor; ConsoleLogEnabled(`Accessing servers with URL: ${apiUrl}`); const serverList = await fetchDataGM(apiUrl, { credentials: "omit" }).catch(() => null); if (serverList && serverList.data) { ConsoleLogEnabled(`Discovered ${serverList.data.length} servers in this set.`); for (let index = 0; index < serverList.data.length; index++) { const server = serverList.data[index]; if (!playerFound) { totalServersChecked++; // Calculate time elapsed const timeElapsed = Math.floor((Date.now() - startTime) / 1000); // Update the progress popup updateSearchProgressPopup( progressPopup, totalServersChecked, timeElapsed ); if (server.playerTokens.length === 0) { ConsoleLogEnabled(`Server ${index + 1} is empty. Proceeding to next server.`); continue; } ConsoleLogEnabled(`Inspecting server ${index + 1} hosting ${server.playing} players...`); let thumbnailData; while (true) { let requestBody = []; const generateRequestEntry = (playerToken) => ({ requestId: `0:${playerToken}:AvatarHeadshot:150x150:png:regular`, type: "AvatarHeadShot", targetId: 0, token: playerToken, format: "png", size: "150x150" }); server.playerTokens.forEach(token => { requestBody.push(generateRequestEntry(token)); }); try { ConsoleLogEnabled(`Fetching thumbnails for ${server.playerTokens.length} player(s)...`); thumbnailData = await fetchData("https://thumbnails.roblox.com/v1/batch", { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json" }, body: JSON.stringify(requestBody) }); ConsoleLogEnabled("Thumbnail Data Response:", thumbnailData); break; } catch (error) { ConsoleLogEnabled("Thumbnail retrieval error:", error); await wait(1000); } } if (!thumbnailData.data) { ConsoleLogEnabled("No thumbnail data available. Moving to next server."); continue; } for (let thumbIndex = 0; thumbIndex < thumbnailData.data.length; thumbIndex++) { const thumbnail = thumbnailData.data[thumbIndex]; if (thumbnail && thumbnail.imageUrl === avatarThumbnail) { playerFound = true; ConsoleLogEnabled(`Player located in server ${index + 1}!`); notifications("Found User's Server! Joining Server...", "success", "🚀", "5000"); showLoadingOverlay(); Roblox.GameLauncher.joinGameInstance(gameId, server.id); fadeOutAndRemove_7th_progress(progressPopup.popup, progressPopup.overlay); Loadingbar(false); disableFilterButton(false); return { gameId, serverId: server.id, currentPlayers: server.playing, maximumPlayers: server.maxPlayers, }; } } } else { break; } } pageCursor = serverList.nextPageCursor; if (!pageCursor || playerFound) break; else { ConsoleLogEnabled("Pausing for 2.5 seconds before next server batch..."); await wait(2500); } } else { ConsoleLogEnabled("Server fetch failed. Retrying in 10 seconds..."); notifications("Got rate limited. Waiting 10 seconds...", "info", "❗", "10000") await wait(10000); } } if (!playerFound) { // Wait for 2 seconds before calling another function setTimeout(() => { fadeOutAndRemove_7th_progress(progressPopup.popup, progressPopup.overlay); notifications("User not found playing this game!", "error", "⚠️", "5000"); notifications("If the user is playing, try again in a minute. The Roblox API may not be updated yet.", "info", "🔄", "20000"); }, 2000); // 2000 milliseconds = 2 seconds // Existing logic (unchanged) Loadingbar(false); disableFilterButton(false); ConsoleLogEnabled(`Player not found in the searched servers (${totalServersChecked} servers checked)`); // Update the progress popup with the final count updateSearchProgressPopup( progressPopup, totalServersChecked, Math.floor((Date.now() - startTime) / 1000), true ); return `Player not found in the searched servers (${totalServersChecked} servers checked)`; } } // Enhanced progress popup for search visualization function showSearchProgressPopup(avatarThumbnail) { // Create the overlay with blur effect const overlay = document.createElement('div'); overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.6); z-index: 9999; opacity: 0; transition: opacity 0.4s ease; backdrop-filter: blur(3px); `; document.body.appendChild(overlay); // Create the popup container with improved styling const popup = document.createElement('div'); popup.className = 'search-progress-popup'; popup.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0.95); background-color: rgb(30, 32, 34); padding: 30px; border-radius: 12px; z-index: 10000; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.8); display: flex; flex-direction: column; align-items: center; gap: 20px; width: 340px; opacity: 0; transition: opacity 0.4s ease, transform 0.4s ease; border: 1px solid rgba(255, 255, 255, 0.1); `; // Improved title with visual hierarchy const title = document.createElement('h3'); title.textContent = 'Searching for Player...'; title.style.cssText = ` color: white; margin: 0 0 10px 0; font-size: 20px; font-weight: 600; text-align: center; `; popup.appendChild(title); // Add animated loader element const loaderContainer = document.createElement('div'); loaderContainer.style.cssText = ` position: relative; width: 120px; height: 120px; display: flex; justify-content: center; align-items: center; `; // Create ripple effect around avatar const ripple = document.createElement('div'); ripple.style.cssText = ` position: absolute; width: 110px; height: 110px; border-radius: 50%; border: 3px solid #b71c1c; animation: ripple 1.5s ease-out infinite; opacity: 0; `; // Add the keyframe animation const style = document.createElement('style'); style.textContent = ` @keyframes ripple { 0% { transform: scale(0.8); opacity: 0.8; } 100% { transform: scale(1.2); opacity: 0; } } @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(255, 68, 68, 0.5); } 70% { box-shadow: 0 0 0 10px rgba(58, 122, 255, 0); } 100% { box-shadow: 0 0 0 0 rgba(58, 122, 255, 0); } } `; document.head.appendChild(style); // Enhanced player thumbnail display const thumbnail = document.createElement('img'); thumbnail.src = avatarThumbnail; thumbnail.style.cssText = ` width: 100px; height: 100px; border-radius: 50%; object-fit: cover; border: 4px solid #b71c1c; box-shadow: 0 0 20px rgba(255, 68, 68, 0.5); animation: pulse 2s infinite; z-index: 2; `; loaderContainer.appendChild(ripple); loaderContainer.appendChild(thumbnail); popup.appendChild(loaderContainer); // Status container const statusContainer = document.createElement('div'); statusContainer.style.cssText = ` background-color: rgba(0, 0, 0, 0.2); border-radius: 8px; padding: 15px; width: 100%; box-sizing: border-box; `; // Improved progress indicators with icons const serversSearchedContainer = document.createElement('div'); serversSearchedContainer.style.cssText = ` display: flex; align-items: center; gap: 10px; margin-bottom: 10px; `; const serverIcon = document.createElement('span'); serverIcon.textContent = '🔍'; serverIcon.style.fontSize = '18px'; const serversSearchedText = document.createElement('p'); serversSearchedText.textContent = 'Servers searched: 0'; serversSearchedText.style.cssText = ` color: white; margin: 0; font-size: 16px; flex-grow: 1; `; serversSearchedContainer.appendChild(serverIcon); serversSearchedContainer.appendChild(serversSearchedText); const timeElapsedContainer = document.createElement('div'); timeElapsedContainer.style.cssText = ` display: flex; align-items: center; gap: 10px; `; const timeIcon = document.createElement('span'); timeIcon.textContent = '⏱️'; timeIcon.style.fontSize = '18px'; const timeElapsedText = document.createElement('p'); timeElapsedText.textContent = 'Time elapsed: 0s'; timeElapsedText.style.cssText = ` color: white; margin: 0; font-size: 16px; flex-grow: 1; `; timeElapsedContainer.appendChild(timeIcon); timeElapsedContainer.appendChild(timeElapsedText); statusContainer.appendChild(serversSearchedContainer); statusContainer.appendChild(timeElapsedContainer); popup.appendChild(statusContainer); // Add a status message that can be updated const statusMessage = document.createElement('p'); statusMessage.textContent = 'Scanning servers...'; statusMessage.style.cssText = ` color: #aaaaaa; margin: -10px 0 0 0; font-size: 14px; text-align: center; font-style: italic; `; popup.appendChild(statusMessage); // Append the popup to the body document.body.appendChild(popup); // Fade in the overlay and popup setTimeout(() => { overlay.style.opacity = '1'; popup.style.opacity = '1'; popup.style.transform = 'translate(-50%, -50%) scale(1)'; }, 10); return { popup, overlay, serversSearchedText, timeElapsedText, statusMessage, ripple }; } // Enhanced update function for search progress popup function updateSearchProgressPopup( progressPopup, totalServersChecked, timeElapsed, isFinal = false ) { // Update numbers with animation effect const currentServers = parseInt(progressPopup.serversSearchedText.textContent.match(/\d+/)[0]); const currentTime = parseInt(progressPopup.timeElapsedText.textContent.match(/\d+/)[0]); // Animate the number changes progressPopup.serversSearchedText.textContent = `Servers searched: ${totalServersChecked}`; progressPopup.timeElapsedText.textContent = `Time elapsed: ${timeElapsed}s`; // Update status message based on progress if (totalServersChecked % 5 === 0) { const messages = [ "Scanning servers...", "Checking player lists...", "Searching for matches...", "Processing server data...", "Analyzing player tokens..." ]; progressPopup.statusMessage.textContent = messages[Math.floor(Math.random() * messages.length)]; } if (isFinal) { progressPopup.statusMessage.textContent = "Search completed"; progressPopup.statusMessage.style.color = "#ffaa00"; progressPopup.statusMessage.style.fontWeight = "bold"; progressPopup.ripple.style.animationPlayState = "paused"; progressPopup.serversSearchedText.textContent = `Servers searched: ${totalServersChecked} (complete)`; } } // Enhanced removal function for progress popup function fadeOutAndRemove_7th_progress(popup, overlay) { popup.style.opacity = '0'; popup.style.transform = 'translate(-50%, -50%) scale(0.9)'; overlay.style.opacity = '0'; setTimeout(() => { popup.remove(); overlay.remove(); }, 400); } /********************************************************************************************************************************************************************************************************************************************* End of: This is all the functions for the 8 buttons *********************************************************************************************************************************************************************************************************************************************/ /********************************************************************************************************************************************************************************************************************************************* The Universal Functions *********************************************************************************************************************************************************************************************************************************************/ /******************************************************* name of function: disableLoadMoreButton description: Disables the "Load More" button *******************************************************/ function disableLoadMoreButton() { const loadMoreButton = document.querySelector('.rbx-running-games-load-more'); if (loadMoreButton) { loadMoreButton.disabled = true; loadMoreButton.style.opacity = '0.5'; // Optional: Make the button look disabled loadMoreButton.style.cursor = 'not-allowed'; // Optional: Change cursor to indicate disabled state loadMoreButton.title = 'Disabled by Rolocate'; // Set tooltip text } else { ConsoleLogEnabled('Load More button not found!'); ConsoleLogEnabled('Maybe moved by another extension!'); } } /******************************************************* name of function: Loadingbar description: Shows or hides a loading bar (now using pulsing boxes) *******************************************************/ function Loadingbar(disable) { const serverListSection = document.querySelector('#rbx-public-running-games'); const serverCardsContainer = document.querySelector('#rbx-public-game-server-item-container'); const emptyGameInstancesContainer = document.querySelector('.section-content-off.empty-game-instances-container'); const noServersMessage = emptyGameInstancesContainer?.querySelector('.no-servers-message'); // Check if the "Unable to load servers." message is visible if (!serverCardsContainer && noServersMessage?.textContent.includes('Unable to load servers.')) { notifications('Unable to load servers. Please refresh the page.', 'error', '⚠️', '8000'); return; } // Reset everything if disable is true if (disable) { if (serverCardsContainer) { serverCardsContainer.innerHTML = ''; // Clear contents serverCardsContainer.removeAttribute('style'); // Remove inline styles if present } // Prevent duplicate loading bars const existingLoadingBar = document.querySelector('#loading-bar'); if (existingLoadingBar) { existingLoadingBar.remove(); // Remove the existing loading bar if it exists } // Create and display the loading boxes const loadingContainer = document.createElement('div'); loadingContainer.id = 'loading-bar'; loadingContainer.style.cssText = ` display: flex; justify-content: center; align-items: center; gap: 5px; margin-top: 10px; `; const fragment = document.createDocumentFragment(); for (let i = 0; i < 3; i++) { const box = document.createElement('div'); box.style.cssText = ` width: 10px; height: 10px; background-color: white; margin: 0 5px; border-radius: 2px; animation: pulse 1.2s ${i * 0.2}s infinite; `; fragment.appendChild(box); } loadingContainer.appendChild(fragment); if (serverListSection) { serverListSection.appendChild(loadingContainer); } // Inject CSS only once const existingStyle = document.querySelector('#loading-style'); if (!existingStyle) { const styleSheet = document.createElement('style'); styleSheet.id = 'loading-style'; styleSheet.textContent = ` @keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.5); } } `; document.head.appendChild(styleSheet); } // Remove gradient div if it exists const gradientDiv = document.querySelector('div[style*="background: linear-gradient(145deg"]'); if (gradientDiv) { gradientDiv.remove(); } // ik this approach sucks but its the best i can do. it remove ths premium messages with this specific // text so it doesnet remove the other stuff, you prob cant even understand what im sayin right now const premiumMessageDiv = document.querySelector('.premium-message-text'); if (premiumMessageDiv) { const messageText = premiumMessageDiv.textContent.trim(); const errorMessages = [ "Error: Cannot access server regions because you have not purchased the game.", "Error: This game requires users to teleport to a subplace. As a result, server regions cannot be retrieved." ]; if (errorMessages.includes(messageText)) { showMessage("END"); } } } else { // If disable is false, remove the loading bar const loadingBar = document.querySelector('#loading-bar'); if (loadingBar) { loadingBar.remove(); } // Reset any applied styles const styleSheet = document.querySelector('#loading-style'); if (styleSheet) { styleSheet.remove(); } } } /******************************************************* name of function: fetchPlayerThumbnails description: Fetches player thumbnails for up to 5 players. Skips the batch if an error occurs. *******************************************************/ async function fetchPlayerThumbnails(playerTokens) { // Limit to the first 5 player tokens const limitedTokens = playerTokens.slice(0, 5); const body = limitedTokens.map(token => ({ requestId: `0:${token}:AvatarHeadshot:150x150:png:regular`, type: "AvatarHeadShot", targetId: 0, token, format: "png", size: "150x150", })); try { const response = await fetch("https://thumbnails.roblox.com/v1/batch", { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, body: JSON.stringify(body), }); // Check if the response is successful if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } const data = await response.json(); return data.data || []; // Return the data or an empty array if no data is present } catch (error) { ConsoleLogEnabled('Error fetching player thumbnails:', error); return []; // Return an empty array if an error occurs } } /******************************************************* name of function: disableFilterButton description: Disables or enables the filter button based on the input. *******************************************************/ function disableFilterButton(disable) { const filterButton = document.querySelector('.RL-filter-button'); const refreshButtons = document.querySelectorAll('.btn-more.rbx-refresh.refresh-link-icon.btn-control-xs.btn-min-width'); const filterOverlayId = 'filter-button-overlay'; const refreshOverlayClass = 'refresh-button-overlay'; if (filterButton) { const parent = filterButton.parentElement; if (disable) { // Disable the filter button with an overlay filterButton.disabled = true; filterButton.style.opacity = '0.5'; filterButton.style.cursor = 'not-allowed'; // Create an overlay if it doesn't exist let overlay = document.getElementById(filterOverlayId); if (!overlay) { overlay = document.createElement('div'); overlay.id = filterOverlayId; overlay.style.position = 'absolute'; overlay.style.top = '-10px'; overlay.style.left = '-10px'; overlay.style.width = 'calc(100% + 20px)'; overlay.style.height = 'calc(100% + 20px)'; overlay.style.backgroundColor = 'transparent'; overlay.style.zIndex = '9999'; overlay.style.pointerEvents = 'all'; // Block clicks parent.style.position = 'relative'; parent.appendChild(overlay); } } else { // Enable the filter button filterButton.disabled = false; filterButton.style.opacity = '1'; filterButton.style.cursor = 'pointer'; // Remove the overlay if it exists const overlay = document.getElementById(filterOverlayId); if (overlay) { overlay.remove(); } } } else { ConsoleLogEnabled('Filter button not found! Something is wrong!'); notifications("Something's wrong. Please report and issue on Greasyfork.", "error", "⚠️", "15000"); } if (refreshButtons.length > 0) { refreshButtons.forEach((refreshButton) => { const refreshParent = refreshButton.parentElement; if (disable) { // Create an overlay over the refresh button let refreshOverlay = refreshParent.querySelector(`.${refreshOverlayClass}`); if (!refreshOverlay) { refreshOverlay = document.createElement('div'); refreshOverlay.className = refreshOverlayClass; refreshOverlay.style.position = 'absolute'; refreshOverlay.style.top = '-10px'; refreshOverlay.style.left = '-10px'; refreshOverlay.style.width = 'calc(100% + 20px)'; refreshOverlay.style.height = 'calc(100% + 20px)'; refreshOverlay.style.backgroundColor = 'transparent'; refreshOverlay.style.zIndex = '9999'; refreshOverlay.style.pointerEvents = 'all'; // Block clicks refreshParent.style.position = 'relative'; refreshParent.appendChild(refreshOverlay); } } else { // Remove the overlay if it exists const refreshOverlay = refreshParent.querySelector(`.${refreshOverlayClass}`); if (refreshOverlay) { refreshOverlay.remove(); } } }); } else { ConsoleLogEnabled('Refresh button not found!'); notifications("Something's wrong. Please report and issue on Greasyfork.", "error", "⚠️", "15000"); } } async function rbx_card(serverId, playerTokens, maxPlayers, playing, gameId) { // Fetch player thumbnails (up to 5) const thumbnails = await fetchPlayerThumbnails(playerTokens); // Create the server card container const cardItem = document.createElement('li'); cardItem.className = 'rbx-game-server-item col-md-3 col-sm-4 col-xs-6'; // Create the player thumbnails container const playerThumbnailsContainer = document.createElement('div'); playerThumbnailsContainer.className = 'player-thumbnails-container'; // Add player thumbnails to the container (up to 5) thumbnails.forEach(thumbnail => { const playerAvatar = document.createElement('span'); playerAvatar.className = 'avatar avatar-headshot-md player-avatar'; const thumbnailImage = document.createElement('span'); thumbnailImage.className = 'thumbnail-2d-container avatar-card-image'; const img = document.createElement('img'); img.src = thumbnail.imageUrl; img.alt = ''; img.title = ''; thumbnailImage.appendChild(img); playerAvatar.appendChild(thumbnailImage); playerThumbnailsContainer.appendChild(playerAvatar); }); // Add the 6th placeholder for remaining players if (playing > 5) { const remainingPlayers = playing - 5; const placeholder = document.createElement('span'); placeholder.className = 'avatar avatar-headshot-md player-avatar hidden-players-placeholder'; placeholder.textContent = `+${remainingPlayers}`; placeholder.style.cssText = ` background-color: #6a6f81; /* Grayish-blue background */ color: white; display: flex; align-items: center; justify-content: center; border-radius: 50%; /* Fully round */ font-size: 16px; /* Larger font size */ width: 60px; /* Larger width */ height: 60px; /* Larger height */ `; playerThumbnailsContainer.appendChild(placeholder); } // Create the server details container const serverDetails = document.createElement('div'); serverDetails.className = 'rbx-game-server-details game-server-details'; // Add server status (e.g., "15 of 15 people max") const serverStatus = document.createElement('div'); serverStatus.className = 'text-info rbx-game-status rbx-game-server-status text-overflow'; serverStatus.textContent = `${playing} of ${maxPlayers} people max`; serverDetails.appendChild(serverStatus); // Add the player count gauge const gaugeContainer = document.createElement('div'); gaugeContainer.className = 'server-player-count-gauge border'; const gaugeInner = document.createElement('div'); gaugeInner.className = 'gauge-inner-bar border'; gaugeInner.style.width = `${(playing / maxPlayers) * 100}%`; gaugeContainer.appendChild(gaugeInner); serverDetails.appendChild(gaugeContainer); // Create a container for the buttons const buttonContainer = document.createElement('div'); buttonContainer.className = 'button-container'; buttonContainer.style.cssText = ` display: flex; gap: 8px; /* Space between buttons */ `; // Add the "Join" button const joinButton = document.createElement('button'); joinButton.type = 'button'; joinButton.className = 'btn-full-width btn-control-xs rbx-game-server-join game-server-join-btn btn-primary-md btn-min-width'; joinButton.textContent = 'Join'; // Add click event to join the server joinButton.addEventListener('click', () => { showLoadingOverlay(); Roblox.GameLauncher.joinGameInstance(gameId, serverId); }); buttonContainer.appendChild(joinButton); // Add the "Invite" button const inviteButton = document.createElement('button'); inviteButton.type = 'button'; inviteButton.className = 'btn-full-width btn-control-xs rbx-game-server-invite game-server-invite-btn btn-secondary-md btn-min-width'; inviteButton.textContent = 'Invite'; // Add click event to log the invite link inviteButton.addEventListener('click', () => { const inviteLink = `https://oqarshi.github.io/Invite/?placeid=${gameId}&serverid=${serverId}`; ConsoleLogEnabled('Copied invite link:', inviteLink); navigator.clipboard.writeText(inviteLink).then(() => { notifications('Success! Invite link copied to clipboard!', 'success', '🎉', '2000'); ConsoleLogEnabled('Invite link copied to clipboard'); }).catch(err => { ConsoleLogEnabled('Failed to copy invite link:', err); notifications('Failed! Invite link copied to clipboard!', 'error', '⚠️', '2000'); }); }); buttonContainer.appendChild(inviteButton); // Add the button container to the server details serverDetails.appendChild(buttonContainer); // Assemble the card const cardContainer = document.createElement('div'); cardContainer.className = 'card-item'; cardContainer.appendChild(playerThumbnailsContainer); cardContainer.appendChild(serverDetails); cardItem.appendChild(cardContainer); // Add the card to the server list const serverList = document.querySelector('#rbx-public-game-server-item-container'); serverList.appendChild(cardItem); } /********************************************************************************************************************************************************************************************************************************************* End of function for the notification function *********************************************************************************************************************************************************************************************************************************************/ /********************************************************************************************************************************************************************************************************************************************* Launching Function *********************************************************************************************************************************************************************************************************************************************/ function showLoadingOverlay() { // Create the content div (no overlay background) const content = document.createElement('div'); content.style.position = 'fixed'; content.style.top = 'calc(50% - 15px)'; // Move 15px higher content.style.left = '50%'; content.style.transform = 'translate(-50%, -50%)'; // Center the box horizontally content.style.width = '400px'; content.style.height = '250px'; content.style.backgroundColor = '#1e1e1e'; // Dark background content.style.display = 'flex'; content.style.flexDirection = 'column'; content.style.justifyContent = 'center'; content.style.alignItems = 'center'; content.style.boxShadow = '0 4px 20px rgba(0, 0, 0, 0.5)'; // Subtle shadow content.style.borderRadius = '12px'; // Slightly rounded corners content.style.zIndex = '1000000'; // z-index set to 1 million content.style.opacity = '0'; // Start with 0 opacity for fade-in content.style.transition = 'opacity 0.5s ease'; // Fade-in transition content.style.fontFamily = 'Arial, sans-serif'; // Clean font // Create the loading text const loadingText = document.createElement('p'); loadingText.textContent = 'Joining Roblox Game...'; loadingText.style.fontSize = '20px'; loadingText.style.color = '#fff'; // White text for contrast loadingText.style.marginTop = '20px'; // Spacing between image and text loadingText.style.fontWeight = 'bold'; // Bold text // Create the base64 image const base64Image = document.createElement('img'); base64Image.src = window.Base64Images.logo; base64Image.style.width = '80px'; // Slightly larger image base64Image.style.height = '80px'; // Slightly larger image base64Image.style.borderRadius = '8px'; // Rounded corners for the image // Create the loading boxes container const loadingBoxes = document.createElement('div'); loadingBoxes.style.display = 'flex'; loadingBoxes.style.alignItems = 'center'; loadingBoxes.style.justifyContent = 'center'; loadingBoxes.style.marginTop = '15px'; // Spacing between text and boxes // Create the three loading boxes for (let i = 0; i < 3; i++) { const box = document.createElement('div'); box.style.width = '10px'; box.style.height = '10px'; box.style.backgroundColor = '#fff'; // White boxes box.style.margin = '0 5px'; // Spacing between boxes box.style.borderRadius = '2px'; // Slightly rounded corners box.style.animation = `pulse 1.2s ${i * 0.2}s infinite`; // Animation with delay loadingBoxes.appendChild(box); } // Define the pulse animation using CSS const style = document.createElement('style'); style.textContent = ` @keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.5); } } `; document.head.appendChild(style); // Add the animation to the document // Append the image, text, and loading boxes to the content div content.appendChild(base64Image); content.appendChild(loadingText); content.appendChild(loadingBoxes); // Append the content div to the body document.body.appendChild(content); // Trigger fade-in animation setTimeout(() => { content.style.opacity = '1'; }, 10); // Small delay to trigger the transition // Remove the content after 8 seconds with fade-out animation setTimeout(() => { content.style.opacity = '0'; // Fade out setTimeout(() => { document.body.removeChild(content); // Remove after fade-out completes }, 500); // Wait for the fade-out transition to finish }, 8000); // 8000 milliseconds = 8 seconds } /********************************************************************************************************************************************************************************************************************************************* End of function for the launching function *********************************************************************************************************************************************************************************************************************************************/ const serverRegionsByIp = { "128.116.0.0": { city: "Hong Kong", country: { name: "Hong Kong", code: "HK" }, latitude: 22.3193, longitude: 114.1694 }, "128.116.1.0": { city: "Los Angeles", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 34.0522, longitude: -118.2437 }, "128.116.2.0": { city: "Warsaw", country: { name: "Poland", code: "PL" }, region: { name: "Mazowieckie", code: "14" }, latitude: 52.2297, longitude: 21.0122 }, "128.116.3.0": { city: "Warsaw", country: { name: "Poland", code: "PL" }, region: { name: "Mazowieckie", code: "14" }, latitude: 52.2297, longitude: 21.0122 }, "128.116.4.0": { city: "Paris", country: { name: "France", code: "FR" }, region: { name: "Île-de-France", code: "IDF" }, latitude: 48.8566, longitude: 2.3522 }, "128.116.5.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 }, "128.116.6.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 }, "128.116.7.0": { city: "Mumbai", country: { name: "India", code: "IN" }, region: { name: "Mahārāshtra", code: "MH" }, latitude: 19.0760, longitude: 72.8777 }, "128.116.8.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 }, "128.116.9.0": { city: "Mumbai", country: { name: "India", code: "IN" }, region: { name: "Mahārāshtra", code: "MH" }, latitude: 19.0760, longitude: 72.8777 }, "128.116.10.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 }, "128.116.11.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 }, "128.116.12.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 }, "128.116.13.0": { city: "Amsterdam", country: { name: "Netherlands", code: "NL" }, region: { name: "Noord-Holland", code: "NH" }, latitude: 52.3676, longitude: 4.9041 }, "128.116.14.0": { city: "Hong Kong", country: { name: "Hong Kong", code: "HK" }, latitude: 22.3193, longitude: 114.1694 }, "128.116.15.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 }, "128.116.16.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 }, "128.116.17.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 }, "128.116.18.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 }, "128.116.19.0": { city: "Paris", country: { name: "France", code: "FR" }, region: { name: "Île-de-France", code: "IDF" }, latitude: 48.8566, longitude: 2.3522 }, "128.116.20.0": { city: "Paris", country: { name: "France", code: "FR" }, region: { name: "Île-de-France", code: "IDF" }, latitude: 48.8566, longitude: 2.3522 }, "128.116.21.0": { city: "Amsterdam", country: { name: "Netherlands", code: "NL" }, region: { name: "Noord-Holland", code: "NH" }, latitude: 52.3676, longitude: 4.9041 }, "128.116.22.0": { city: "Atlanta", country: { name: "United States", code: "US" }, region: { name: "Georgia", code: "GA" }, latitude: 33.7490, longitude: -84.3880 }, "128.116.23.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 }, "128.116.24.0": { city: "Atlanta", country: { name: "United States", code: "US" }, region: { name: "Georgia", code: "GA" }, latitude: 33.7490, longitude: -84.3880 }, "128.116.25.0": { city: "Atlanta", country: { name: "United States", code: "US" }, region: { name: "Georgia", code: "GA" }, latitude: 33.7490, longitude: -84.3880 }, "128.116.26.0": { city: "Paris", country: { name: "France", code: "FR" }, region: { name: "Île-de-France", code: "IDF" }, latitude: 48.8566, longitude: 2.3522 }, "128.116.27.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 }, "128.116.28.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 }, "128.116.29.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 }, "128.116.30.0": { city: "Hong Kong", country: { name: "Hong Kong", code: "HK" }, latitude: 22.3193, longitude: 114.1694 }, "128.116.31.0": { city: "Warsaw", country: { name: "Poland", code: "PL" }, region: { name: "Mazowieckie", code: "14" }, latitude: 52.2297, longitude: 21.0122 }, "128.116.32.0": { city: "New York City", country: { name: "United States", code: "US" }, region: { name: "New York", code: "NY" }, latitude: 40.7128, longitude: -74.0060 }, "128.116.33.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 }, "128.116.34.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 }, "128.116.35.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 }, "128.116.36.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 }, "128.116.37.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 }, "128.116.38.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 }, "128.116.39.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 }, "128.116.40.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 }, "128.116.41.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 }, "128.116.42.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 }, "128.116.43.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 }, "128.116.44.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 }, "128.116.45.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 }, "128.116.46.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 }, "128.116.47.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 }, "128.116.48.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 }, "128.116.49.0": { city: "Los Angeles", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 34.0522, longitude: -118.2437 }, "128.116.50.0": { city: "Singapore", country: { name: "Singapore", code: "SG" }, latitude: 1.3521, longitude: 103.8198 }, "128.116.51.0": { city: "Sydney", country: { name: "Australia", code: "AU" }, region: { name: "New South Wales", code: "NSW" }, latitude: -33.8688, longitude: 151.2093 }, "128.116.52.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 }, "128.116.53.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 }, "128.116.54.0": { city: "Amsterdam", country: { name: "Netherlands", code: "NL" }, region: { name: "Noord-Holland", code: "NH" }, latitude: 52.3676, longitude: 4.9041 }, "128.116.55.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 }, "128.116.56.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 }, "128.116.57.0": { city: "San Jose", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3382, longitude: -121.8863 }, "128.116.58.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 }, "128.116.59.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 }, "128.116.60.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 }, "128.116.61.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 }, "128.116.62.0": { city: "Seattle", country: { name: "United States", code: "US" }, region: { name: "Washington", code: "WA" }, latitude: 47.6062, longitude: -122.3321 }, "128.116.63.0": { city: "Los Angeles", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 34.0522, longitude: -118.2437 }, "128.116.64.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 }, "128.116.65.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 }, "128.116.66.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 }, "128.116.67.0": { city: "San Jose", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3382, longitude: -121.8863 }, "128.116.68.0": { city: "Santa Clara", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3541, longitude: -121.9552 }, "128.116.69.0": { city: "Santa Clara", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3541, longitude: -121.9552 }, "128.116.70.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 }, "128.116.71.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 }, "128.116.72.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 }, "128.116.73.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 }, "128.116.74.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 }, "128.116.75.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 }, "128.116.76.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 }, "128.116.77.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 }, "128.116.78.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 }, "128.116.79.0": { city: "Singapore", country: { name: "Singapore", code: "SG" }, latitude: 1.3521, longitude: 103.8198 }, "128.116.80.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 }, "128.116.81.0": { city: "Santa Clara", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3541, longitude: -121.9552 }, "128.116.82.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 }, "128.116.83.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 }, "128.116.84.0": { city: "Elk Grove Village", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 42.0039, longitude: -87.9706 }, "128.116.85.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 }, "128.116.86.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 }, "128.116.87.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 }, "128.116.88.0": { city: "Elk Grove Village", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 42.0039, longitude: -87.9706 }, "128.116.89.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 }, "128.116.90.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 }, "128.116.91.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 }, "128.116.92.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 }, "128.116.93.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 }, "128.116.94.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 }, "128.116.95.0": { city: "Dallas", country: { name: "United States", code: "US" }, region: { name: "Texas", code: "TX" }, latitude: 32.7767, longitude: -96.7970 }, "128.116.96.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 }, "128.116.97.0": { city: "Singapore", country: { name: "Singapore", code: "SG" }, latitude: 1.3521, longitude: 103.8198 }, "128.116.98.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 }, "128.116.99.0": { city: "Atlanta", country: { name: "United States", code: "US" }, region: { name: "Georgia", code: "GA" }, latitude: 33.7490, longitude: -84.3880 }, "128.116.100.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 }, "128.116.101.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 }, "128.116.102.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 }, "128.116.103.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 }, "128.116.104.0": { city: "Mumbai", country: { name: "India", code: "IN" }, region: { name: "Mahārāshtra", code: "MH" }, latitude: 19.0760, longitude: 72.8777 }, "128.116.105.0": { city: "Santa Clara", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3541, longitude: -121.9552 }, "128.116.106.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 }, "128.116.107.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 }, "128.116.108.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 }, "128.116.109.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 }, "128.116.110.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 }, "128.116.111.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 }, "128.116.112.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 }, "128.116.113.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 }, "128.116.114.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 }, "128.116.115.0": { city: "Seattle", country: { name: "United States", code: "US" }, region: { name: "Washington", code: "WA" }, latitude: 47.6062, longitude: -122.3321 }, "128.116.116.0": { city: "Los Angeles", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 34.0522, longitude: -118.2437 }, "128.116.117.0": { city: "San Jose", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3382, longitude: -121.8863 }, "128.116.118.0": { city: "Hong Kong", country: { name: "Hong Kong", code: "HK" }, latitude: 22.3193, longitude: 114.1694 }, "128.116.119.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 }, "128.116.120.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 }, "128.116.121.0": { city: "Amsterdam", country: { name: "Netherlands", code: "NL" }, region: { name: "Noord-Holland", code: "NH" }, latitude: 52.3676, longitude: 4.9041 }, "128.116.122.0": { city: "Paris", country: { name: "France", code: "FR" }, region: { name: "Île-de-France", code: "IDF" }, latitude: 48.8566, longitude: 2.3522 }, "128.116.123.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 }, "128.116.124.0": { city: "Warsaw", country: { name: "Poland", code: "PL" }, region: { name: "Mazowieckie", code: "14" }, latitude: 52.2297, longitude: 21.0122 }, "128.116.125.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 }, "128.116.126.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 }, "128.116.127.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 }, }; // find_game_id function does nothing lmao but its ere i guees function find_game_id() { ConsoleLogEnabled('Trying to find game id'); const gameIdMatch = window.location.pathname.match(/\/games\/(\d+)\//); if (!gameIdMatch) { ConsoleLogEnabled("Game ID not found in URL!"); return; } const gameId = gameIdMatch[1]; } // end of the check for the url } /******************************************************* End of code for the random hop button and the filter button on roblox.com/games/* *******************************************************/ })();