// ==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.org/scripts/526611/1574250/Rolocate%20Base64%20Image%20Library.js // @downloadURL https://update.greasyfork.icu/scripts/523727/RoLocate.user.js // @updateURL https://update.greasyfork.icu/scripts/523727/RoLocate.meta.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 = `

Settings

Home

${getSettingsContent("home")}
`; 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 ` Rolocate Settings Menu. `; } if (section === "appearance") { return ` `; } if (section === "advanced") { return ` For Experienced Users Only 🧠🙃 `; } if (section === "about") { return `
Rolocate: Version 34.3

Credits

This project was created by:

`; } // the help if (section === "help") { return `

General Tab:

Appearance Tab:

Advanced Tab:

`; } // the general return `
`; } 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 = `

Quick Navigation

Configure up to 9 custom navigation shortcuts

Logo
`; // Build inputs for 9 links in a 3x3 grid const inputsGrid = `
${Array.from({length: 9}, (_, i) => `

${i + 1}

`).join("")}
`; // Build footer with buttons const footer = `
`; // 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 = ` Settings `; 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 = ''; break; case 'error': iconContent = ''; break; case 'warning': iconContent = ''; break; case 'info': default: iconContent = ''; 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 = `
×
`; 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 = `${countdown}`; 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 = ` 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 = ` `; 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 = `Game ID: ${gameId} Server ID: ${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 = ` `; 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 = ` 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 = ` 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 = ` 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 = ` 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, "$1"); 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, '$1'); 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, '$1'); }; // 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