RoLocate

Adds filter options to roblox server page. Alternative to paid extensions like RoPro, RoGold (Ultimate), RoQol, and RoKit.

  1. // ==UserScript==
  2. // @name RoLocate
  3. // @namespace https://oqarshi.github.io/
  4. // @version 33.3
  5. // @description Adds filter options to roblox server page. Alternative to paid extensions like RoPro, RoGold (Ultimate), RoQol, and RoKit.
  6. // @author Oqarshi
  7. // @match https://www.roblox.com/*
  8. // @license CC-BY-4.0; https://creativecommons.org/licenses/by/4.0/
  9. // @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
  10. // @grant GM_xmlhttpRequest
  11. // @require https://update.gf.qytechs.cn/scripts/526611/1535754/Rolocate%20Base64%20Image%20Library.js
  12. // ==/UserScript==
  13.  
  14.  
  15.  
  16. (function() {
  17. 'use strict';
  18.  
  19.  
  20.  
  21. function initializeLocalStorage() {
  22. // Define default settings
  23. const defaultSettings = {
  24. enableLogs: false, // disabled by default
  25. removeads: false, // disabled by default
  26. togglefilterserversbutton: true, // enable by default
  27. AutoRunServerRegions: false, // disabled by default
  28. };
  29.  
  30. // Loop through default settings and set them in localStorage if they don't exist
  31. Object.entries(defaultSettings).forEach(([key, value]) => {
  32. const storageKey = `ROLOCATE_${key}`;
  33. if (localStorage.getItem(storageKey) === null) {
  34. localStorage.setItem(storageKey, value);
  35. }
  36. });
  37. }
  38.  
  39. function openSettingsMenu() {
  40. if (document.getElementById("userscript-settings-menu")) return;
  41.  
  42. // Initialize localStorage with default values if they don't exist
  43. initializeLocalStorage();
  44.  
  45. // Create overlay
  46. const overlay = document.createElement("div");
  47. overlay.id = "userscript-settings-menu";
  48. overlay.innerHTML = `
  49. <div class="settings-container">
  50. <button id="close-settings" class="close-hover">✖</button>
  51. <div class="settings-sidebar">
  52. <h2>Settings</h2>
  53. <ul>
  54. <li class="active" data-section="home">🏠 Home</li>
  55. <li data-section="general">⚙️ General</li>
  56. <li data-section="appearance">🎨 Appearance</li>
  57. <li data-section="advanced">🚀 Advanced</li>
  58. <li data-section ="help">📙 Help</li>
  59. <li data-section="about">ℹ️ About</li>
  60. </ul>
  61. </div>
  62. <div class="settings-content">
  63. <h2 id="settings-title">Home</h2>
  64. <div id="settings-body">${getSettingsContent("home")}</div>
  65. </div>
  66. </div>
  67. `;
  68.  
  69. document.body.appendChild(overlay);
  70.  
  71. // Inject styles
  72. const style = document.createElement("style");
  73. style.textContent = `
  74. @keyframes fadeIn {
  75. from { opacity: 0; transform: scale(0.95); }
  76. to { opacity: 1; transform: scale(1); }
  77. }
  78. @keyframes fadeOut {
  79. from { opacity: 1; transform: scale(1); }
  80. to { opacity: 0; transform: scale(0.95); }
  81. }
  82. @keyframes sectionFade {
  83. from { opacity: 0; transform: translateY(10px); }
  84. to { opacity: 1; transform: translateY(0); }
  85. }
  86.  
  87. #userscript-settings-menu {
  88. position: fixed;
  89. top: 0; left: 0;
  90. width: 100vw; height: 100vh;
  91. background: rgba(0,0,0,0.6);
  92. display: flex;
  93. align-items: center;
  94. justify-content: center;
  95. z-index: 10000;
  96. animation: fadeIn 0.3s ease-out;
  97. }
  98. .settings-container {
  99. display: flex;
  100. position: relative;
  101. width: 520px; height: 380px;
  102. background: #1e1e1e;
  103. border-radius: 14px;
  104. overflow: hidden;
  105. box-shadow: 0 12px 24px rgba(0,0,0,0.5);
  106. font-family: Arial, sans-serif;
  107. }
  108. #close-settings {
  109. position: absolute;
  110. top: 12px;
  111. right: 12px;
  112. background: transparent;
  113. border: none;
  114. color: white;
  115. font-size: 20px;
  116. cursor: pointer;
  117. z-index: 10001;
  118. }
  119. .settings-sidebar {
  120. width: 35%;
  121. background: #272727;
  122. padding: 15px;
  123. color: white;
  124. display: flex;
  125. flex-direction: column;
  126. align-items: center;
  127. }
  128. .settings-sidebar ul {
  129. list-style: none;
  130. padding: 0;
  131. width: 100%;
  132. }
  133. .settings-sidebar li {
  134. padding: 12px;
  135. text-align: center;
  136. cursor: pointer;
  137. transition: 0.3s;
  138. border-radius: 6px;
  139. font-weight: bold;
  140. }
  141. .settings-sidebar li:hover, .settings-sidebar .active {
  142. background: #444;
  143. }
  144. /* Custom Scrollbar */
  145. .settings-content {
  146. flex: 1;
  147. padding: 20px;
  148. color: white;
  149. text-align: center;
  150. max-height: 320px;
  151. overflow-y: auto;
  152. scrollbar-width: auto;
  153. scrollbar-color: darkgreen black;
  154. }
  155.  
  156. /* Webkit (Chrome, Safari) Scrollbar */
  157. .settings-content::-webkit-scrollbar {
  158. width: 14px; /* Increased thickness but it doesent work for some reason */
  159. }
  160. .settings-content::-webkit-scrollbar-track {
  161. background: black;
  162. border-radius: 7px;
  163. }
  164. .settings-content::-webkit-scrollbar-thumb {
  165. background: darkgreen;
  166. border-radius: 7px;
  167. }
  168. .settings-content::-webkit-scrollbar-thumb:hover {
  169. background: #006400; /* Darker green on hover */
  170. }
  171. .settings-content h2,
  172. .settings-content div {
  173. animation: sectionFade 0.3s ease-in-out;
  174. }
  175. .close-hover {
  176. position: relative;
  177. color: black;
  178. transition: color 0.3s ease;
  179. background: none;
  180. border: none;
  181. font-size: 1.5rem;
  182. cursor: pointer;
  183. }
  184. .close-hover::after {
  185. content: "" !important;
  186. position: absolute !important;
  187. left: 0 !important;
  188. bottom: -2px !important;
  189. width: 0% !important;
  190. height: 2px !important;
  191. background-color: red !important;
  192. transition: width 0.3s ease !important;
  193. }
  194. .close-hover:hover {
  195. color: red !important;
  196. }
  197. .close-hover:hover::after {
  198. width: 100% !important;
  199. }
  200.  
  201. /* Toggle Slider Styles */
  202. .toggle-slider {
  203. display: flex;
  204. align-items: center;
  205. margin: 10px 0;
  206. cursor: pointer;
  207. }
  208. .toggle-slider input {
  209. display: none;
  210. }
  211. .toggle-slider .slider {
  212. position: relative;
  213. display: inline-block;
  214. width: 40px;
  215. height: 20px;
  216. background-color: #A9A9A9;
  217. border-radius: 20px;
  218. margin-right: 10px;
  219. transition: background-color 0.3s;
  220. }
  221. .toggle-slider .slider::before {
  222. content: "";
  223. position: absolute;
  224. height: 16px;
  225. width: 16px;
  226. left: 2px;
  227. bottom: 2px;
  228. background-color: white;
  229. border-radius: 50%;
  230. transition: transform 0.3s;
  231. }
  232. .toggle-slider input:checked + .slider {
  233. background-color: #4CAF50;
  234. }
  235. .toggle-slider input:checked + .slider::before {
  236. transform: translateX(20px);
  237. }
  238. .rolocate-logo {
  239. width: 75px !important; /* Force width */
  240. height: 75px !important; /* Ensure proper scaling */
  241. object-fit: contain; /* Prevent distortion */
  242. border-radius: 10px; /* Rounded corners */
  243. display: block;
  244. margin: 0 auto 10px auto; /* Center and add spacing */
  245. }
  246. .version {
  247. font-size: 14px;
  248. color: #aaa;
  249. margin-bottom: 20px;
  250. }
  251. .settings-content ul {
  252. text-align: left;
  253. list-style-type: none;
  254. padding: 0;
  255. }
  256. .settings-content ul li {
  257. margin: 10px 0;
  258. }
  259. .settings-content ul li a {
  260. color: #4CAF50;
  261. text-decoration: none;
  262. }
  263. .settings-content ul li a:hover {
  264. text-decoration: underline;
  265. }
  266. .warning_advanced {
  267. font-size: 14px; /* Adjust size as needed */
  268. color: red;
  269. font-weight: bold;
  270. }
  271. .average_text {
  272. font-size: 16px;
  273. color: grey;
  274. font-weight: bold;
  275. }
  276. h2 {
  277. text-decoration: underline;
  278. }
  279.  
  280. `;
  281. document.head.appendChild(style);
  282.  
  283. // Sidebar logic with animation
  284. document.querySelectorAll(".settings-sidebar li").forEach(li => {
  285. li.addEventListener("click", function() {
  286. const currentActive = document.querySelector(".settings-sidebar .active");
  287. if (currentActive) currentActive.classList.remove("active");
  288. this.classList.add("active");
  289.  
  290. const section = this.getAttribute("data-section");
  291. const settingsBody = document.getElementById("settings-body");
  292. const settingsTitle = document.getElementById("settings-title");
  293.  
  294. // Apply fade-out first
  295. settingsBody.style.animation = "fadeOut 0.2s ease-in forwards";
  296. settingsTitle.style.animation = "fadeOut 0.2s ease-in forwards";
  297.  
  298. setTimeout(() => {
  299. // Update content
  300. settingsTitle.textContent = section.charAt(0).toUpperCase() + section.slice(1);
  301. settingsBody.innerHTML = getSettingsContent(section);
  302.  
  303. // Apply fade-in animation
  304. settingsBody.style.animation = "sectionFade 0.3s ease-in-out forwards";
  305. settingsTitle.style.animation = "sectionFade 0.3s ease-in-out forwards";
  306.  
  307. applyStoredSettings();
  308. }, 200);
  309. });
  310. });
  311.  
  312. // Close button with fade-out animation
  313. document.getElementById("close-settings").addEventListener("click", function() {
  314. overlay.style.animation = "fadeOut 0.3s ease-in forwards";
  315. setTimeout(() => overlay.remove(), 300);
  316. });
  317.  
  318. // Apply stored settings on open
  319. applyStoredSettings();
  320. }
  321.  
  322. function getSettingsContent(section) {
  323. if (section === "home") {
  324. return `
  325. <img class="rolocate-logo" src="${window.Base64Images.logo}" alt="ROLOCATE Logo">
  326. <span class="average_text">Rolocate Settings Menu.</span>
  327. `;
  328. }
  329. if (section === "appearance") {
  330. return `
  331. <span class="average_text">Nothing to see here! Come back later for more awesome features! 😊</span>
  332. `;
  333. }
  334. if (section === "advanced") {
  335. return `
  336. <span class="warning_advanced">⚠️ Warning: Do not edit unless you know what you're doing! ⚠️</span>
  337. <label class="toggle-slider">
  338. <input type="checkbox" id="enableLogs">
  339. <span class="slider"></span>
  340. Enable Console Logs
  341. </label>
  342. <label class="toggle-slider">
  343. <input type="checkbox" id="togglefilterserversbutton">
  344. <span class="slider"></span>
  345. Enable Filter & Server Hop
  346. </label>
  347. `;
  348. }
  349. if (section === "about") {
  350. return `
  351. <div class="version">Rolocate: Version 33.3</div>
  352. <h2>Credits</h2>
  353. <p>This project was created by:</p>
  354. <ul>
  355. <li>Developer: <a href="https://www.roblox.com/users/545334824/profile" target="_blank">Oqarshi</a></li>
  356. <li>Special Thanks: <a href="https://chromewebstore.google.com/detail/btroblox-making-roblox-be/hbkpclpemjeibhioopcebchdmohaieln" target="_blank">Btroblox Team</a></li>
  357. <li>Roblox Locate: <a href="https://gf.qytechs.cn/en/scripts/523727-rolocate" target="_blank">GreasyFork</a></li>
  358. <li>Invite & FAQ Source Code: <a href="https://github.com/Oqarshi/Invite" target="_blank">GitHub</a></li>
  359. <li>FAQ Website: <a href="https://oqarshi.github.io/Invite/rolocate/index.html" target="_blank">RoLocate FAQ</a></li>
  360. </ul>
  361. `;
  362. } // the help
  363. if (section === "help") {
  364. return `
  365. <h2>General Tab:</h2>
  366. <ul>
  367. <li>Auto Run Server Regions: <a>Replaces Roblox's 8 default servers with at least 8 servers, providing detailed info such as location and ping.</a></li>
  368. <li>Remove All Roblox Ads: <a>Blocks most ads on the roblox site. Still experimental.</a></li>
  369. </ul>
  370. <h2>Appearance Tab:</h2>
  371. <ul>
  372. <li>Nothing yet!</a></li>
  373. </ul>
  374. <h2>Advanced Tab:</h2>
  375. <ul>
  376. <li>Enable Console Logs: <a>Enables console.log messages from the script.</a></li>
  377. <li>Enable Filter & Server Hop: <a>Enables filter and server hop features on the game page.</a></li>
  378. </ul>
  379. `;
  380. } // the general
  381. return `
  382. <label class="toggle-slider">
  383. <input type="checkbox" id="AutoRunServerRegions">
  384. <span class="slider"></span>
  385. Auto Run Server Regions
  386. </label>
  387. <label class="toggle-slider">
  388. <input type="checkbox" id="removeads">
  389. <span class="slider"></span>
  390. Remove All Roblox Ads
  391. </label>
  392. `;
  393. }
  394.  
  395. function applyStoredSettings() {
  396. document.querySelectorAll("input[type='checkbox']").forEach(checkbox => {
  397. const storageKey = `ROLOCATE_${checkbox.id}`;
  398. checkbox.checked = localStorage.getItem(storageKey) === "true";
  399.  
  400. checkbox.addEventListener("change", () => {
  401. localStorage.setItem(storageKey, checkbox.checked);
  402. });
  403. });
  404. }
  405.  
  406. function AddSettingsButton() {
  407. const base64Logo = window.Base64Images.logo;
  408. const navbarGroup = document.querySelector('.nav.navbar-right.rbx-navbar-icon-group');
  409. if (!navbarGroup || document.getElementById('custom-logo')) return;
  410.  
  411. const li = document.createElement('li');
  412. li.id = 'custom-logo-container';
  413. li.style.position = 'relative';
  414.  
  415. li.innerHTML = `
  416. <img id="custom-logo"
  417. style="
  418. margin-top: 6px;
  419. margin-left: 6px;
  420. width: 26px;
  421. cursor: pointer;
  422. border-radius: 4px;
  423. transition: all 0.2s ease-in-out;
  424. "
  425. src="${base64Logo}">
  426. <span id="custom-tooltip"
  427. style="
  428. visibility: hidden;
  429. background-color: black;
  430. color: white;
  431. text-align: center;
  432. padding: 5px;
  433. border-radius: 5px;
  434. position: absolute;
  435. top: 35px;
  436. left: 50%;
  437. transform: translateX(-50%);
  438. white-space: nowrap;
  439. font-size: 12px;
  440. opacity: 0;
  441. transition: opacity 0.2s ease-in-out;
  442. ">
  443. Settings
  444. </span>
  445. `;
  446.  
  447. const logo = li.querySelector('#custom-logo');
  448. const tooltip = li.querySelector('#custom-tooltip');
  449.  
  450. logo.addEventListener('click', () => openSettingsMenu());
  451.  
  452. logo.addEventListener('mouseover', () => {
  453. logo.style.width = '30px';
  454. logo.style.border = '2px solid white';
  455. tooltip.style.visibility = 'visible';
  456. tooltip.style.opacity = '1';
  457. });
  458.  
  459. logo.addEventListener('mouseout', () => {
  460. logo.style.width = '26px';
  461. logo.style.border = 'none';
  462. tooltip.style.visibility = 'hidden';
  463. tooltip.style.opacity = '0';
  464. });
  465.  
  466. navbarGroup.appendChild(li);
  467. }
  468.  
  469.  
  470.  
  471.  
  472. /*************************************************************************
  473. notification function
  474. *************************************************************************/
  475. function notifications(message, type = 'info', emoji = '', duration = 3000) {
  476. // Helper function to darken (or lighten) a hex color.
  477. // Pass a negative percent to darken, a positive percent to lighten.
  478. function shadeColor(color, percent) {
  479. let num = parseInt(color.slice(1), 16),
  480. amt = Math.round(2.55 * percent),
  481. R = (num >> 16) + amt,
  482. G = ((num >> 8) & 0xFF) + amt,
  483. B = (num & 0xFF) + amt;
  484. R = Math.max(Math.min(255, R), 0);
  485. G = Math.max(Math.min(255, G), 0);
  486. B = Math.max(Math.min(255, B), 0);
  487. return "#" + ((1 << 24) + (R << 16) + (G << 8) + B).toString(16).slice(1);
  488. }
  489.  
  490. // Inject CSS styles for the toast and close button once
  491. if (!document.getElementById('toast-styles')) {
  492. const style = document.createElement('style');
  493. style.id = 'toast-styles';
  494. style.innerHTML = `
  495. #toast-container {
  496. position: fixed;
  497. top: 20px;
  498. right: 20px;
  499. z-index: 9999;
  500. display: flex;
  501. flex-direction: column;
  502. gap: 10px;
  503. }
  504.  
  505. .toast {
  506. position: relative;
  507. min-width: 300px;
  508. max-width: 400px;
  509. padding: 15px 20px;
  510. border-radius: 8px;
  511. box-shadow: 0 2px 10px rgba(0,0,0,0.15);
  512. opacity: 0;
  513. transform: translateX(50px);
  514. transition: opacity 0.5s ease, transform 0.5s ease;
  515. font-family: Arial, sans-serif;
  516. word-wrap: break-word;
  517. }
  518.  
  519. .toast .toast-content {
  520. display: flex;
  521. align-items: center;
  522. }
  523.  
  524. .toast .toast-close-btn {
  525. position: absolute;
  526. top: 8px;
  527. right: 12px;
  528. cursor: pointer;
  529. font-weight: bold;
  530. font-size: 18px;
  531. line-height: 18px;
  532. color: #fff;
  533. display: inline-block;
  534. }
  535.  
  536. /* Underline animation for close button */
  537. .toast .toast-close-btn::after {
  538. content: '';
  539. position: absolute;
  540. left: 0;
  541. bottom: -2px;
  542. width: 100%;
  543. height: 2px;
  544. background: currentColor;
  545. transform: scaleX(0);
  546. transform-origin: left;
  547. transition: transform 0.3s ease;
  548. }
  549.  
  550. .toast .toast-close-btn:hover::after {
  551. transform: scaleX(1);
  552. }
  553.  
  554. .toast .progress-bar {
  555. position: absolute;
  556. bottom: 0;
  557. left: 0;
  558. height: 4px;
  559. background-color: rgba(255,255,255,0.7);
  560. width: 100%;
  561. border-bottom-left-radius: 8px;
  562. border-bottom-right-radius: 8px;
  563. }
  564. `;
  565. document.head.appendChild(style);
  566. }
  567.  
  568. // Create or get the container
  569. let container = document.getElementById('toast-container');
  570. if (!container) {
  571. container = document.createElement('div');
  572. container.id = 'toast-container';
  573. document.body.appendChild(container);
  574. }
  575.  
  576. // Create toast element
  577. const toast = document.createElement('div');
  578. toast.className = 'toast';
  579.  
  580. // Determine the base color based on type
  581. let baseColor;
  582. switch (type.toLowerCase()) {
  583. case 'success':
  584. baseColor = '#4CAF50';
  585. break;
  586. case 'error':
  587. baseColor = '#F44336';
  588. break;
  589. case 'info':
  590. default:
  591. baseColor = '#2196F3';
  592. break;
  593. }
  594.  
  595. // Create a dark version of the base color (darkened by 20%)
  596. let darkColor = shadeColor(baseColor, -20);
  597.  
  598. // Set a gradient background from the dark variant to the base color
  599. toast.style.background = `linear-gradient(90deg, ${darkColor}, ${baseColor})`;
  600.  
  601. // Create content wrapper with optional emoji
  602. const content = document.createElement('div');
  603. content.className = 'toast-content';
  604. content.innerHTML = `${emoji ? `<span style="margin-right:8px;">${emoji}</span>` : ''}<span>${message}</span>`;
  605. toast.appendChild(content);
  606.  
  607. // Create the close (×) button with underline animation on hover
  608. const closeBtn = document.createElement('span');
  609. closeBtn.className = 'toast-close-btn';
  610. closeBtn.innerHTML = '&times;';
  611. closeBtn.addEventListener('click', () => removeToast(toast));
  612. toast.appendChild(closeBtn);
  613.  
  614. // Create progress bar
  615. const progressBar = document.createElement('div');
  616. progressBar.className = 'progress-bar';
  617. // Set the progress bar's transition to match the duration
  618. progressBar.style.transition = `width ${duration}ms linear`;
  619. toast.appendChild(progressBar);
  620.  
  621. // Append toast to container and animate in
  622. container.appendChild(toast);
  623. setTimeout(() => {
  624. toast.style.opacity = '1';
  625. toast.style.transform = 'translateX(0)';
  626. // Start progress bar animation
  627. setTimeout(() => {
  628. progressBar.style.width = '0%';
  629. }, 50);
  630. }, 50);
  631.  
  632. // Auto-remove toast after the specified duration
  633. const removeTimeout = setTimeout(() => removeToast(toast), duration);
  634.  
  635. // Function to fade out and remove toast
  636. function removeToast(toastEl) {
  637. clearTimeout(removeTimeout);
  638. toastEl.style.opacity = '0';
  639. toastEl.style.transform = 'translateX(50px)';
  640. setTimeout(() => toastEl.remove(), 500);
  641. }
  642. }
  643.  
  644.  
  645. function Update_Popup() {
  646. const VERSION = "V33.3";
  647. const PREV_VERSION = "V32.3";
  648.  
  649. if (localStorage.getItem(PREV_VERSION)) {
  650. localStorage.removeItem(PREV_VERSION);
  651. }
  652.  
  653. if (localStorage.getItem(VERSION)) return;
  654. localStorage.setItem(VERSION, "true");
  655.  
  656. const css = `
  657. .first-time-popup {
  658. display: flex;
  659. position: fixed;
  660. inset: 0;
  661. background: rgba(0, 0, 0, 0.7);
  662. justify-content: center;
  663. align-items: center;
  664. z-index: 1000;
  665. opacity: 0;
  666. animation: fadeIn 0.4s ease-in-out forwards;
  667. }
  668. .first-time-popup-content {
  669. background: rgba(25, 25, 25, 0.95);
  670. border-radius: 18px;
  671. padding: 30px;
  672. width: 420px;
  673. max-width: 90%;
  674. box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
  675. text-align: center;
  676. color: #fff;
  677. transform: scale(0.85);
  678. animation: scaleUp 0.5s ease-out forwards;
  679. }
  680. .popup-header {
  681. font-size: 22px;
  682. font-weight: bold;
  683. color: #4da6ff;
  684. text-transform: uppercase;
  685. letter-spacing: 1px;
  686. margin-bottom: 5px;
  687. }
  688. .popup-version {
  689. font-size: 18px;
  690. font-weight: bold;
  691. color: #ffcc00;
  692. margin-bottom: 15px;
  693. }
  694. .popup-info {
  695. font-size: 15px;
  696. color: #ccc;
  697. margin-bottom: 20px;
  698. line-height: 1.6;
  699. padding: 10px;
  700. border-radius: 10px;
  701. background: rgba(255, 255, 255, 0.05);
  702. }
  703. .popup-info a {
  704. color: #4da6ff;
  705. text-decoration: none;
  706. font-weight: bold;
  707. transition: color 0.3s ease;
  708. }
  709. .popup-info a:hover {
  710. color: #80bfff;
  711. text-decoration: underline;
  712. }
  713. .popup-footer {
  714. font-size: 14px;
  715. color: #aaa;
  716. font-weight: bold;
  717. margin-top: 10px;
  718. transition: opacity 0.3s ease-out;
  719. }
  720. .popup-footer.hidden {
  721. opacity: 0;
  722. visibility: hidden;
  723. }
  724. .popup-note {
  725. font-size: 13px;
  726. font-weight: bold;
  727. color: #ff6666;
  728. margin-top: 8px;
  729. }
  730. .popup-logo {
  731. display: block;
  732. margin: 0 auto 15px;
  733. width: 80px; /* Adjust based on your preference */
  734. height: auto;
  735. border-radius: 10px; /* Optional: Adds rounded corners */
  736. }
  737.  
  738. .first-time-popup-close {
  739. position: absolute;
  740. top: 10px;
  741. right: 15px;
  742. font-size: 24px;
  743. font-weight: bold;
  744. cursor: pointer;
  745. color: #fff;
  746. opacity: 0.4;
  747. transition: opacity 0.3s ease;
  748. pointer-events: none;
  749. }
  750.  
  751. .first-time-popup-close.active {
  752. opacity: 1;
  753. pointer-events: auto;
  754. }
  755.  
  756. .first-time-popup-close:hover {
  757. color: #ff4d4d;
  758. }
  759.  
  760. .first-time-popup-close::after {
  761. content: "";
  762. display: block;
  763. width: 0%;
  764. height: 2px;
  765. background: #ff4d4d;
  766. transition: width 0.3s ease-out;
  767. }
  768.  
  769. .first-time-popup-close:hover::after {
  770. width: 100%;
  771. }
  772.  
  773. @keyframes fadeIn {
  774. from { opacity: 0; }
  775. to { opacity: 1; }
  776. }
  777. @keyframes fadeOut {
  778. from { opacity: 1; }
  779. to { opacity: 0; }
  780. }
  781. @keyframes scaleUp {
  782. 0% { transform: scale(0.85); }
  783. 60% { transform: scale(1.05); }
  784. 100% { transform: scale(1); }
  785. }
  786. @keyframes scaleDown {
  787. from { transform: scale(1); }
  788. to { transform: scale(0.85); }
  789. }
  790. `;
  791.  
  792. const style = document.createElement('style');
  793. style.type = 'text/css';
  794. style.innerHTML = css;
  795. document.head.appendChild(style);
  796.  
  797. const popupHTML = `
  798. <div class="first-time-popup">
  799. <div class="first-time-popup-content">
  800. <span class="first-time-popup-close">&times;</span>
  801. <img class="popup-logo" src="${window.Base64Images.logo}" alt="Rolocate Logo">
  802. <div class="popup-header"><b>Rolocate Update</b></div>
  803. <div class="popup-version"><b>Version: ${VERSION}</b></div>
  804. <div class="popup-info">
  805. <p>Fixed script to new html changes by Roblox website, plus a new feature: "Remove All Roblox Ads" Disabled by default. Its still experimental and may not block all ads. Check out the <a href="https://oqarshi.github.io/Invite/rolocate/index.html" target="_blank">FAQ page</a>! This won't appear again until the next update.</p>
  806. <div class="popup-footer">Closing enabled in <span id="countdown-timer"><strong>5</strong></span> seconds...</div>
  807. </div>
  808. </div>
  809. `;
  810.  
  811. const popupContainer = document.createElement('div');
  812. popupContainer.innerHTML = popupHTML;
  813. document.body.appendChild(popupContainer);
  814.  
  815. const closeButton = document.querySelector('.first-time-popup-close');
  816. const popup = document.querySelector('.first-time-popup');
  817. const countdownTimer = document.getElementById('countdown-timer');
  818. const footer = document.querySelector('.popup-footer');
  819.  
  820. let countdown = 5;
  821. const countdownInterval = setInterval(() => {
  822. countdown--;
  823. countdownTimer.innerHTML = `<strong>${countdown}</strong>`;
  824.  
  825. if (countdown <= 0) {
  826. clearInterval(countdownInterval);
  827. closeButton.classList.add('active');
  828. footer.classList.add('hidden'); // Hides the countdown text
  829. }
  830. }, 1000);
  831.  
  832. closeButton.addEventListener('click', () => {
  833. popup.style.animation = 'fadeOut 0.4s ease-in-out forwards';
  834. document.querySelector('.first-time-popup-content').style.animation = 'scaleDown 0.4s ease-in-out forwards';
  835. setTimeout(() => {
  836. popup.remove();
  837. }, 400);
  838. });
  839. }
  840.  
  841. function removeAds() {
  842. if (localStorage.getItem("ROLOCATE_removeads") !== "true") {
  843. return;
  844. }
  845.  
  846. const iframeSelector = `.ads-container iframe,.abp iframe,.abp-spacer iframe,.abp-container iframe,.top-abp-container iframe,
  847. #AdvertisingLeaderboard iframe,#AdvertisementRight iframe,#MessagesAdSkyscraper iframe,.Ads_WideSkyscraper iframe,
  848. .profile-ads-container iframe, #ad iframe, iframe[src*="roblox.com/user-sponsorship/"]`;
  849.  
  850. const iframes = document.getElementsByTagName("iframe");
  851. const scripts = document.getElementsByTagName("script");
  852. const doneMap = new WeakMap();
  853.  
  854. function removeElements() {
  855. // Remove Iframes
  856. for (let i = iframes.length; i--;) {
  857. const iframe = iframes[i];
  858. if (!doneMap.get(iframe) && iframe.matches(iframeSelector)) {
  859. iframe.remove();
  860. doneMap.set(iframe, true);
  861. }
  862. }
  863.  
  864. // Remove Scripts
  865. for (let i = scripts.length; i--;) {
  866. const script = scripts[i];
  867. if (doneMap.get(script)) {
  868. continue;
  869. }
  870. doneMap.set(script, true);
  871.  
  872. if (script.src && (
  873. script.src.includes("imasdk.googleapis.com") ||
  874. script.src.includes("googletagmanager.com") ||
  875. script.src.includes("radar.cedexis.com") ||
  876. script.src.includes("ns1p.net")
  877. )) {
  878. script.remove();
  879. } else {
  880. const cont = script.textContent;
  881. if (!cont.includes("ContentJS") && (
  882. cont.includes("scorecardresearch.com") ||
  883. cont.includes("cedexis.com") ||
  884. cont.includes("pingdom.net") ||
  885. cont.includes("ns1p.net") ||
  886. cont.includes("Roblox.Hashcash") ||
  887. cont.includes("Roblox.VideoPreRollDFP") ||
  888. cont.includes("Roblox.AdsHelper=") ||
  889. cont.includes("googletag.enableServices()") ||
  890. cont.includes("gtag('config'")
  891. )) {
  892. script.remove();
  893. } else if (cont.includes("Roblox.EventStream.Init")) {
  894. script.textContent = cont.replace(/"[^"]*"/g, "\"\"");
  895. }
  896. }
  897. }
  898.  
  899. // Hide Sponsored Game Cards
  900. document.querySelectorAll(".game-card-native-ad").forEach(ad => {
  901. const gameCard = ad.closest(".game-card-container");
  902. if (gameCard) {
  903. gameCard.style.display = "none";
  904. }
  905. });
  906. }
  907.  
  908. // Observe DOM for dynamically added elements
  909. new MutationObserver(removeElements).observe(document.body, {
  910. childList: true,
  911. subtree: true
  912. });
  913.  
  914. removeElements(); // Initial run
  915. }
  916.  
  917.  
  918.  
  919. function ConsoleLogEnabled(...args) {
  920. if (localStorage.getItem("ROLOCATE_enableLogs") === "true") {
  921. console.log(...args);
  922. }
  923. }
  924.  
  925.  
  926.  
  927. // Load all required stuff hehehe
  928. window.addEventListener("load", () => {
  929. loadBase64Library(() => {
  930. ConsoleLogEnabled("Loaded Base64Images. It is ready to use!");
  931. });
  932.  
  933. AddSettingsButton(() => {
  934. ConsoleLogEnabled("Loaded Settings button!");
  935. });
  936.  
  937. Update_Popup();
  938. initializeLocalStorage();
  939. removeAds();
  940. });
  941.  
  942.  
  943.  
  944. function loadBase64Library(callback, timeout = 5000) {
  945. let elapsed = 0;
  946. (function waitForLibrary() {
  947. if (typeof window.Base64Images === "undefined") {
  948. if (elapsed < timeout) {
  949. elapsed += 50;
  950. setTimeout(waitForLibrary, 50);
  951. } else {
  952. ConsoleLogEnabled("Base64Images did not load within the timeout.");
  953. notifications('An error occured! No icons will show. Please refresh the page.', 'error', '⚠️', '8000')
  954. }
  955. } else {
  956. if (callback) callback();
  957. }
  958. })();
  959. }
  960.  
  961.  
  962. /*******************************************************
  963. The code for the random hop button and the filter button on roblox.com/games/*
  964. *******************************************************/
  965.  
  966. if (window.location.href.startsWith("https://www.roblox.com/games/") && localStorage.getItem("ROLOCATE_togglefilterserversbutton") === "true") {
  967. let Isongamespage = false; // Initially false
  968. /*********************************************************************************************************************************************************************************************************************************************
  969. 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
  970.  
  971. *********************************************************************************************************************************************************************************************************************************************/
  972. /*******************************************************
  973. name of function: createPopup
  974. description: Creates a popup with server filtering options and interactive buttons.
  975. *******************************************************/
  976. function createPopup() {
  977. const popup = document.createElement('div');
  978. popup.className = 'server-filters-dropdown-box'; // Unique class name
  979. popup.style.cssText = `
  980. position: absolute;
  981. width: 210px;
  982. height: 382px;
  983. right: 0px;
  984. top: 30px;
  985. z-index: 1000;
  986. border-radius: 5px;
  987. background-color: rgb(30, 32, 34);
  988. display: flex;
  989. flex-direction: column;
  990. padding: 5px;
  991. `;
  992.  
  993. // Create the header section
  994. const header = document.createElement('div');
  995. header.style.cssText = `
  996. display: flex;
  997. align-items: center;
  998. padding: 10px;
  999. border-bottom: 1px solid #444;
  1000. margin-bottom: 5px;
  1001. `;
  1002.  
  1003. // Add the logo (base64 image)
  1004. const logo = document.createElement('img');
  1005. logo.src = window.Base64Images.logo;
  1006. logo.style.cssText = `
  1007. width: 24px;
  1008. height: 24px;
  1009. margin-right: 10px;
  1010. `;
  1011.  
  1012. // Add the title
  1013. const title = document.createElement('span');
  1014. title.textContent = 'RoLocate';
  1015. title.style.cssText = `
  1016. color: white;
  1017. font-size: 18px;
  1018. font-weight: bold;
  1019. `;
  1020.  
  1021. // Append logo and title to the header
  1022. header.appendChild(logo);
  1023. header.appendChild(title);
  1024.  
  1025. // Append the header to the popup
  1026. popup.appendChild(header);
  1027.  
  1028. // Define unique names, tooltips, experimental status, and explanations for each button
  1029. const buttonData = [{
  1030. name: "Smallest Servers",
  1031. tooltip: "**Reverses the order of the server list.** The emptiest servers will be displayed first.",
  1032. experimental: false
  1033. },
  1034. {
  1035. name: "Available Space",
  1036. tooltip: "**Filters out servers which are full.** Servers with space will only be shown.",
  1037. experimental: false
  1038. },
  1039. {
  1040. name: "Player Count",
  1041. 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.",
  1042. experimental: false
  1043. },
  1044. {
  1045. name: "Random Shuffle",
  1046. tooltip: "**Display servers in a completely random order.** Shows servers with space and servers with low player counts in a randomized order.",
  1047. experimental: false
  1048. },
  1049. {
  1050. name: "Server Region",
  1051. 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.",
  1052. experimental: true,
  1053. experimentalExplanation: "**Experimental**: Still in development and testing. Ping may be inaccurate sometimes because of the Roblox API."
  1054. },
  1055. {
  1056. name: "Best Connection",
  1057. 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.",
  1058. experimental: true,
  1059. experimentalExplanation: "**Experimental**: Still in development and testing. it may be less accurate in regions with fewer Roblox servers"
  1060. },
  1061. {
  1062. name: "Join Small Server",
  1063. 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.",
  1064. experimental: false
  1065. },
  1066. {
  1067. name: "Locate Player",
  1068. 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.",
  1069. experimental: true,
  1070. experimentalExplanation: "**Experimental**: Still in development and testing. It may not be accurate with popular avatars, such as the default Roblox avatars."
  1071. }
  1072. ];
  1073.  
  1074. // Create buttons with unique names, tooltips, experimental status, and explanations
  1075. buttonData.forEach((data, index) => {
  1076. const buttonContainer = document.createElement('div');
  1077. buttonContainer.className = 'server-filter-option';
  1078. buttonContainer.style.cssText = `
  1079. width: 190px;
  1080. height: 30px;
  1081. background-color: #393B3D;
  1082. margin: 5px;
  1083. border-radius: 5px;
  1084. padding: 3.5px;
  1085. position: relative;
  1086. cursor: pointer;
  1087. display: flex;
  1088. align-items: center;
  1089. justify-content: center;
  1090. transition: background-color 0.3s ease;
  1091. `;
  1092.  
  1093. const tooltip = document.createElement('div');
  1094. tooltip.className = 'filter-tooltip';
  1095. tooltip.style.cssText = `
  1096. display: none;
  1097. position: absolute;
  1098. top: -10px;
  1099. left: 200px;
  1100. width: auto;
  1101. inline-size: 200px;
  1102. height: auto;
  1103. background-color: #191B1D;
  1104. color: white;
  1105. padding: 5px;
  1106. border-radius: 5px;
  1107. white-space: pre-wrap;
  1108. font-size: 14px;
  1109. `;
  1110.  
  1111. // Parse tooltip text and replace **...** with bold HTML tags
  1112. tooltip.innerHTML = data.tooltip.replace(/\*\*(.*?)\*\*/g, "<b style='color: #068f00;'>$1</b>");
  1113.  
  1114. const buttonText = document.createElement('p');
  1115. buttonText.style.cssText = `
  1116. margin: 0;
  1117. color: white;
  1118. font-size: 16px;
  1119. `;
  1120. buttonText.textContent = data.name;
  1121.  
  1122. // Add "EXP" label if the button is experimental
  1123. if (data.experimental) {
  1124. const expLabel = document.createElement('span');
  1125. expLabel.textContent = 'EXP';
  1126. expLabel.style.cssText = `
  1127. margin-left: 8px;
  1128. color: gold;
  1129. font-size: 12px;
  1130. font-weight: bold;
  1131. background-color: rgba(255, 215, 0, 0.1);
  1132. padding: 2px 6px;
  1133. border-radius: 3px;
  1134. `;
  1135. buttonText.appendChild(expLabel);
  1136. }
  1137.  
  1138. // Add experimental explanation tooltip (left side)
  1139. let experimentalTooltip = null;
  1140. if (data.experimental) {
  1141. experimentalTooltip = document.createElement('div');
  1142. experimentalTooltip.className = 'experimental-tooltip';
  1143. experimentalTooltip.style.cssText = `
  1144. display: none;
  1145. position: absolute;
  1146. top: 0;
  1147. right: 200px;
  1148. width: 200px;
  1149. background-color: #191B1D;
  1150. color: white;
  1151. padding: 5px;
  1152. border-radius: 5px;
  1153. font-size: 14px;
  1154. white-space: pre-wrap;
  1155. z-index: 1001;
  1156. `;
  1157.  
  1158. // Function to replace **text** with bold and gold styled text
  1159. const formatText = (text) => {
  1160. return text.replace(/\*\*(.*?)\*\*/g, '<span style="font-weight: bold; color: gold;">$1</span>');
  1161. };
  1162.  
  1163. // Apply the formatting to the experimental explanation
  1164. experimentalTooltip.innerHTML = formatText(data.experimentalExplanation);
  1165.  
  1166. buttonContainer.appendChild(experimentalTooltip);
  1167. }
  1168. buttonContainer.appendChild(tooltip);
  1169. buttonContainer.appendChild(buttonText);
  1170.  
  1171. buttonContainer.addEventListener('mouseover', () => {
  1172. tooltip.style.display = 'block';
  1173. if (data.experimental) {
  1174. experimentalTooltip.style.display = 'block';
  1175. }
  1176. buttonContainer.style.backgroundColor = '#4A4C4E'; // Hover effect
  1177. });
  1178. buttonContainer.addEventListener('mouseout', () => {
  1179. tooltip.style.display = 'none';
  1180. if (data.experimental) {
  1181. experimentalTooltip.style.display = 'none';
  1182. }
  1183. buttonContainer.style.backgroundColor = '#393B3D'; // Revert to original color
  1184. });
  1185.  
  1186. buttonContainer.addEventListener('click', () => {
  1187. switch (index) {
  1188. case 0:
  1189. smallest_servers();
  1190. break;
  1191. case 1:
  1192. available_space_servers();
  1193. break;
  1194. case 2:
  1195. player_count_tab();
  1196. break;
  1197. case 3:
  1198. random_servers();
  1199. break;
  1200. case 4:
  1201. createServerCountPopup((totalLimit) => {
  1202. rebuildServerList(gameId, totalLimit);
  1203. });
  1204. break;
  1205. case 5:
  1206. rebuildServerList(gameId, 50, true);
  1207. break;
  1208. case 6:
  1209. auto_join_small_server();
  1210. break;
  1211. case 7:
  1212. find_user_server_tab();
  1213. break;
  1214. }
  1215. });
  1216.  
  1217. popup.appendChild(buttonContainer);
  1218. });
  1219.  
  1220. return popup;
  1221. }
  1222.  
  1223.  
  1224. /*******************************************************
  1225. name of function: ServerHop
  1226. description: Handles server hopping by fetching and joining a random server, excluding recently joined servers.
  1227. *******************************************************/
  1228. // Main function to handle the server hopping
  1229. function ServerHop() {
  1230. ConsoleLogEnabled("Starting server hop...");
  1231. showLoadingOverlay();
  1232.  
  1233. // Extract the game ID from the URL
  1234. const url = window.location.href;
  1235. const gameId = url.split("/")[4]; // Extracts the game ID, assuming URL is in the format: /games/{gameId}/Title
  1236.  
  1237. ConsoleLogEnabled(`Game ID: ${gameId}`);
  1238.  
  1239. // Array to store server IDs
  1240. let serverIds = [];
  1241. let nextPageCursor = null;
  1242. let pagesRequested = 0;
  1243.  
  1244. // Get the list of all recently joined servers in localStorage
  1245. const allStoredServers = Object.keys(localStorage)
  1246. .filter(key => key.startsWith("recentServers_"))
  1247. .map(key => JSON.parse(localStorage.getItem(key)));
  1248.  
  1249. // Remove any expired servers for all games (older than 15 minutes)
  1250. const currentTime = new Date().getTime();
  1251. allStoredServers.forEach(storedServers => {
  1252. const validServers = storedServers.filter(server => {
  1253. const lastJoinedTime = new Date(server.timestamp).getTime();
  1254. return (currentTime - lastJoinedTime) <= 15 * 60 * 1000; // 15 minutes
  1255. });
  1256.  
  1257. // Update localStorage with the valid (non-expired) servers
  1258. localStorage.setItem(`recentServers_${gameId}`, JSON.stringify(validServers));
  1259. });
  1260.  
  1261. // Get the list of recently joined servers for the current game
  1262. const storedServers = JSON.parse(localStorage.getItem(`recentServers_${gameId}`)) || [];
  1263.  
  1264. // Check if there are any recently joined servers and exclude them from selection
  1265. const validServers = storedServers.filter(server => {
  1266. const lastJoinedTime = new Date(server.timestamp).getTime();
  1267. return (currentTime - lastJoinedTime) <= 15 * 60 * 1000; // 15 minutes
  1268. });
  1269.  
  1270. if (validServers.length > 0) {
  1271. ConsoleLogEnabled(`Excluding servers joined in the last 15 minutes: ${validServers.map(s => s.serverId).join(', ')}`);
  1272. } else {
  1273. ConsoleLogEnabled("No recently joined servers within the last 15 minutes. Proceeding to pick a new server.");
  1274. }
  1275.  
  1276. // Function to fetch servers
  1277. function fetchServers(cursor) {
  1278. const url = `https://games.roblox.com/v1/games/${gameId}/servers/0?sortOrder=2&excludeFullGames=true&limit=100${cursor ? `&cursor=${cursor}` : ""}`;
  1279.  
  1280. GM_xmlhttpRequest({
  1281. method: "GET",
  1282. url: url,
  1283. onload: function(response) {
  1284. ConsoleLogEnabled("API Response:", response.responseText);
  1285.  
  1286. try {
  1287. const data = JSON.parse(response.responseText);
  1288.  
  1289. // If there's an error, log it and return without processing
  1290. if (data.errors) {
  1291. ConsoleLogEnabled("Skipping unreadable response:", data.errors[0].message);
  1292. return;
  1293. }
  1294.  
  1295. // After a successful request, wait 0.15 seconds before proceeding
  1296. setTimeout(() => {
  1297. if (!data || !data.data) {
  1298. ConsoleLogEnabled("Invalid response structure: 'data' is missing or undefined", data);
  1299. return;
  1300. }
  1301.  
  1302. data.data.forEach(server => {
  1303. if (validServers.some(vs => vs.serverId === server.id)) {
  1304. ConsoleLogEnabled(`Skipping previously joined server ${server.id}.`);
  1305. } else {
  1306. serverIds.push(server.id);
  1307. }
  1308. });
  1309.  
  1310. // Fetch next page if available and within limit
  1311. if (data.nextPageCursor && pagesRequested < 4) {
  1312. pagesRequested++;
  1313. ConsoleLogEnabled(`Fetching page ${pagesRequested}...`);
  1314. fetchServers(data.nextPageCursor);
  1315. } else {
  1316. pickRandomServer();
  1317. }
  1318. }, 150);
  1319.  
  1320. } catch (error) {
  1321. ConsoleLogEnabled("Error parsing response:", error);
  1322. }
  1323. },
  1324. onerror: function(error) {
  1325. ConsoleLogEnabled("Error fetching server data:", error);
  1326. }
  1327. });
  1328. }
  1329.  
  1330. // Function to pick a random server and join it
  1331. function pickRandomServer() {
  1332. if (serverIds.length > 0) {
  1333. const randomServerId = serverIds[Math.floor(Math.random() * serverIds.length)];
  1334. ConsoleLogEnabled(`Joining server: ${randomServerId}`);
  1335.  
  1336. // Join the game instance with the selected server ID
  1337. Roblox.GameLauncher.joinGameInstance(gameId, randomServerId);
  1338.  
  1339. // Store the selected server ID with the time and date in localStorage
  1340. const timestamp = new Date().toISOString();
  1341. const newServer = {
  1342. serverId: randomServerId,
  1343. timestamp
  1344. };
  1345. validServers.push(newServer);
  1346.  
  1347. // Save the updated list of recently joined servers to localStorage
  1348. localStorage.setItem(`recentServers_${gameId}`, JSON.stringify(validServers));
  1349.  
  1350. ConsoleLogEnabled(`Server ${randomServerId} stored with timestamp ${timestamp}`);
  1351. } else {
  1352. ConsoleLogEnabled("No servers found to join.");
  1353. notifications("You have joined all the servers recently. No servers found to join.", "error", "⚠️", "5000");
  1354. }
  1355. }
  1356.  
  1357. // Start the fetching process
  1358. fetchServers();
  1359. }
  1360.  
  1361.  
  1362. if (window.location.href.startsWith("https://www.roblox.com/games/")) {
  1363.  
  1364. window.addEventListener("load", () => {
  1365. // Extract game ID from URL
  1366. function findGameId() {
  1367. const match = window.location.href.match(/games\/(\d+)/);
  1368. return match ? match[1] : null;
  1369. }
  1370.  
  1371. // Auto-click "Servers" tab if enabled in localStorage
  1372. if (localStorage.ROLOCATE_AutoRunServerRegions === "true") {
  1373. setTimeout(() => {
  1374. const serversTab = document.querySelector("#tab-game-instances a");
  1375. if (serversTab) {
  1376. serversTab.click();
  1377. }
  1378. }, 1000);
  1379. }
  1380.  
  1381. // Auto-run server regions if enabled in localStorage
  1382. if (localStorage.ROLOCATE_AutoRunServerRegions === "true") {
  1383. setTimeout(() => {
  1384. const gameId = findGameId();
  1385. if (gameId) {
  1386. Loadingbar(true);
  1387. disableFilterButton(true);
  1388. disableLoadMoreButton();
  1389. rebuildServerList(gameId, 16);
  1390. }
  1391. }, 2000);
  1392. }
  1393. });
  1394.  
  1395.  
  1396.  
  1397. Isongamespage = true;
  1398.  
  1399. const observer = new MutationObserver((mutations, obs) => {
  1400. const serverListOptions = document.querySelector('.server-list-options');
  1401. const playButton = document.querySelector('.btn-common-play-game-lg.btn-primary-md');
  1402.  
  1403. if (serverListOptions && !document.querySelector('.RL-filter-button')) {
  1404. const filterButton = document.createElement('a');
  1405. filterButton.className = 'RL-filter-button';
  1406. filterButton.style.cssText = `
  1407. color: white;
  1408. font-weight: bold;
  1409. text-decoration: none;
  1410. cursor: pointer;
  1411. margin-left: 10px;
  1412. padding: 5px 10px;
  1413. display: flex;
  1414. align-items: center;
  1415. gap: 5px;
  1416. position: relative;
  1417. margin-top: 4px;
  1418. `;
  1419.  
  1420. filterButton.addEventListener('mouseover', () => {
  1421. filterButton.style.textDecoration = 'underline';
  1422. });
  1423. filterButton.addEventListener('mouseout', () => {
  1424. filterButton.style.textDecoration = 'none';
  1425. });
  1426.  
  1427. const buttonText = document.createElement('span');
  1428. buttonText.className = 'RL-filter-text';
  1429. buttonText.textContent = 'Filters';
  1430. filterButton.appendChild(buttonText);
  1431.  
  1432. const icon = document.createElement('span');
  1433. icon.className = 'RL-filter-icon';
  1434. icon.textContent = '≡';
  1435. icon.style.cssText = `font-size: 18px;`;
  1436. filterButton.appendChild(icon);
  1437.  
  1438. serverListOptions.appendChild(filterButton);
  1439.  
  1440. let popup = null;
  1441. filterButton.addEventListener('click', (event) => {
  1442. event.stopPropagation();
  1443. if (popup) {
  1444. popup.remove();
  1445. popup = null;
  1446. } else {
  1447. popup = createPopup();
  1448. popup.style.top = `${filterButton.offsetHeight}px`;
  1449. popup.style.left = '0';
  1450. filterButton.appendChild(popup);
  1451. }
  1452. });
  1453.  
  1454. document.addEventListener('click', (event) => {
  1455. if (popup && !filterButton.contains(event.target)) {
  1456. popup.remove();
  1457. popup = null;
  1458. }
  1459. });
  1460. }
  1461.  
  1462. if (playButton && !document.querySelector('.custom-play-button')) {
  1463. const buttonContainer = document.createElement('div');
  1464. buttonContainer.style.cssText = `
  1465. display: flex;
  1466. gap: 10px;
  1467. align-items: center;
  1468. width: 100%;
  1469. `;
  1470.  
  1471. playButton.style.cssText += `
  1472. flex: 3;
  1473. padding: 10px 12px;
  1474. text-align: center;
  1475. `;
  1476.  
  1477. const serverHopButton = document.createElement('button');
  1478. serverHopButton.className = 'custom-play-button';
  1479. serverHopButton.style.cssText = `
  1480. background-color: #335fff;
  1481. color: white;
  1482. border: none;
  1483. padding: 7.5px 12px;
  1484. cursor: pointer;
  1485. font-weight: bold;
  1486. border-radius: 8px;
  1487. flex: 1;
  1488. text-align: center;
  1489. display: flex;
  1490. align-items: center;
  1491. justify-content: center;
  1492. position: relative;
  1493. `;
  1494.  
  1495. const tooltip = document.createElement('div');
  1496. tooltip.textContent = 'Join Random Server / Server Hop';
  1497. tooltip.style.cssText = `
  1498. position: absolute;
  1499. background-color: rgba(51, 95, 255, 0.8);
  1500. color: white;
  1501. padding: 5px 10px;
  1502. border-radius: 5px;
  1503. font-size: 12px;
  1504. visibility: hidden;
  1505. opacity: 0;
  1506. transition: opacity 0.2s ease-in-out;
  1507. bottom: 100%;
  1508. left: 50%;
  1509. transform: translateX(-50%);
  1510. white-space: nowrap;
  1511. `;
  1512. serverHopButton.appendChild(tooltip);
  1513.  
  1514. serverHopButton.addEventListener('mouseover', () => {
  1515. tooltip.style.visibility = 'visible';
  1516. tooltip.style.opacity = '1';
  1517. });
  1518.  
  1519. serverHopButton.addEventListener('mouseout', () => {
  1520. tooltip.style.visibility = 'hidden';
  1521. tooltip.style.opacity = '0';
  1522. });
  1523.  
  1524. const logo = document.createElement('img');
  1525. logo.src = window.Base64Images.icon_serverhop;
  1526. logo.style.cssText = `
  1527. width: 45px;
  1528. height: 45px;
  1529. `;
  1530. serverHopButton.appendChild(logo);
  1531.  
  1532. playButton.parentNode.insertBefore(buttonContainer, playButton);
  1533. buttonContainer.appendChild(playButton);
  1534. buttonContainer.appendChild(serverHopButton);
  1535.  
  1536. serverHopButton.addEventListener('click', () => {
  1537. ServerHop();
  1538. });
  1539. }
  1540.  
  1541. if (document.querySelector('.RL-filter-button') && document.querySelector('.custom-play-button')) {
  1542. obs.disconnect();
  1543. }
  1544. });
  1545.  
  1546. observer.observe(document.body, {
  1547. childList: true,
  1548. subtree: true
  1549. });
  1550. }
  1551.  
  1552.  
  1553.  
  1554.  
  1555. /*********************************************************************************************************************************************************************************************************************************************
  1556. 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
  1557.  
  1558. *********************************************************************************************************************************************************************************************************************************************/
  1559.  
  1560.  
  1561. /*********************************************************************************************************************************************************************************************************************************************
  1562. Functions for the 1st button
  1563.  
  1564. *********************************************************************************************************************************************************************************************************************************************/
  1565.  
  1566.  
  1567. /*******************************************************
  1568. name of function: smallest_servers
  1569. description: Fetches the smallest servers, disables the "Load More" button, shows a loading bar, and recreates the server cards.
  1570. *******************************************************/
  1571. async function smallest_servers() {
  1572. // Disable the "Load More" button and show the loading bar
  1573. Loadingbar(true);
  1574. disableFilterButton(true);
  1575. disableLoadMoreButton();
  1576. notifications("Finding small servers...", "success", "🧐");
  1577.  
  1578. // Get the game ID from the URL
  1579. const gameId = window.location.pathname.split('/')[2];
  1580.  
  1581. // Retry mechanism
  1582. let retries = 3;
  1583. let success = false;
  1584.  
  1585. while (retries > 0 && !success) {
  1586. try {
  1587. // Use GM_xmlhttpRequest to fetch server data from the Roblox API
  1588. const response = await new Promise((resolve, reject) => {
  1589. GM_xmlhttpRequest({
  1590. method: "GET",
  1591. url: `https://games.roblox.com/v1/games/${gameId}/servers/0?sortOrder=1&excludeFullGames=true&limit=100`,
  1592. onload: function(response) {
  1593. if (response.status === 429) {
  1594. reject(new Error('429: Too Many Requests'));
  1595. } else if (response.status >= 200 && response.status < 300) {
  1596. resolve(response);
  1597. } else {
  1598. reject(new Error(`HTTP error! status: ${response.status}`));
  1599. }
  1600. },
  1601. onerror: function(error) {
  1602. reject(error);
  1603. }
  1604. });
  1605. });
  1606.  
  1607. const data = JSON.parse(response.responseText);
  1608.  
  1609. // Process each server
  1610. for (const server of data.data) {
  1611. const {
  1612. id: serverId,
  1613. playerTokens,
  1614. maxPlayers,
  1615. playing
  1616. } = server;
  1617.  
  1618. // Pass the server data to the card creation function
  1619. await rbx_card(serverId, playerTokens, maxPlayers, playing, gameId);
  1620. }
  1621.  
  1622. success = true; // Mark as successful if no errors occurred
  1623. } catch (error) {
  1624. retries--; // Decrement the retry count
  1625.  
  1626. if (error.message === '429: Too Many Requests' && retries > 0) {
  1627. ConsoleLogEnabled('Encountered a 429 error. Retrying in 5 seconds...');
  1628. await new Promise(resolve => setTimeout(resolve, 5000)); // Wait for 5 seconds
  1629. } else {
  1630. ConsoleLogEnabled('Error fetching server data:', error);
  1631. break; // Exit the loop if it's not a 429 error or no retries left
  1632. }
  1633. } finally {
  1634. if (success || retries === 0) {
  1635. // Hide the loading bar and enable the filter button
  1636. Loadingbar(false);
  1637. disableFilterButton(false);
  1638. }
  1639. }
  1640. }
  1641. }
  1642.  
  1643.  
  1644.  
  1645. /*********************************************************************************************************************************************************************************************************************************************
  1646. Functions for the 2nd button
  1647.  
  1648. *********************************************************************************************************************************************************************************************************************************************/
  1649.  
  1650.  
  1651. /*******************************************************
  1652. name of function: available_space_servers
  1653. description: Fetches servers with available space, disables the "Load More" button, shows a loading bar, and recreates the server cards.
  1654. *******************************************************/
  1655. async function available_space_servers() {
  1656. // Disable the "Load More" button and show the loading bar
  1657. Loadingbar(true);
  1658. disableLoadMoreButton();
  1659. disableFilterButton(true);
  1660. notifications("Finding servers with space...", "success", "🧐");
  1661.  
  1662. // Get the game ID from the URL
  1663. const gameId = window.location.pathname.split('/')[2];
  1664.  
  1665. // Retry mechanism
  1666. let retries = 3;
  1667. let success = false;
  1668.  
  1669. while (retries > 0 && !success) {
  1670. try {
  1671. // Use GM_xmlhttpRequest to fetch server data from the Roblox API
  1672. const response = await new Promise((resolve, reject) => {
  1673. GM_xmlhttpRequest({
  1674. method: "GET",
  1675. url: `https://games.roblox.com/v1/games/${gameId}/servers/0?sortOrder=2&excludeFullGames=true&limit=100`,
  1676. onload: function(response) {
  1677. if (response.status === 429) {
  1678. reject(new Error('429: Too Many Requests'));
  1679. } else if (response.status >= 200 && response.status < 300) {
  1680. resolve(response);
  1681. } else {
  1682. reject(new Error(`HTTP error! status: ${response.status}`));
  1683. }
  1684. },
  1685. onerror: function(error) {
  1686. reject(error);
  1687. }
  1688. });
  1689. });
  1690.  
  1691. const data = JSON.parse(response.responseText);
  1692.  
  1693. // Process each server
  1694. for (const server of data.data) {
  1695. const {
  1696. id: serverId,
  1697. playerTokens,
  1698. maxPlayers,
  1699. playing
  1700. } = server;
  1701.  
  1702. // Pass the server data to the card creation function
  1703. await rbx_card(serverId, playerTokens, maxPlayers, playing, gameId);
  1704. }
  1705.  
  1706. success = true; // Mark as successful if no errors occurred
  1707. } catch (error) {
  1708. retries--; // Decrement the retry count
  1709.  
  1710. if (error.message === '429: Too Many Requests' && retries > 0) {
  1711. ConsoleLogEnabled('Encountered a 429 error. Retrying in 10 seconds...');
  1712. await new Promise(resolve => setTimeout(resolve, 10000)); // Wait for 10 seconds
  1713. } else {
  1714. ConsoleLogEnabled('Error fetching server data:', error);
  1715. break; // Exit the loop if it's not a 429 error or no retries left
  1716. }
  1717. } finally {
  1718. if (success || retries === 0) {
  1719. // Hide the loading bar and enable the filter button
  1720. Loadingbar(false);
  1721. disableFilterButton(false);
  1722. }
  1723. }
  1724. }
  1725. }
  1726.  
  1727. /*********************************************************************************************************************************************************************************************************************************************
  1728. Functions for the 3rd button
  1729.  
  1730. *********************************************************************************************************************************************************************************************************************************************/
  1731.  
  1732.  
  1733. /*******************************************************
  1734. name of function: player_count_tab
  1735. description: Opens a popup for the user to select the max player count using a slider and filters servers accordingly.
  1736. *******************************************************/
  1737. function player_count_tab() {
  1738. // Check if the max player count has already been determined
  1739. if (!player_count_tab.maxPlayers) {
  1740. // Try to find the element containing the player count information
  1741. const playerCountElement = document.querySelector('.text-info.rbx-game-status.rbx-game-server-status.text-overflow');
  1742. if (playerCountElement) {
  1743. const playerCountText = playerCountElement.textContent.trim();
  1744. const match = playerCountText.match(/(\d+) of (\d+) people max/);
  1745. if (match) {
  1746. const maxPlayers = parseInt(match[2], 10);
  1747. if (!isNaN(maxPlayers) && maxPlayers > 1) {
  1748. player_count_tab.maxPlayers = maxPlayers;
  1749. ConsoleLogEnabled("Found text element with max playercount");
  1750. }
  1751. }
  1752. } else {
  1753. // If the element is not found, extract the gameId from the URL
  1754. const gameIdMatch = window.location.href.match(/games\/(\d+)/);
  1755. if (gameIdMatch && gameIdMatch[1]) {
  1756. const gameId = gameIdMatch[1];
  1757. // Send a request to the Roblox API to get server information
  1758. GM_xmlhttpRequest({
  1759. method: 'GET',
  1760. url: `https://games.roblox.com/v1/games/${gameId}/servers/public?sortOrder=1&excludeFullGames=true&limit=100`,
  1761. onload: function(response) {
  1762. try {
  1763. if (response.status === 429) {
  1764. // Rate limit error, default to 100
  1765. ConsoleLogEnabled("Rate limited defaulting to 100.");
  1766. player_count_tab.maxPlayers = 100;
  1767. } else {
  1768. ConsoleLogEnabled("Valid api response");
  1769. const data = JSON.parse(response.responseText);
  1770. if (data.data && data.data.length > 0) {
  1771. const maxPlayers = data.data[0].maxPlayers;
  1772. if (!isNaN(maxPlayers) && maxPlayers > 1) {
  1773. player_count_tab.maxPlayers = maxPlayers;
  1774. }
  1775. }
  1776. }
  1777. // Update the slider range if the popup is already created
  1778. const slider = document.querySelector('.player-count-popup input[type="range"]');
  1779. if (slider) {
  1780. slider.max = player_count_tab.maxPlayers ? (player_count_tab.maxPlayers - 1).toString() : '100';
  1781. slider.style.background = `
  1782. linear-gradient(
  1783. to right,
  1784. #00A2FF 0%,
  1785. #00A2FF ${slider.value}%,
  1786. #444 ${slider.value}%,
  1787. #444 100%
  1788. );
  1789. `;
  1790. }
  1791. } catch (error) {
  1792. ConsoleLogEnabled('Failed to parse API response:', error);
  1793. // Default to 100 if parsing fails
  1794. player_count_tab.maxPlayers = 100;
  1795. const slider = document.querySelector('.player-count-popup input[type="range"]');
  1796. if (slider) {
  1797. slider.max = '100';
  1798. slider.style.background = `
  1799. linear-gradient(
  1800. to right,
  1801. #00A2FF 0%,
  1802. #00A2FF ${slider.value}%,
  1803. #444 ${slider.value}%,
  1804. #444 100%
  1805. );
  1806. `;
  1807. }
  1808. }
  1809. },
  1810. onerror: function(error) {
  1811. ConsoleLogEnabled('Failed to fetch server information:', error);
  1812. ConsoleLogEnabled('Fallback to 100 players.');
  1813. // Default to 100 if the request fails
  1814. player_count_tab.maxPlayers = 100;
  1815. const slider = document.querySelector('.player-count-popup input[type="range"]');
  1816. if (slider) {
  1817. slider.max = '100';
  1818. slider.style.background = `
  1819. linear-gradient(
  1820. to right,
  1821. #00A2FF 0%,
  1822. #00A2FF ${slider.value}%,
  1823. #444 ${slider.value}%,
  1824. #444 100%
  1825. );
  1826. `;
  1827. }
  1828. }
  1829. });
  1830. }
  1831. }
  1832. }
  1833. // Create the overlay (backdrop)
  1834. const overlay = document.createElement('div');
  1835. overlay.style.cssText = `
  1836. position: fixed;
  1837. top: 0;
  1838. left: 0;
  1839. width: 100%;
  1840. height: 100%;
  1841. background-color: rgba(0, 0, 0, 0.5);
  1842. z-index: 9999;
  1843. opacity: 0;
  1844. transition: opacity 0.3s ease;
  1845. `;
  1846. document.body.appendChild(overlay);
  1847.  
  1848. // Create the popup container
  1849. const popup = document.createElement('div');
  1850. popup.className = 'player-count-popup';
  1851. popup.style.cssText = `
  1852. position: fixed;
  1853. top: 50%;
  1854. left: 50%;
  1855. transform: translate(-50%, -50%);
  1856. background-color: rgb(30, 32, 34);
  1857. padding: 20px;
  1858. border-radius: 10px;
  1859. z-index: 10000;
  1860. box-shadow: 0 0 15px rgba(0, 0, 0, 0.7);
  1861. display: flex;
  1862. flex-direction: column;
  1863. align-items: center;
  1864. gap: 15px;
  1865. width: 300px;
  1866. opacity: 0;
  1867. transition: opacity 0.3s ease, transform 0.3s ease;
  1868. `;
  1869.  
  1870. // Add a close button in the top-right corner (bigger size)
  1871. const closeButton = document.createElement('button');
  1872. closeButton.innerHTML = '&times;'; // Using '×' for the close icon
  1873. closeButton.style.cssText = `
  1874. position: absolute;
  1875. top: 10px;
  1876. right: 10px;
  1877. background: transparent;
  1878. border: none;
  1879. color: #ffffff;
  1880. font-size: 24px; /* Increased font size */
  1881. cursor: pointer;
  1882. width: 36px; /* Increased size */
  1883. height: 36px; /* Increased size */
  1884. border-radius: 50%;
  1885. display: flex;
  1886. align-items: center;
  1887. justify-content: center;
  1888. transition: background-color 0.3s ease, color 0.3s ease;
  1889. `;
  1890. closeButton.addEventListener('mouseenter', () => {
  1891. closeButton.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
  1892. closeButton.style.color = '#ff4444';
  1893. });
  1894. closeButton.addEventListener('mouseleave', () => {
  1895. closeButton.style.backgroundColor = 'transparent';
  1896. closeButton.style.color = '#ffffff';
  1897. });
  1898.  
  1899. // Add a title
  1900. const title = document.createElement('h3');
  1901. title.textContent = 'Select Max Player Count';
  1902. title.style.cssText = `
  1903. color: white;
  1904. margin: 0;
  1905. font-size: 18px;
  1906. font-weight: 500;
  1907. `;
  1908. popup.appendChild(title);
  1909.  
  1910. // Add a slider with improved functionality and styling
  1911. const slider = document.createElement('input');
  1912. slider.type = 'range';
  1913. slider.min = '1';
  1914. slider.max = player_count_tab.maxPlayers ? (player_count_tab.maxPlayers - 1).toString() : '100';
  1915. slider.value = '1'; // Default value
  1916. slider.step = '1'; // Step for better accuracy
  1917. slider.style.cssText = `
  1918. width: 80%;
  1919. cursor: pointer;
  1920. margin: 10px 0;
  1921. -webkit-appearance: none; /* Remove default styling */
  1922. background: transparent;
  1923. `;
  1924. // Custom slider track
  1925. slider.style.background = `
  1926. linear-gradient(
  1927. to right,
  1928. #00A2FF 0%,
  1929. #00A2FF ${slider.value}%,
  1930. #444 ${slider.value}%,
  1931. #444 100%
  1932. );
  1933. border-radius: 5px;
  1934. height: 6px;
  1935. `;
  1936. // Custom slider thumb
  1937. slider.style.setProperty('--thumb-size', '20px'); /* Larger thumb */
  1938. slider.style.setProperty('--thumb-color', '#00A2FF');
  1939. slider.style.setProperty('--thumb-hover-color', '#0088cc');
  1940. slider.style.setProperty('--thumb-border', '2px solid #fff');
  1941. slider.style.setProperty('--thumb-shadow', '0 0 5px rgba(0, 0, 0, 0.5)');
  1942. slider.addEventListener('input', () => {
  1943. slider.style.background = `
  1944. linear-gradient(
  1945. to right,
  1946. #00A2FF 0%,
  1947. #00A2FF ${slider.value}%,
  1948. #444 ${slider.value}%,
  1949. #444 100%
  1950. );
  1951. `;
  1952. sliderValue.textContent = slider.value; // Update the displayed value
  1953. });
  1954. // Keyboard support for better accuracy (fixed to increment/decrement by 1)
  1955. slider.addEventListener('keydown', (e) => {
  1956. e.preventDefault(); // Prevent default behavior (which might cause jumps)
  1957. let newValue = parseInt(slider.value, 10);
  1958. if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {
  1959. newValue = Math.max(1, newValue - 1); // Decrease by 1
  1960. } else if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {
  1961. newValue = Math.min(100, newValue + 1); // Increase by 1
  1962. }
  1963. slider.value = newValue;
  1964. slider.dispatchEvent(new Event('input')); // Trigger input event to update UI
  1965. });
  1966. popup.appendChild(slider);
  1967.  
  1968. // Add a display for the slider value
  1969. const sliderValue = document.createElement('span');
  1970. sliderValue.textContent = slider.value;
  1971. sliderValue.style.cssText = `
  1972. color: white;
  1973. font-size: 16px;
  1974. font-weight: bold;
  1975. `;
  1976. popup.appendChild(sliderValue);
  1977.  
  1978. // Add a submit button with dark, blackish style
  1979. const submitButton = document.createElement('button');
  1980. submitButton.textContent = 'Search';
  1981. submitButton.style.cssText = `
  1982. padding: 8px 20px;
  1983. font-size: 16px;
  1984. background-color: #1a1a1a; /* Dark blackish color */
  1985. color: white;
  1986. border: none;
  1987. border-radius: 5px;
  1988. cursor: pointer;
  1989. transition: background-color 0.3s ease, transform 0.2s ease;
  1990. `;
  1991. submitButton.addEventListener('mouseenter', () => {
  1992. submitButton.style.backgroundColor = '#333'; /* Slightly lighter on hover */
  1993. submitButton.style.transform = 'scale(1.05)';
  1994. });
  1995. submitButton.addEventListener('mouseleave', () => {
  1996. submitButton.style.backgroundColor = '#1a1a1a';
  1997. submitButton.style.transform = 'scale(1)';
  1998. });
  1999.  
  2000. // Add a yellow box with a tip under the submit button
  2001. const tipBox = document.createElement('div');
  2002. tipBox.style.cssText = `
  2003. width: 100%;
  2004. padding: 10px;
  2005. background-color: rgba(255, 204, 0, 0.15);
  2006. border-radius: 5px;
  2007. text-align: center;
  2008. font-size: 14px;
  2009. color: #ffcc00;
  2010. transition: background-color 0.3s ease;
  2011. `;
  2012. tipBox.textContent = 'Tip: Click the slider and use the arrow keys for more accuracy.';
  2013. tipBox.addEventListener('mouseenter', () => {
  2014. tipBox.style.backgroundColor = 'rgba(255, 204, 0, 0.25)';
  2015. });
  2016. tipBox.addEventListener('mouseleave', () => {
  2017. tipBox.style.backgroundColor = 'rgba(255, 204, 0, 0.15)';
  2018. });
  2019. popup.appendChild(tipBox);
  2020.  
  2021. // Append the popup to the body
  2022. document.body.appendChild(popup);
  2023.  
  2024. // Fade in the overlay and popup
  2025. setTimeout(() => {
  2026. overlay.style.opacity = '1';
  2027. popup.style.opacity = '1';
  2028. popup.style.transform = 'translate(-50%, -50%) scale(1)';
  2029. }, 10);
  2030.  
  2031. /*******************************************************
  2032. name of function: fadeOutAndRemove
  2033. description: Fades out and removes the popup and overlay.
  2034. *******************************************************/
  2035. function fadeOutAndRemove(popup, overlay) {
  2036. popup.style.opacity = '0';
  2037. popup.style.transform = 'translate(-50%, -50%) scale(0.9)';
  2038. overlay.style.opacity = '0';
  2039. setTimeout(() => {
  2040. popup.remove();
  2041. overlay.remove();
  2042. }, 300); // Match the duration of the transition
  2043. }
  2044.  
  2045. // Close the popup when clicking outside
  2046. overlay.addEventListener('click', () => {
  2047. fadeOutAndRemove(popup, overlay);
  2048. });
  2049.  
  2050. // Close the popup when the close button is clicked
  2051. closeButton.addEventListener('click', () => {
  2052. fadeOutAndRemove(popup, overlay);
  2053. });
  2054.  
  2055. // Handle submit button click
  2056. submitButton.addEventListener('click', () => {
  2057. const maxPlayers = parseInt(slider.value, 10);
  2058. if (!isNaN(maxPlayers) && maxPlayers > 0) {
  2059. filterServersByPlayerCount(maxPlayers);
  2060. fadeOutAndRemove(popup, overlay);
  2061. } else {
  2062. notifications('Error: Please enter a number greater than 0', 'error', '⚠️', '5000');
  2063. }
  2064. });
  2065.  
  2066. popup.appendChild(submitButton);
  2067. popup.appendChild(closeButton);
  2068. }
  2069. /*******************************************************
  2070. name of function: fetchServersWithRetry
  2071. description: Fetches server data with retry logic and a delay between requests to avoid rate-limiting.
  2072. Uses GM_xmlhttpRequest instead of fetch.
  2073. *******************************************************/
  2074. async function fetchServersWithRetry(url, retries = 15, currentDelay = 750) {
  2075. return new Promise((resolve, reject) => {
  2076. GM_xmlhttpRequest({
  2077. method: 'GET',
  2078. url: url,
  2079. onload: function(response) {
  2080. // Check for 429 Rate Limit error
  2081. if (response.status === 429) {
  2082. if (retries > 0) {
  2083. const newDelay = currentDelay * 1; // Exponential backoff
  2084. ConsoleLogEnabled(`[DEBUG] Rate limited. Waiting ${newDelay / 1000} seconds before retrying...`);
  2085. setTimeout(() => {
  2086. resolve(fetchServersWithRetry(url, retries - 1, newDelay)); // Retry with increased delay
  2087. }, newDelay);
  2088. } else {
  2089. ConsoleLogEnabled('[DEBUG] Rate limit retries exhausted.');
  2090. notifications('Error: Rate limited please try again later.', 'error', '⚠️', '5000')
  2091. reject(new Error('RateLimit'));
  2092. }
  2093. return;
  2094. }
  2095.  
  2096. // Handle other HTTP errors
  2097. if (response.status < 200 || response.status >= 300) {
  2098. ConsoleLogEnabled('[DEBUG] HTTP error:', response.status, response.statusText);
  2099. reject(new Error(`HTTP error: ${response.status}`));
  2100. return;
  2101. }
  2102.  
  2103. // Parse and return the JSON data
  2104. try {
  2105. const data = JSON.parse(response.responseText);
  2106. ConsoleLogEnabled('[DEBUG] Fetched data successfully:', data);
  2107. resolve(data);
  2108. } catch (error) {
  2109. ConsoleLogEnabled('[DEBUG] Error parsing JSON:', error);
  2110. reject(error);
  2111. }
  2112. },
  2113. onerror: function(error) {
  2114. ConsoleLogEnabled('[DEBUG] Error in GM_xmlhttpRequest:', error);
  2115. reject(error);
  2116. }
  2117. });
  2118. });
  2119. }
  2120.  
  2121. /*******************************************************
  2122. name of function: filterServersByPlayerCount
  2123. description: Filters servers to show only those with a player count equal to or below the specified max.
  2124. If no exact matches are found, prioritizes servers with player counts lower than the input.
  2125. Keeps fetching until at least 8 servers are found, with a dynamic delay between requests.
  2126. *******************************************************/
  2127. async function filterServersByPlayerCount(maxPlayers) {
  2128. // Validate maxPlayers before proceeding
  2129. if (isNaN(maxPlayers) || maxPlayers < 1 || !Number.isInteger(maxPlayers)) {
  2130. ConsoleLogEnabled('[DEBUG] Invalid input for maxPlayers.');
  2131. notifications('Error: Please input a valid whole number greater than or equal to 1.', 'error', '⚠️', '5000');
  2132. return;
  2133. }
  2134.  
  2135. // Disable UI elements and clear the server list
  2136. Loadingbar(true);
  2137. disableLoadMoreButton();
  2138. disableFilterButton(true);
  2139. const serverList = document.querySelector('#rbx-public-game-server-item-container');
  2140. serverList.innerHTML = '';
  2141.  
  2142. const gameId = window.location.pathname.split('/')[2];
  2143. let cursor = null;
  2144. let serversFound = 0;
  2145. let serverMaxPlayers = null;
  2146. let isCloserToOne = null;
  2147. let topDownServers = []; // Servers collected during top-down search
  2148. let bottomUpServers = []; // Servers collected during bottom-up search
  2149. let currentDelay = 500; // Initial delay of 0.5 seconds
  2150. const timeLimit = 3 * 60 * 1000; // 3 minutes in milliseconds
  2151. const startTime = Date.now(); // Record the start time
  2152. notifications('Will search for a maximum of 3 minutes to find a server.', 'success', '🔎', '5000');
  2153.  
  2154.  
  2155. try {
  2156. while (serversFound < 16) {
  2157. // Check if the time limit has been exceeded
  2158. if (Date.now() - startTime > timeLimit) {
  2159. ConsoleLogEnabled('[DEBUG] Time limit reached. Proceeding to fallback servers.');
  2160. notifications('Warning: Time limit reached. Proceeding to fallback servers.', 'warning', '❗', '5000');
  2161. break;
  2162. }
  2163.  
  2164. // Fetch initial data to determine serverMaxPlayers and isCloserToOne
  2165. if (!serverMaxPlayers) {
  2166. const initialUrl = cursor ?
  2167. `https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=100&cursor=${cursor}` :
  2168. `https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=100`;
  2169.  
  2170. const initialData = await fetchServersWithRetry(initialUrl);
  2171. if (initialData.data.length > 0) {
  2172. serverMaxPlayers = initialData.data[0].maxPlayers;
  2173. isCloserToOne = maxPlayers <= (serverMaxPlayers / 2);
  2174. } else {
  2175. notifications("No servers found in initial fetch.", "error", "⚠️", "5000")
  2176. ConsoleLogEnabled('[DEBUG] No servers found in initial fetch.', 'warning', '❗');
  2177. break;
  2178. }
  2179. }
  2180.  
  2181. // Validate maxPlayers against serverMaxPlayers
  2182. if (maxPlayers >= serverMaxPlayers) {
  2183. ConsoleLogEnabled('[DEBUG] Invalid input: maxPlayers is greater than or equal to serverMaxPlayers.');
  2184. notifications(`Error: Please input a number between 1 through ${serverMaxPlayers - 1}`, 'error', '⚠️', '5000');
  2185. return;
  2186. }
  2187.  
  2188. // Adjust the URL based on isCloserToOne
  2189. const baseUrl = isCloserToOne ?
  2190. `https://games.roblox.com/v1/games/${gameId}/servers/public?sortOrder=1&excludeFullGames=true&limit=100` :
  2191. `https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=100`; // why does this work lmao
  2192.  
  2193. const url = cursor ? `${baseUrl}&cursor=${cursor}` : baseUrl;
  2194. const data = await fetchServersWithRetry(url);
  2195.  
  2196. // Safety check: Ensure the server list is valid and iterable
  2197. if (!Array.isArray(data.data)) {
  2198. ConsoleLogEnabled('[DEBUG] Invalid server list received. Waiting 1 second before retrying...');
  2199. await delay(1000); // Wait 1 second before retrying
  2200. continue; // Skip the rest of the loop and retry
  2201. }
  2202.  
  2203. // Filter and process servers
  2204. for (const server of data.data) {
  2205. if (server.playing === maxPlayers) {
  2206. await rbx_card(server.id, server.playerTokens, server.maxPlayers, server.playing, gameId);
  2207. serversFound++;
  2208.  
  2209. if (serversFound >= 16) {
  2210. break;
  2211. }
  2212. } else if (!isCloserToOne && server.playing > maxPlayers) {
  2213. topDownServers.push(server); // Add to top-down fallback list
  2214. } else if (isCloserToOne && server.playing < maxPlayers) {
  2215. bottomUpServers.push(server); // Add to bottom-up fallback list
  2216. }
  2217. }
  2218.  
  2219. // Exit if no more servers are available
  2220. if (!data.nextPageCursor) {
  2221. break;
  2222. }
  2223.  
  2224. cursor = data.nextPageCursor;
  2225.  
  2226. // Adjust delay dynamically
  2227. if (currentDelay > 150) {
  2228. currentDelay = Math.max(150, currentDelay / 2); // Gradually reduce delay
  2229. }
  2230. ConsoleLogEnabled(`[DEBUG] Waiting ${currentDelay / 1000} seconds before next request...`);
  2231. await delay(currentDelay);
  2232. }
  2233.  
  2234. // If no exact matches were found or time limit reached, use fallback servers
  2235. if (serversFound === 0 && (topDownServers.length > 0 || bottomUpServers.length > 0)) {
  2236. notifications(`There are no servers with ${maxPlayers} players. Showing servers closest to ${maxPlayers} players.`, 'warning', '😔', '8000');
  2237. // Sort top-down servers by player count (ascending)
  2238. topDownServers.sort((a, b) => a.playing - b.playing);
  2239.  
  2240. // Sort bottom-up servers by player count (descending)
  2241. bottomUpServers.sort((a, b) => b.playing - a.playing);
  2242.  
  2243. // Combine both fallback lists (prioritize top-down servers first)
  2244. const combinedFallback = [...topDownServers, ...bottomUpServers];
  2245.  
  2246. for (const server of combinedFallback) {
  2247. await rbx_card(server.id, server.playerTokens, server.maxPlayers, server.playing, gameId);
  2248. serversFound++;
  2249.  
  2250. if (serversFound >= 16) {
  2251. break;
  2252. }
  2253. }
  2254. }
  2255.  
  2256. if (serversFound <= 0) {
  2257. notifications('No Servers Found Within The Provided Criteria', 'info', '🔎', '5000');
  2258. }
  2259. } catch (error) {
  2260. ConsoleLogEnabled('[DEBUG] Error in filterServersByPlayerCount:', error);
  2261. } finally {
  2262. Loadingbar(false);
  2263. disableFilterButton(false);
  2264. }
  2265. }
  2266.  
  2267. /*********************************************************************************************************************************************************************************************************************************************
  2268. Functions for the 4th button
  2269.  
  2270. *********************************************************************************************************************************************************************************************************************************************/
  2271.  
  2272. /*******************************************************
  2273. name of function: random_servers
  2274. 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.
  2275. *******************************************************/
  2276. async function random_servers() {
  2277. notifications('Finding Random Servers. Please wait 2-5 seconds', 'success', '🔎', '5000');
  2278. // Disable the "Load More" button and show the loading bar
  2279. Loadingbar(true);
  2280. disableFilterButton(true);
  2281. disableLoadMoreButton();
  2282.  
  2283. // Get the game ID from the URL
  2284. const gameId = window.location.pathname.split('/')[2];
  2285.  
  2286. try {
  2287. // Fetch servers from the first URL with retry logic
  2288. const firstUrl = `https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=10`;
  2289. const firstData = await fetchWithRetry(firstUrl, 10); // Retry up to 3 times
  2290.  
  2291. // Wait for 5 seconds
  2292. await delay(1500);
  2293.  
  2294. // Fetch servers from the second URL with retry logic
  2295. const secondUrl = `https://games.roblox.com/v1/games/${gameId}/servers/public?sortOrder=1&excludeFullGames=true&limit=10`;
  2296. const secondData = await fetchWithRetry(secondUrl, 10); // Retry up to 3 times
  2297.  
  2298. // Combine the servers from both URLs
  2299. const combinedServers = [...firstData.data, ...secondData.data];
  2300.  
  2301. // Remove duplicates by server ID
  2302. const uniqueServers = [];
  2303. const seenServerIds = new Set();
  2304.  
  2305. for (const server of combinedServers) {
  2306. if (!seenServerIds.has(server.id)) {
  2307. seenServerIds.add(server.id);
  2308. uniqueServers.push(server);
  2309. }
  2310. }
  2311.  
  2312. // Shuffle the unique servers array
  2313. const shuffledServers = shuffleArray(uniqueServers);
  2314.  
  2315. // Get the first 16 shuffled servers
  2316. const selectedServers = shuffledServers.slice(0, 16);
  2317.  
  2318. // Process each server in random order
  2319. for (const server of selectedServers) {
  2320. const {
  2321. id: serverId,
  2322. playerTokens,
  2323. maxPlayers,
  2324. playing
  2325. } = server;
  2326.  
  2327. // Pass the server data to the card creation function
  2328. await rbx_card(serverId, playerTokens, maxPlayers, playing, gameId);
  2329. }
  2330. } catch (error) {
  2331. ConsoleLogEnabled('Error fetching server data:', error);
  2332. notifications('Error: Failed to fetch server data. Please try again later.', 'error', '⚠️', '5000');
  2333. } finally {
  2334. // Hide the loading bar and enable the filter button
  2335. Loadingbar(false);
  2336. disableFilterButton(false);
  2337. }
  2338. }
  2339.  
  2340. /*******************************************************
  2341. name of function: fetchWithRetry
  2342. description: Fetches data from a URL with retry logic for 429 errors using GM_xmlhttpRequest.
  2343. *******************************************************/
  2344. function fetchWithRetry(url, retries) {
  2345. return new Promise((resolve, reject) => {
  2346. const attemptFetch = (attempt = 0) => {
  2347. GM_xmlhttpRequest({
  2348. method: "GET",
  2349. url: url,
  2350. onload: function(response) {
  2351. if (response.status === 429) {
  2352. if (attempt < retries) {
  2353. ConsoleLogEnabled(`Rate limited. Retrying in 2.5 seconds... (Attempt ${attempt + 1}/${retries})`);
  2354. setTimeout(() => attemptFetch(attempt + 1), 1500); // Wait 1.5 seconds and retry
  2355. } else {
  2356. reject(new Error('Rate limit exceeded after retries'));
  2357. }
  2358. } else if (response.status >= 200 && response.status < 300) {
  2359. try {
  2360. const data = JSON.parse(response.responseText);
  2361. resolve(data);
  2362. } catch (error) {
  2363. reject(new Error('Failed to parse JSON response'));
  2364. }
  2365. } else {
  2366. reject(new Error(`HTTP error: ${response.status}`));
  2367. }
  2368. },
  2369. onerror: function(error) {
  2370. if (attempt < retries) {
  2371. ConsoleLogEnabled(`Error occurred. Retrying in 10 seconds... (Attempt ${attempt + 1}/${retries})`);
  2372. setTimeout(() => attemptFetch(attempt + 1), 10000); // Wait 10 seconds and retry
  2373. } else {
  2374. reject(error);
  2375. }
  2376. }
  2377. });
  2378. };
  2379.  
  2380. attemptFetch();
  2381. });
  2382. }
  2383.  
  2384. /*******************************************************
  2385. name of function: shuffleArray
  2386. description: Shuffles an array using the Fisher-Yates algorithm.
  2387. *******************************************************/
  2388. function shuffleArray(array) {
  2389. for (let i = array.length - 1; i > 0; i--) {
  2390. const j = Math.floor(Math.random() * (i + 1)); // Random index from 0 to i
  2391. [array[i], array[j]] = [array[j], array[i]]; // Swap elements
  2392. }
  2393. return array;
  2394. }
  2395.  
  2396.  
  2397. /*********************************************************************************************************************************************************************************************************************************************
  2398. Functions for the 5th button. taken from my other project
  2399.  
  2400. *********************************************************************************************************************************************************************************************************************************************/
  2401.  
  2402. if (Isongamespage) {
  2403. // Create a <style> element
  2404. const style = document.createElement('style');
  2405. style.textContent = `
  2406. /* Overlay for the modal background */
  2407. .overlay {
  2408. position: fixed;
  2409. top: 0;
  2410. left: 0;
  2411. width: 100%;
  2412. height: 100%;
  2413. background-color: rgba(0, 0, 0, 0.85); /* Solid black overlay */
  2414. z-index: 1000; /* Ensure overlay is below the popup */
  2415. opacity: 0; /* Start invisible */
  2416. animation: fadeIn 0.3s ease forwards; /* Fade-in animation */
  2417. }
  2418.  
  2419. @keyframes fadeIn {
  2420. from {
  2421. opacity: 0;
  2422. }
  2423. to {
  2424. opacity: 1;
  2425. }
  2426. }
  2427.  
  2428. /* Popup Container for the server region */
  2429. .filter-popup {
  2430. background-color: #1e1e1e; /* Darker background */
  2431. color: #ffffff; /* White text */
  2432. padding: 25px;
  2433. border-radius: 12px;
  2434. box-shadow: 0 8px 20px rgba(0, 0, 0, 0.5);
  2435. width: 320px;
  2436. max-width: 90%;
  2437. position: fixed; /* Fixed positioning */
  2438. top: 50%; /* Center vertically */
  2439. left: 50%; /* Center horizontally */
  2440. transform: translate(-50%, -50%); /* Offset to truly center */
  2441. text-align: center;
  2442. z-index: 1001; /* Ensure popup is above the overlay */
  2443. border: 1px solid #444; /* Subtle border */
  2444. opacity: 0; /* Start invisible */
  2445. animation: fadeInPopup 0.3s ease 0.1s forwards; /* Fade-in animation with delay */
  2446. }
  2447.  
  2448. @keyframes fadeInPopup {
  2449. from {
  2450. opacity: 0;
  2451. transform: translate(-50%, -55%); /* Slight upward offset */
  2452. }
  2453. to {
  2454. opacity: 1;
  2455. transform: translate(-50%, -50%); /* Center position */
  2456. }
  2457. }
  2458.  
  2459. /* Fade-out animation for overlay and popup */
  2460. .overlay.fade-out {
  2461. animation: fadeOut 0.3s ease forwards;
  2462. }
  2463.  
  2464. .filter-popup.fade-out {
  2465. animation: fadeOutPopup 0.3s ease forwards;
  2466. }
  2467.  
  2468. @keyframes fadeOut {
  2469. from {
  2470. opacity: 1;
  2471. }
  2472. to {
  2473. opacity: 0;
  2474. }
  2475. }
  2476.  
  2477. @keyframes fadeOutPopup {
  2478. from {
  2479. opacity: 1;
  2480. transform: translate(-50%, -50%); /* Center position */
  2481. }
  2482. to {
  2483. opacity: 0;
  2484. transform: translate(-50%, -55%); /* Slight upward offset */
  2485. }
  2486. }
  2487.  
  2488. /* Close Button for the server selector */
  2489. #closePopup {
  2490. position: absolute;
  2491. top: 5px; /* Reduced from 12px to 5px */
  2492. right: 1px; /* Reduced from 12px to 5px */
  2493. background: transparent; /* Transparent background */
  2494. border: none;
  2495. color: #ffffff; /* White color */
  2496. font-size: 20px;
  2497. cursor: pointer;
  2498. width: 28px;
  2499. height: 28px;
  2500. border-radius: 50%;
  2501. display: flex;
  2502. align-items: center;
  2503. justify-content: center;
  2504. transition: background-color 0.3s ease, color 0.3s ease;
  2505. }
  2506.  
  2507. #closePopup:hover {
  2508. background-color: rgba(255, 255, 255, 0.1); /* Light hover effect */
  2509. color: #ff4444; /* Red color on hover */
  2510. }
  2511.  
  2512. /* Label */
  2513. .filter-popup label {
  2514. display: block;
  2515. margin-bottom: 12px;
  2516. font-size: 16px;
  2517. color: #ffffff;
  2518. font-weight: 500; /* Slightly bolder text */
  2519. }
  2520.  
  2521. /* Dropdown */
  2522. .filter-popup select {
  2523. background-color: #333; /* Darker gray background */
  2524. color: #ffffff; /* White text */
  2525. padding: 10px;
  2526. border-radius: 6px;
  2527. border: 1px solid #555; /* Darker border */
  2528. width: 100%;
  2529. margin-bottom: 12px;
  2530. font-size: 14px;
  2531. transition: border-color 0.3s ease;
  2532. }
  2533.  
  2534. .filter-popup select:focus {
  2535. border-color: #888; /* Lighter border on focus */
  2536. outline: none;
  2537. }
  2538.  
  2539. /* Custom Input */
  2540. .filter-popup input[type="number"] {
  2541. background-color: #333; /* Darker gray background */
  2542. color: #ffffff; /* White text */
  2543. padding: 10px;
  2544. border-radius: 6px;
  2545. border: 1px solid #555; /* Darker border */
  2546. width: 100%;
  2547. margin-bottom: 12px;
  2548. font-size: 14px;
  2549. transition: border-color 0.3s ease;
  2550. }
  2551.  
  2552. .filter-popup input[type="number"]:focus {
  2553. border-color: #888; /* Lighter border on focus */
  2554. outline: none;
  2555. }
  2556.  
  2557. /* Confirm Button */
  2558. #confirmServerCount {
  2559. background-color: #444; /* Dark gray background */
  2560. color: #ffffff; /* White text */
  2561. padding: 10px 20px;
  2562. border: 1px solid #666; /* Gray border */
  2563. border-radius: 6px;
  2564. cursor: pointer;
  2565. font-size: 14px;
  2566. width: 100%;
  2567. transition: background-color 0.3s ease, transform 0.2s ease;
  2568. }
  2569.  
  2570. #confirmServerCount:hover {
  2571. background-color: #555; /* Lighter gray on hover */
  2572. transform: translateY(-1px); /* Slight lift effect */
  2573. }
  2574.  
  2575. #confirmServerCount:active {
  2576. transform: translateY(0); /* Reset lift effect on click */
  2577. }
  2578.  
  2579. /* Highlighted server item */
  2580. .rbx-game-server-item.highlighted {
  2581. border: 2px solid #4caf50; /* Green border */
  2582. border-radius: 8px;
  2583. background-color: rgba(76, 175, 80, 0.1); /* Subtle green background */
  2584. }
  2585.  
  2586. /* Disabled fetch button */
  2587. .fetch-button:disabled {
  2588. opacity: 0.5;
  2589. cursor: not-allowed;
  2590. }
  2591.  
  2592. /* Popup Header */
  2593. .popup-header {
  2594. margin-bottom: 24px;
  2595. text-align: left;
  2596. padding: 16px;
  2597. background-color: rgba(255, 255, 255, 0.05); /* Subtle background for contrast */
  2598. border-radius: 8px;
  2599. border: 1px solid rgba(255, 255, 255, 0.1); /* Subtle border */
  2600. transition: background-color 0.3s ease, border-color 0.3s ease;
  2601. }
  2602.  
  2603. .popup-header:hover {
  2604. background-color: rgba(255, 255, 255, 0.08); /* Slightly brighter on hover */
  2605. border-color: rgba(255, 255, 255, 0.2);
  2606. }
  2607.  
  2608. .popup-header h3 {
  2609. margin: 0 0 12px 0;
  2610. font-size: 22px;
  2611. color: #ffffff;
  2612. font-weight: 700; /* Bolder for emphasis */
  2613. letter-spacing: -0.5px; /* Tighter letter spacing for modern look */
  2614. }
  2615.  
  2616. .popup-header p {
  2617. margin: 0;
  2618. font-size: 14px;
  2619. color: #cccccc;
  2620. line-height: 1.6; /* Improved line height for readability */
  2621. opacity: 0.9; /* Slightly transparent for a softer look */
  2622. }
  2623.  
  2624. /* Popup Footer */
  2625. .popup-footer {
  2626. margin-top: 20px;
  2627. text-align: left;
  2628. font-size: 14px;
  2629. color: #ffcc00; /* Yellow color for warnings */
  2630. background-color: rgba(255, 204, 0, 0.15); /* Lighter yellow background */
  2631. padding: 12px;
  2632. border-radius: 8px;
  2633. border: 1px solid rgba(255, 204, 0, 0.15); /* Subtle border */
  2634. transition: background-color 0.3s ease, border-color 0.3s ease;
  2635. }
  2636.  
  2637. .popup-footer:hover {
  2638. background-color: rgba(255, 204, 0, 0.25); /* Slightly brighter on hover */
  2639. border-color: rgba(255, 204, 0, 0.25);
  2640. }
  2641.  
  2642. .popup-footer p {
  2643. margin: 0;
  2644. line-height: 1.5;
  2645. font-weight: 500; /* Slightly bolder for emphasis */
  2646. }
  2647.  
  2648. /* Label */
  2649. .filter-popup label {
  2650. display: block;
  2651. margin-bottom: 12px;
  2652. font-size: 15px;
  2653. color: #ffffff;
  2654. font-weight: 500;
  2655. text-align: left;
  2656. opacity: 0.9; /* Slightly transparent for a softer look */
  2657. transition: opacity 0.3s ease;
  2658. }
  2659.  
  2660. .filter-popup label:hover {
  2661. opacity: 1; /* Fully opaque on hover */
  2662. }
  2663.  
  2664. select:hover, select:focus {
  2665. border-color: #ffffff;
  2666. outline: none;
  2667. }
  2668.  
  2669.  
  2670. `;
  2671. // Append the <style> element to the document head
  2672. document.head.appendChild(style);
  2673. }
  2674.  
  2675.  
  2676. // Function to show the message under the "Load More" button
  2677. function showMessage(message) {
  2678. const loadMoreButtonContainer = document.querySelector('.rbx-public-running-games-footer');
  2679.  
  2680. if (!loadMoreButtonContainer) {
  2681. ConsoleLogEnabled("Error: 'Load More' button container not found! Ensure the element exists in the DOM.");
  2682. return;
  2683. }
  2684.  
  2685. // Create the message element
  2686. const messageElement = document.createElement('div');
  2687. messageElement.className = 'filter-message';
  2688. messageElement.textContent = message;
  2689.  
  2690. // Clear any existing message and append the new one
  2691. const existingMessage = loadMoreButtonContainer.querySelector('.filter-message');
  2692. if (existingMessage) {
  2693. ConsoleLogEnabled("Warning: An existing message was found and will be replaced.");
  2694. existingMessage.remove(); // Remove the existing message if it exists
  2695. }
  2696.  
  2697. loadMoreButtonContainer.appendChild(messageElement);
  2698.  
  2699. ConsoleLogEnabled("Message displayed successfully:", message);
  2700.  
  2701. return messageElement;
  2702. }
  2703.  
  2704.  
  2705. // Function to hide the message of the showmessage functioon
  2706. function hideMessage() {
  2707. const messageElement = document.querySelector('.filter-message');
  2708. if (messageElement) messageElement.remove();
  2709. }
  2710.  
  2711. // Function to show the popup for random stuff
  2712. function showPopup() {
  2713. const overlay = document.createElement('div');
  2714. overlay.className = 'overlay';
  2715.  
  2716. const popup = document.createElement('div');
  2717. popup.className = 'filter-popup';
  2718. popup.textContent = 'Uh somethings wrong if you see this message. Please report to the greasyfork issues page!';
  2719.  
  2720. document.body.appendChild(overlay);
  2721. document.body.appendChild(popup);
  2722.  
  2723. return popup;
  2724. }
  2725.  
  2726. // Function to hide the popup for the stuff
  2727. function hidePopup() {
  2728. const popup = document.querySelector('.filter-popup');
  2729. const overlay = document.querySelector('.overlay');
  2730.  
  2731. if (popup) popup.remove();
  2732. if (overlay) overlay.remove();
  2733. }
  2734.  
  2735. // Function to fetch server details so game id and job id. yea!
  2736. async function fetchServerDetails(gameId, jobId) {
  2737. return new Promise((resolve, reject) => {
  2738. GM_xmlhttpRequest({
  2739. method: "POST",
  2740. url: "https://gamejoin.roblox.com/v1/join-game-instance", // url for game id
  2741. headers: { // doesent need cookie cuase of magic
  2742. "Content-Type": "application/json",
  2743. "User-Agent": "Roblox/WinInet",
  2744. },
  2745. data: JSON.stringify({
  2746. placeId: gameId,
  2747. gameId: jobId
  2748. }),
  2749. onload: function(response) {
  2750. const json = JSON.parse(response.responseText);
  2751.  
  2752. ConsoleLogEnabled("API Response:", json); // This prints the full response
  2753.  
  2754. // Check if the response indicates that the user needs to purchase the game
  2755. if (json.status === 12 && json.message === 'You need to purchase access to this game before you can play.') { // yea error message!
  2756. reject('purchase_required'); // Special error code for this case yea!
  2757. return;
  2758. }
  2759.  
  2760.  
  2761. // Check if the response indicates that the user needs to purchase the game
  2762. if (json.status === 12 && json.message === 'Cannot join this non-root place due to join restrictions') { // yea error message!
  2763. reject('subplace_join_restriction'); // Special error code for this case yea!
  2764. return;
  2765. }
  2766.  
  2767. const address = json?.joinScript?.UdmuxEndpoints?.[0]?.Address ?? json?.joinScript?.MachineAddress;
  2768.  
  2769. if (!address) {
  2770. ConsoleLogEnabled("API Response (Unknown Location) Which means Full Server!:", json); // Log the API response for debug
  2771. reject(`Unable to fetch server location: Status ${json.status}`); // debug
  2772. return;
  2773. }
  2774.  
  2775. const location = serverRegionsByIp[address.replace(/^(128\.116\.\d+)\.\d+$/, "$1.0")]; // lmao all servers atart with this so yea dont argue with me
  2776.  
  2777. if (!location) {
  2778. ConsoleLogEnabled("API Response (Unknown Location):", json); // Log the API response into the chat. might remove it from production but idc rn
  2779. reject(`Unknown server address ${address}`);
  2780. return;
  2781. }
  2782.  
  2783. resolve(location);
  2784. },
  2785. onerror: function(error) {
  2786. ConsoleLogEnabled("API Request Failed:", error); // damn if this happpens idk what to tell u
  2787. reject(`Failed to fetch server details: ${error}`);
  2788. },
  2789. });
  2790. });
  2791. }
  2792.  
  2793. // cusomt delay also known as sleep fucntion in js cause this language sucks and doesent have a default function
  2794. function delay(ms) {
  2795. return new Promise(resolve => setTimeout(resolve, ms));
  2796. }
  2797.  
  2798. // Function to create a popup for selecting the number of servers
  2799. // basically yea thats what it doesent
  2800. function createServerCountPopup(callback) {
  2801. const overlay = document.createElement('div');
  2802. overlay.className = 'overlay';
  2803.  
  2804. const popup = document.createElement('div');
  2805. popup.className = 'filter-popup'; // reason 100 is selected because thjats how many the api will show per request
  2806. popup.innerHTML = `
  2807. <button id="closePopup">X</button>
  2808. <div class="popup-header">
  2809. <h3>Select Number of Servers</h3>
  2810. <p>Choose how many servers you want to search. Higher values will provide more location variety but may take longer to process.</p>
  2811. <div class="popup-footer">
  2812. <p><strong>Note:</strong> Searching over 100 servers may take longer and could result in rate limiting.</p> <!-- For everyone's sake dont ask me about the &nbsp; chain.-->
  2813. </div>
  2814. </div>
  2815. <label for="serverCount">Select Number of Servers:</label>
  2816. <select id="serverCount">
  2817. <option value="10">10 Servers</option>
  2818. <option value="25">25 Servers</option>
  2819. <option value="50">50 Servers</option>
  2820. <option value="100" selected>100 Servers</option>
  2821. <option value="200">200 Servers</option>
  2822. <option value="500">500 Servers</option>
  2823. <option value="1000">1000 Servers</option>
  2824. <option value="custom">Custom</option>
  2825. </select>
  2826. <input id="customServerCount" type="number" min="1" max="1000" placeholder="Enter a number (1-1000)" style="display: none;">
  2827. <button id="confirmServerCount">Confirm</button>
  2828. `;
  2829.  
  2830. document.body.appendChild(overlay);
  2831. document.body.appendChild(popup);
  2832.  
  2833. const serverCountDropdown = popup.querySelector('#serverCount');
  2834. const customServerCountInput = popup.querySelector('#customServerCount');
  2835. const confirmButton = popup.querySelector('#confirmServerCount');
  2836. const closeButton = popup.querySelector('#closePopup');
  2837.  
  2838. // Show/hide custom input based on dropdown selection
  2839. serverCountDropdown.addEventListener('change', () => {
  2840. if (serverCountDropdown.value === 'custom') {
  2841. customServerCountInput.style.display = 'block';
  2842. } else {
  2843. customServerCountInput.style.display = 'none';
  2844. }
  2845. });
  2846.  
  2847. // button click on start or what ever
  2848. confirmButton.addEventListener('click', () => {
  2849. let serverCount;
  2850.  
  2851. if (serverCountDropdown.value === 'custom') {
  2852. serverCount = parseInt(customServerCountInput.value);
  2853.  
  2854. // Validate custom input
  2855. if (isNaN(serverCount) || serverCount < 1 || serverCount > 1000) {
  2856. notifications('Error: Please enter a valid number between 1 and 1000.', 'error', '⚠️', '5000')
  2857. return;
  2858. }
  2859. } else {
  2860. serverCount = parseInt(serverCountDropdown.value);
  2861. }
  2862.  
  2863. // Show an alert if the user selects a number above 100
  2864. if (serverCount > 100) { // error cause people dont know about this maybe. idk yea so here. also if u think this is a stupid way i should have done it before the button press idc so yea
  2865. notifications('Warning: Searching over 100 servers may take some time and you might get rate limited!', 'warning', '❗', '8000');
  2866. }
  2867.  
  2868. // Pass the selected server count to the callback
  2869. callback(serverCount);
  2870. disableFilterButton(true); // disbale filter button
  2871. disableLoadMoreButton(true); // disable load more button
  2872. hidePopup();
  2873. Loadingbar(true); // enable loading bar
  2874. });
  2875.  
  2876. // Close button logic :))
  2877. closeButton.addEventListener('click', () => {
  2878. hidePopup();
  2879. });
  2880.  
  2881. // Function to hide the popup
  2882. // yea im dumb and used the same function name but it works and im too lazy to change it
  2883. function hidePopup() {
  2884. const overlay = document.querySelector('.overlay');
  2885. const popup = document.querySelector('.filter-popup');
  2886.  
  2887. // Add fade-out classes
  2888. overlay.classList.add('fade-out');
  2889. popup.classList.add('fade-out');
  2890.  
  2891. // Remove elements after animation completes
  2892. setTimeout(() => {
  2893. overlay.remove();
  2894. popup.remove();
  2895. }, 300); // Match the duration of the fade-out animation
  2896. }
  2897. }
  2898.  
  2899. // Function to fetch public servers
  2900. // totallimit is amount of sevrers to fetch
  2901. async function fetchPublicServers(gameId, totalLimit) {
  2902. let servers = [];
  2903. let cursor = null;
  2904.  
  2905. while (servers.length < totalLimit) { // too lazy to comment any of this. hopefully i remember what this does in the future
  2906. const url = `https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=100${cursor ? `&cursor=${cursor}` : ''}`;
  2907.  
  2908. const response = await new Promise((resolve, reject) => {
  2909. GM_xmlhttpRequest({
  2910. method: "GET",
  2911. url: url,
  2912. onload: function(response) {
  2913. resolve(JSON.parse(response.responseText));
  2914. },
  2915. onerror: function(error) {
  2916. reject(`Failed to fetch public servers: ${error}`);
  2917. },
  2918. });
  2919. });
  2920.  
  2921. servers = servers.concat(response.data);
  2922.  
  2923. if (!response.nextPageCursor || servers.length >= totalLimit) {
  2924. break;
  2925. }
  2926.  
  2927. cursor = response.nextPageCursor;
  2928. await delay(3000); // wait 3 seconds before each page request. if u think this is slow i tried 1 second i got rate limited :|
  2929. }
  2930.  
  2931. return servers.slice(0, totalLimit);
  2932. }
  2933.  
  2934. function createFilterDropdowns(servers) {
  2935. // Create the main filter container
  2936. const filterContainer = document.createElement('div');
  2937. Object.assign(filterContainer.style, {
  2938. display: 'flex',
  2939. gap: '24px',
  2940. alignItems: 'center',
  2941. padding: '28px',
  2942. background: 'linear-gradient(145deg, rgba(25,25,25,0.98) 0%, rgba(15,15,15,0.98) 100%)',
  2943. borderRadius: '20px',
  2944. boxShadow: '0 16px 32px rgba(0,0,0,0.4)',
  2945. backdropFilter: 'blur(20px)',
  2946. opacity: '0',
  2947. transform: 'translateY(-40px) scale(0.95)',
  2948. transition: 'all 0.8s cubic-bezier(0.23, 1, 0.32, 1)',
  2949. position: 'relative',
  2950. border: '1px solid rgba(255,255,255,0.1)',
  2951. margin: '24px',
  2952. fontFamily: "'Inter', sans-serif",
  2953. fontSize: '16px' // Larger font size
  2954. });
  2955.  
  2956. // Add animated border glow with red accents
  2957. const borderGlow = document.createElement('div');
  2958. Object.assign(borderGlow.style, {
  2959. position: 'absolute',
  2960. inset: '0',
  2961. borderRadius: '20px',
  2962. pointerEvents: 'none',
  2963. background: 'linear-gradient(45deg, rgba(255,50,50,0.2), rgba(255,50,50,0.1))',
  2964. zIndex: '-1',
  2965. animation: 'gradientShift 12s ease infinite'
  2966. });
  2967. filterContainer.appendChild(borderGlow);
  2968.  
  2969. // Add dynamic CSS for animations
  2970. const style = document.createElement('style');
  2971. style.textContent = `
  2972. @keyframes gradientShift {
  2973. 0% { background-position: 0% 50%; }
  2974. 50% { background-position: 100% 50%; }
  2975. 100% { background-position: 0% 50%; }
  2976. }
  2977. select::-webkit-scrollbar {
  2978. width: '10px';
  2979. }
  2980. select::-webkit-scrollbar-track {
  2981. background: rgba(30,30,30,0.5);
  2982. }
  2983. select::-webkit-scrollbar-thumb {
  2984. background: rgba(255,50,50,0.4);
  2985. border-radius: '6px';
  2986. }
  2987. select::-webkit-scrollbar-thumb:hover {
  2988. background: rgba(255,50,50,0.6);
  2989. }
  2990. `;
  2991. document.head.appendChild(style);
  2992.  
  2993. // Add logo with hover effects
  2994. const logo = document.createElement('img');
  2995. logo.src = window.Base64Images.logo;
  2996. Object.assign(logo.style, {
  2997. width: '56px',
  2998. height: '56px',
  2999. borderRadius: '14px',
  3000. marginRight: '20px',
  3001. transition: 'transform 0.4s ease, filter 0.4s ease',
  3002. filter: 'drop-shadow(0 6px 12px rgba(255,50,50,0.25))'
  3003. });
  3004. logo.addEventListener('mouseover', () => {
  3005. logo.style.transform = 'rotate(-10deg) scale(1.2)';
  3006. logo.style.filter = 'drop-shadow(0 8px 16px rgba(255,50,50,0.4))';
  3007. });
  3008. logo.addEventListener('mouseout', () => {
  3009. logo.style.transform = 'rotate(0) scale(1)';
  3010. logo.style.filter = 'drop-shadow(0 6px 12px rgba(255,50,50,0.25))';
  3011. });
  3012. filterContainer.appendChild(logo);
  3013.  
  3014. // Function to create a premium dropdown
  3015. const createDropdown = (id, placeholder) => {
  3016. const wrapper = document.createElement('div');
  3017. Object.assign(wrapper.style, {
  3018. position: 'relative',
  3019. minWidth: '220px' // Wider dropdown for a modern look
  3020. });
  3021.  
  3022. const dropdown = document.createElement('select');
  3023. dropdown.id = id;
  3024. dropdown.innerHTML = `<option value="">${placeholder}</option>`;
  3025. Object.assign(dropdown.style, {
  3026. width: '100%',
  3027. padding: '16px 56px 16px 24px', // More padding for a spacious feel
  3028. fontSize: '16px', // Larger font size
  3029. fontWeight: '500',
  3030. background: 'linear-gradient(145deg, rgba(40,40,40,0.9), rgba(25,25,25,0.9))',
  3031. color: '#FF1A1A',
  3032. border: '1px solid rgba(255,255,255,0.15)',
  3033. borderRadius: '12px',
  3034. boxShadow: '0 6px 12px rgba(0,0,0,0.2)',
  3035. appearance: 'none',
  3036. cursor: 'pointer',
  3037. transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
  3038. opacity: '0',
  3039. transform: 'translateY(-20px)'
  3040. });
  3041.  
  3042. // Custom chevron icon
  3043. const chevron = document.createElement('div');
  3044. chevron.innerHTML = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 9l6 6 6-6"/></svg>`;
  3045. Object.assign(chevron.style, {
  3046. position: 'absolute',
  3047. right: '20px',
  3048. top: '50%',
  3049. transform: 'translateY(-50%)',
  3050. pointerEvents: 'none',
  3051. transition: 'transform 0.4s ease',
  3052. color: 'rgba(255,50,50,0.9)' // Red chevron for a premium touch
  3053. });
  3054.  
  3055. // Dropdown interactions
  3056. dropdown.addEventListener('mouseover', () => {
  3057. dropdown.style.background = 'linear-gradient(145deg, rgba(60,60,60,0.9), rgba(40,40,40,0.9))';
  3058. dropdown.style.boxShadow = '0 8px 16px rgba(0,0,0,0.3)';
  3059. chevron.style.transform = 'translateY(-50%) rotate(180deg)';
  3060. });
  3061. dropdown.addEventListener('mouseout', () => {
  3062. dropdown.style.background = 'linear-gradient(145deg, rgba(40,40,40,0.9), rgba(25,25,25,0.9))';
  3063. dropdown.style.boxShadow = '0 6px 12px rgba(0,0,0,0.2)';
  3064. chevron.style.transform = 'translateY(-50%)';
  3065. });
  3066. dropdown.addEventListener('focus', () => {
  3067. dropdown.style.outline = '2px solid rgba(255,50,50,0.5)';
  3068. dropdown.style.outlineOffset = '2px';
  3069. });
  3070. dropdown.addEventListener('change', () => {
  3071. dropdown.style.transform = 'scale(0.98)';
  3072. setTimeout(() => dropdown.style.transform = 'scale(1)', 150);
  3073. });
  3074.  
  3075. // Fade-in animation
  3076. setTimeout(() => {
  3077. dropdown.style.opacity = '1';
  3078. dropdown.style.transform = 'translateY(0)';
  3079. }, 400);
  3080.  
  3081. wrapper.appendChild(dropdown);
  3082. wrapper.appendChild(chevron);
  3083. return wrapper;
  3084. };
  3085.  
  3086. // Create dropdowns
  3087. const countryDropdown = createDropdown('countryFilter', 'All Countries');
  3088. const cityDropdown = createDropdown('cityFilter', 'All Cities');
  3089.  
  3090. // Populate dropdowns with server data
  3091. const countryCounts = {};
  3092. servers.forEach(server => {
  3093. const country = server.location.country.name;
  3094. countryCounts[country] = (countryCounts[country] || 0) + 1;
  3095. });
  3096.  
  3097. Object.keys(countryCounts).forEach(country => {
  3098. const option = document.createElement('option');
  3099. option.value = country;
  3100. option.textContent = `${country} (${countryCounts[country]})`;
  3101. countryDropdown.querySelector('select').appendChild(option);
  3102. });
  3103.  
  3104. countryDropdown.querySelector('select').addEventListener('change', () => {
  3105. const selectedCountry = countryDropdown.querySelector('select').value;
  3106. cityDropdown.querySelector('select').innerHTML = '<option value="">All Cities</option>';
  3107.  
  3108. if (selectedCountry) {
  3109. const cityCounts = {};
  3110. servers
  3111. .filter(server => server.location.country.name === selectedCountry)
  3112. .forEach(server => {
  3113. const city = server.location.city;
  3114. const region = server.location.region?.name;
  3115. const cityKey = region ? `${city}, ${region}` : city;
  3116. cityCounts[cityKey] = (cityCounts[cityKey] || 0) + 1;
  3117. });
  3118.  
  3119. Object.keys(cityCounts).forEach(city => {
  3120. const option = document.createElement('option');
  3121. option.value = city;
  3122. option.textContent = `${city} (${cityCounts[city]})`;
  3123. cityDropdown.querySelector('select').appendChild(option);
  3124. });
  3125.  
  3126. // Animate city dropdown update
  3127. cityDropdown.querySelector('select').style.opacity = '0';
  3128. cityDropdown.querySelector('select').style.transform = 'translateY(-10px)';
  3129. setTimeout(() => {
  3130. cityDropdown.querySelector('select').style.opacity = '1';
  3131. cityDropdown.querySelector('select').style.transform = 'translateY(0)';
  3132. }, 150);
  3133. }
  3134. });
  3135.  
  3136. // Append dropdowns to container
  3137. filterContainer.appendChild(countryDropdown);
  3138. filterContainer.appendChild(cityDropdown);
  3139.  
  3140. // Fade-in container
  3141. setTimeout(() => {
  3142. filterContainer.style.opacity = '1';
  3143. filterContainer.style.transform = 'translateY(0) scale(1)';
  3144. }, 200);
  3145.  
  3146. return filterContainer;
  3147. }
  3148.  
  3149. // Function to filter servers based on selected country and city cause im lazy
  3150. function filterServers(servers, country, city) {
  3151. return servers.filter(server => {
  3152. const matchesCountry = !country || server.location.country.name === country;
  3153. const matchesCity = !city || `${server.location.city}${server.location.region?.name ? `, ${server.location.region.name}` : ''}` === city;
  3154. return matchesCountry && matchesCity;
  3155. });
  3156. }
  3157.  
  3158. // Function to sort servers by ping. maybe inaccurate but thats roblox's problem not mine
  3159. function sortServersByPing(servers) {
  3160. return servers.sort((a, b) => a.server.ping - b.server.ping);
  3161. }
  3162.  
  3163. async function fetchPlayerThumbnails_servers(playerTokens) {
  3164. const body = playerTokens.map(token => ({
  3165. requestId: `0:${token}:AvatarHeadshot:150x150:png:regular`,
  3166. type: "AvatarHeadShot",
  3167. targetId: 0,
  3168. token,
  3169. format: "png",
  3170. size: "150x150",
  3171. }));
  3172.  
  3173. const response = await fetch("https://thumbnails.roblox.com/v1/batch", {
  3174. method: "POST",
  3175. headers: {
  3176. "Content-Type": "application/json",
  3177. Accept: "application/json",
  3178. },
  3179. body: JSON.stringify(body),
  3180. });
  3181.  
  3182. const data = await response.json();
  3183. return data.data || [];
  3184. }
  3185.  
  3186. async function rebuildServerList(gameId, totalLimit, best_connection) {
  3187. const serverListContainer = document.getElementById("rbx-public-game-server-item-container");
  3188.  
  3189. // If "Best Connection" is enabled
  3190. // FUNCTION FOR THE 6TH BUTTON!
  3191. if (best_connection === true) {
  3192. disableLoadMoreButton(true);
  3193. disableFilterButton(true);
  3194. notifications("Retrieving Location...", "success", "🌎", '5000')
  3195. // Ask for the user's location
  3196. const userLocation = await getUserLocation();
  3197. if (!userLocation) {
  3198. notifications('Error: Unable to fetch your location. Please enable location access.', 'error', '⚠️', '5000');
  3199. disableFilterButton(false);
  3200. return;
  3201. }
  3202.  
  3203. // Fetch 50 servers
  3204. const servers = await fetchPublicServers(gameId, 50);
  3205. if (servers.length === 0) {
  3206. notifications('Error: No servers found. Please try again later.', 'error', '⚠️', '5000');
  3207. disableFilterButton(false);
  3208. return;
  3209. }
  3210.  
  3211. // Calculate distances and find the closest server
  3212. let closestServer = null;
  3213. let minDistance = Infinity;
  3214. let closestServerLocation = null;
  3215.  
  3216. for (const server of servers) {
  3217. const {
  3218. id: serverId,
  3219. maxPlayers,
  3220. playing
  3221. } = server;
  3222.  
  3223. // Skip full servers
  3224. if (playing >= maxPlayers) {
  3225. continue;
  3226. }
  3227.  
  3228. try {
  3229. // Fetch server location
  3230. const location = await fetchServerDetails(gameId, serverId);
  3231.  
  3232. // Calculate distance
  3233. const distance = calculateDistance(
  3234. userLocation.latitude,
  3235. userLocation.longitude,
  3236. location.latitude,
  3237. location.longitude
  3238. );
  3239.  
  3240. // Update closest server
  3241. if (distance < minDistance) {
  3242. minDistance = distance;
  3243. closestServer = server;
  3244. closestServerLocation = location;
  3245. }
  3246. } catch (error) {
  3247. ConsoleLogEnabled(`Error fetching details for server ${serverId}:`, error);
  3248. // Skip this server and continue with the next one
  3249. continue;
  3250. }
  3251. }
  3252.  
  3253. if (closestServer) {
  3254. // Automatically join the closest server
  3255. showLoadingOverlay();
  3256. Roblox.GameLauncher.joinGameInstance(gameId, closestServer.id);
  3257. notifications(`Joining nearest server!
  3258. Server ID: ${closestServer.id}
  3259. Distance: ${(minDistance / 1.609).toFixed(2)} miles | ${minDistance.toFixed(2)} km
  3260. Location (Country): ${closestServerLocation.country.name}.`, 'success', '🚀', '5000');
  3261.  
  3262. disableFilterButton(false);
  3263. Loadingbar(false);
  3264. } else {
  3265. notifications('No valid servers found. Please try again later after refreshing the webpage. Filter button disabled.', 'error', '⚠️', '8000');
  3266. Loadingbar(false);
  3267. }
  3268.  
  3269. return; // Exit the function after joining the best server
  3270. }
  3271.  
  3272. // Rest of the original function (for non-"Best Connection" mode)
  3273. if (!serverListContainer) {
  3274. ConsoleLogEnabled("Server list container not found!");
  3275. notifications('Error: No Servers found. There is nobody playing this game. Please refresh the page.', 'error', '⚠️', '8000');
  3276. return;
  3277. }
  3278.  
  3279. const messageElement = showMessage("Just a moment, calculating the distance from servers..");
  3280.  
  3281. try {
  3282. // Retrieve user's location for distance calculations
  3283. const userLocation = await getUserLocation();
  3284. if (!userLocation) {
  3285. notifications('Error: Unable to fetch your location. Please enable location access.', 'error', '⚠️', '5000');
  3286. disableFilterButton(false);
  3287. return;
  3288. }
  3289.  
  3290. const servers = await fetchPublicServers(gameId, totalLimit);
  3291. const totalServers = servers.length;
  3292. let skippedServers = 0;
  3293.  
  3294. messageElement.textContent = `Filtering servers, please do not leave this page as it slows down the search...\n${totalServers} servers found, 0 servers loaded.`;
  3295. notifications(`Please do not leave this page as it slows down the search. \nFound a total of ${totalServers} servers.`, 'success', '👍', '8000');
  3296.  
  3297. const serverDetails = [];
  3298. for (let i = 0; i < servers.length; i++) {
  3299. const server = servers[i];
  3300. const {
  3301. id: serverId,
  3302. maxPlayers,
  3303. playing,
  3304. ping,
  3305. fps,
  3306. playerTokens
  3307. } = server;
  3308.  
  3309. let location;
  3310. try {
  3311. location = await fetchServerDetails(gameId, serverId);
  3312. } catch (error) {
  3313. if (error === 'purchase_required') {
  3314. messageElement.textContent = "Error: Cannot access server regions because you have not purchased the game.";
  3315. notifications('Cannot access server regions because you have not purchased the game.', 'error', '⚠️', '15000');
  3316. Loadingbar(false); // disable loading bar
  3317. return;
  3318. } else if (error === 'subplace_join_restriction') {
  3319. messageElement.textContent = "This game requires users to teleport to a subplace. As a result, server regions cannot be retrieved.";
  3320. notifications('Error: This game requires users to teleport to a subplace. As a result, server regions cannot be retrieved.', 'error', '⚠️', '15000');
  3321. Loadingbar(false); // disable loading bar
  3322. return;
  3323. } else {
  3324. ConsoleLogEnabled(error);
  3325. location = {
  3326. city: "Unknown",
  3327. country: {
  3328. name: "Unknown",
  3329. code: "??"
  3330. }
  3331. };
  3332. }
  3333. }
  3334.  
  3335.  
  3336. if (location.city === "Unknown" || playing >= maxPlayers) {
  3337. ConsoleLogEnabled(`Skipping server ${serverId} because it is full or location is unknown.`);
  3338. skippedServers++;
  3339. continue;
  3340. }
  3341.  
  3342. // Fetch player thumbnails
  3343. const playerThumbnails = playerTokens && playerTokens.length > 0 ? await fetchPlayerThumbnails_servers(playerTokens) : [];
  3344.  
  3345. serverDetails.push({
  3346. server,
  3347. location,
  3348. playerThumbnails
  3349. });
  3350.  
  3351. messageElement.textContent = `Filtering servers, please do not leave this page...\n${totalServers} servers found, ${i + 1} server locations found`;
  3352. }
  3353.  
  3354. if (serverDetails.length === 0) {
  3355. messageElement.textContent = "No servers found. Please try again with an increase in the number of servers to search for.";
  3356. notifications('Error: No servers found. Please try again with an increase in the number of servers to search for.', 'error', '⚠️', '5000');
  3357. Loadingbar(false); // disable loading bar
  3358. return;
  3359. }
  3360.  
  3361. const loadedServers = totalServers - skippedServers;
  3362. notifications(`Filtering complete!\n${totalServers} servers found, ${loadedServers} servers loaded, ${skippedServers} servers skipped (full).`, 'success', '👍', '5000');
  3363. messageElement.textContent = `Filtering complete!\n${totalServers} servers found, ${loadedServers} servers loaded, ${skippedServers} servers skipped (full).`;
  3364. Loadingbar(false); // disable loading bar
  3365.  
  3366. // Add filter dropdowns
  3367. const filterContainer = createFilterDropdowns(serverDetails);
  3368. serverListContainer.parentNode.insertBefore(filterContainer, serverListContainer);
  3369.  
  3370. // Style the server list container to use a grid layout
  3371. serverListContainer.style.display = "grid";
  3372. serverListContainer.style.gridTemplateColumns = "repeat(4, 1fr)"; // 4 columns
  3373. serverListContainer.style.gap = "16px"; // Gap between cards
  3374.  
  3375. const displayFilteredServers = (country, city) => {
  3376. serverListContainer.innerHTML = "";
  3377.  
  3378. const filteredServers = filterServers(serverDetails, country, city);
  3379. // Sort servers by distance from the user (fastest first)
  3380. const sortedServers = filteredServers.sort((a, b) => {
  3381. const distanceA = calculateDistance(userLocation.latitude, userLocation.longitude, a.location.latitude, a.location.longitude);
  3382. const distanceB = calculateDistance(userLocation.latitude, userLocation.longitude, b.location.latitude, b.location.longitude);
  3383. return distanceA - distanceB;
  3384. });
  3385.  
  3386. sortedServers.forEach(({
  3387. server,
  3388. location,
  3389. playerThumbnails
  3390. }) => {
  3391. const serverCard = document.createElement("li");
  3392. serverCard.className = "rbx-game-server-item col-md-3 col-sm-4 col-xs-6";
  3393. serverCard.style.width = "100%";
  3394. serverCard.style.minHeight = "400px";
  3395. serverCard.style.display = "flex";
  3396. serverCard.style.flexDirection = "column";
  3397. serverCard.style.justifyContent = "space-between";
  3398. serverCard.style.boxSizing = "border-box";
  3399. serverCard.style.outline = 'none';
  3400. serverCard.style.padding = '6px';
  3401. serverCard.style.borderRadius = '8px';
  3402.  
  3403. // Create label (now based on distance)
  3404. const pingLabel = document.createElement("div");
  3405. pingLabel.style.marginBottom = "5px"; // Adds spacing above label
  3406. pingLabel.style.padding = "5px 10px";
  3407. pingLabel.style.borderRadius = "8px";
  3408. pingLabel.style.fontWeight = "bold";
  3409. pingLabel.style.textAlign = "center"; // Centers the label
  3410.  
  3411. // Calculate distance from user to server
  3412. const distance = calculateDistance(
  3413. userLocation.latitude,
  3414. userLocation.longitude,
  3415. location.latitude,
  3416. location.longitude
  3417. );
  3418.  
  3419. // Calculate ping using the advanced formula
  3420. const calculatedPing = 2 * (distance / 180000) * 1000 * 1.8 + (20 + 0.04 * distance);
  3421.  
  3422. if (distance < 1250) {
  3423. pingLabel.textContent = "⚡ Fast";
  3424. pingLabel.style.backgroundColor = "#014737";
  3425. pingLabel.style.color = "#73e1bc";
  3426. } else if (distance < 5000) {
  3427. pingLabel.textContent = "⏳ OK";
  3428. pingLabel.style.backgroundColor = "#c75a00";
  3429. pingLabel.style.color = "#ffe8c2";
  3430. } else {
  3431. pingLabel.textContent = "🐌 Slow";
  3432. pingLabel.style.backgroundColor = "#771d1d";
  3433. pingLabel.style.color = "#fcc468";
  3434. }
  3435.  
  3436. // Create a container for player thumbnails
  3437. const thumbnailsContainer = document.createElement("div");
  3438. thumbnailsContainer.className = "player-thumbnails-container";
  3439. thumbnailsContainer.style.display = "grid";
  3440. thumbnailsContainer.style.gridTemplateColumns = "repeat(3, 60px)";
  3441. thumbnailsContainer.style.gridTemplateRows = "repeat(2, 60px)";
  3442. thumbnailsContainer.style.gap = "5px";
  3443. thumbnailsContainer.style.marginBottom = "10px";
  3444.  
  3445. // Add player thumbnails to the container (max 5)
  3446. const maxThumbnails = 5;
  3447. const displayedThumbnails = playerThumbnails.slice(0, maxThumbnails);
  3448. displayedThumbnails.forEach(thumb => {
  3449. if (thumb && thumb.imageUrl) {
  3450. const img = document.createElement("img");
  3451. img.src = thumb.imageUrl;
  3452. img.className = "avatar-card-image";
  3453. img.style.width = "60px";
  3454. img.style.height = "60px";
  3455. img.style.borderRadius = "50%";
  3456. thumbnailsContainer.appendChild(img);
  3457. }
  3458. });
  3459.  
  3460. // Add a placeholder for hidden players
  3461. const hiddenPlayers = server.playing - displayedThumbnails.length;
  3462. if (hiddenPlayers > 0) {
  3463. const placeholder = document.createElement("div");
  3464. placeholder.className = "avatar-card-image";
  3465. placeholder.style.width = "60px";
  3466. placeholder.style.height = "60px";
  3467. placeholder.style.borderRadius = "50%";
  3468. placeholder.style.backgroundColor = "#6a6f81";
  3469. placeholder.style.display = "flex";
  3470. placeholder.style.alignItems = "center";
  3471. placeholder.style.justifyContent = "center";
  3472. placeholder.style.color = "#fff";
  3473. placeholder.style.fontSize = "14px";
  3474. placeholder.textContent = `+${hiddenPlayers}`;
  3475. thumbnailsContainer.appendChild(placeholder);
  3476. }
  3477.  
  3478. // Server card content with both distance and calculated ping, with line spacers between each info
  3479. const cardItem = document.createElement("div");
  3480. cardItem.className = "card-item";
  3481. cardItem.style.display = "flex";
  3482. cardItem.style.flexDirection = "column";
  3483. cardItem.style.justifyContent = "space-between";
  3484. cardItem.style.height = "100%";
  3485.  
  3486. cardItem.innerHTML = `
  3487. ${thumbnailsContainer.outerHTML}
  3488. <div class="rbx-game-server-details game-server-details">
  3489. <div class="text-info rbx-game-status rbx-game-server-status text-overflow">
  3490. ${server.playing} of ${server.maxPlayers} people max
  3491. </div>
  3492. <div class="server-player-count-gauge border">
  3493. <div class="gauge-inner-bar border" style="width: ${(server.playing / server.maxPlayers) * 100}%;"></div>
  3494. </div>
  3495. <span data-placeid="${gameId}">
  3496. <button type="button" class="btn-full-width btn-control-xs rbx-game-server-join game-server-join-btn btn-primary-md btn-min-width">Join</button>
  3497. </span>
  3498. </div>
  3499. <div style="margin-top: 10px; text-align: center;">
  3500. ${pingLabel.outerHTML}
  3501. <div class="info-lines" style="margin-top: 8px;">
  3502. <div class="ping-info">Ping: ${calculatedPing.toFixed(2)} ms</div>
  3503. <hr style="margin: 6px 0;">
  3504. <div class="ping-info">Distance: ${distance.toFixed(2)} km</div>
  3505. <hr style="margin: 6px 0;">
  3506. <div class="location-info">${location.city}, ${location.country.name}</div>
  3507. <hr style="margin: 6px 0;">
  3508. <div class="fps-info">FPS: ${Math.round(server.fps)}</div>
  3509. </div>
  3510. </div>
  3511. `;
  3512.  
  3513. const joinButton = cardItem.querySelector(".rbx-game-server-join");
  3514. joinButton.addEventListener("click", () => {
  3515. ConsoleLogEnabled(`Roblox.GameLauncher.joinGameInstance(${gameId}, "${server.id}")`);
  3516. showLoadingOverlay();
  3517. Roblox.GameLauncher.joinGameInstance(gameId, server.id);
  3518. });
  3519.  
  3520. const container = adjustJoinButtonContainer(joinButton);
  3521. const inviteButton = createInviteButton(gameId, server.id);
  3522. container.appendChild(inviteButton);
  3523.  
  3524. serverCard.appendChild(cardItem);
  3525. serverListContainer.appendChild(serverCard);
  3526. });
  3527. };
  3528.  
  3529. // Add event listeners to dropdowns
  3530. const countryFilter = document.getElementById('countryFilter');
  3531. const cityFilter = document.getElementById('cityFilter');
  3532.  
  3533. countryFilter.addEventListener('change', () => {
  3534. displayFilteredServers(countryFilter.value, cityFilter.value);
  3535. });
  3536.  
  3537. cityFilter.addEventListener('change', () => {
  3538. displayFilteredServers(countryFilter.value, cityFilter.value);
  3539. });
  3540.  
  3541. // Display all servers initially
  3542. displayFilteredServers("", "");
  3543.  
  3544. setTimeout(() => {
  3545. hideMessage();
  3546. }, 3000);
  3547. } catch (error) {
  3548. ConsoleLogEnabled("Error rebuilding server list:", error);
  3549. notifications('Filtering error. Try again or refresh to enable the filter button.', 'error', '⚠️ ', '8000');
  3550. messageElement.textContent = "An error occurred while filtering servers. Please try again.";
  3551. Loadingbar(false); // enable loading bar
  3552. } finally {
  3553. Loadingbar(false); // omg bruh i just realzed i could put this here but now im too lazy to thorugh the code to remove all of the loading bar disabl functions
  3554. disableFilterButton(false); // beta test filter button
  3555. //notifications('Note: The filter button is disabled while using server regions. Refresh to enable it.', 'info', '🔄', '15000')
  3556. }
  3557. }
  3558.  
  3559.  
  3560.  
  3561.  
  3562. // Function to extract the game ID from the URL
  3563. function extractGameId() {
  3564. const url = window.location.href;
  3565. const match = url.match(/roblox\.com\/games\/(\d+)/);
  3566.  
  3567. if (match && match[1]) {
  3568. return match[1]; // Return the game ID
  3569. }
  3570. return null; // Return null if no game ID is found
  3571. }
  3572.  
  3573. // Log the game ID to the console
  3574. const gameId = extractGameId();
  3575.  
  3576. // Function to create and append the Invite button
  3577. function createInviteButton(placeId, serverId) { // too lazy to comment this function tbh just ready the name
  3578. const inviteButton = document.createElement('button');
  3579. inviteButton.textContent = 'Invite';
  3580. inviteButton.className = 'btn-control-xs btn-primary-md btn-min-width btn-full-width';
  3581. inviteButton.style.width = '25%';
  3582. inviteButton.style.marginLeft = '5px';
  3583.  
  3584. inviteButton.style.padding = '4px 8px';
  3585. inviteButton.style.fontSize = '12px';
  3586. inviteButton.style.borderRadius = '8px';
  3587. inviteButton.style.backgroundColor = '#3b3e49';
  3588. inviteButton.style.borderColor = '#3b3e49';
  3589. inviteButton.style.color = '#ffffff';
  3590. inviteButton.style.cursor = 'pointer';
  3591. inviteButton.style.fontWeight = '500';
  3592. inviteButton.style.textAlign = 'center';
  3593. inviteButton.style.whiteSpace = 'nowrap';
  3594. inviteButton.style.verticalAlign = 'middle';
  3595. inviteButton.style.lineHeight = '100%';
  3596. inviteButton.style.fontFamily = 'Builder Sans, Helvetica Neue, Helvetica, Arial, Lucida Grande, sans-serif';
  3597. inviteButton.style.textRendering = 'auto';
  3598. inviteButton.style.webkitFontSmoothing = 'antialiased';
  3599. inviteButton.style.mozOsxFontSmoothing = 'grayscale';
  3600.  
  3601. inviteButton.addEventListener('click', () => {
  3602. const inviteLink = `https://oqarshi.github.io/Invite/?placeid=${placeId}&serverid=${serverId}`;
  3603. navigator.clipboard.writeText(inviteLink).then(() => {
  3604. ConsoleLogEnabled(`Invite link copied to clipboard: ${inviteLink}`);
  3605. notifications('Success! Invite link copied to clipboard!', 'success', '🎉', ' 2000');
  3606. }).catch(() => {
  3607. ConsoleLogEnabled('Failed to copy invite link.');
  3608. notifications('Error: Failed to copy invite link', 'error', '😔', '2000');
  3609. });
  3610. });
  3611.  
  3612. return inviteButton;
  3613. }
  3614.  
  3615. // Function to adjust the Join button and its container
  3616. function adjustJoinButtonContainer(joinButton) {
  3617. const container = document.createElement('div');
  3618. container.style.display = 'flex';
  3619. container.style.width = '100%';
  3620.  
  3621. joinButton.style.width = '75%';
  3622.  
  3623. joinButton.parentNode.insertBefore(container, joinButton);
  3624. container.appendChild(joinButton);
  3625.  
  3626. return container;
  3627. }
  3628.  
  3629.  
  3630.  
  3631.  
  3632. /*********************************************************************************************************************************************************************************************************************************************
  3633. Functions for the 6th button.
  3634.  
  3635. *********************************************************************************************************************************************************************************************************************************************/
  3636.  
  3637.  
  3638.  
  3639.  
  3640. function calculateDistance(lat1, lon1, lat2, lon2) {
  3641. const R = 6371; // Radius of the Earth in kilometers
  3642. const dLat = (lat2 - lat1) * (Math.PI / 180);
  3643. const dLon = (lon2 - lon1) * (Math.PI / 180);
  3644. const a =
  3645. Math.sin(dLat / 2) * Math.sin(dLat / 2) +
  3646. Math.cos(lat1 * (Math.PI / 180)) * Math.cos(lat2 * (Math.PI / 180)) *
  3647. Math.sin(dLon / 2) * Math.sin(dLon / 2);
  3648. const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  3649. return R * c; // Distance in kilometers
  3650. }
  3651.  
  3652. function getUserLocation() {
  3653. return new Promise((resolve, reject) => {
  3654. if (!navigator.geolocation) {
  3655. ConsoleLogEnabled("Geolocation is not supported by this browser.");
  3656. return fetchFallbackLocation(resolve);
  3657. }
  3658.  
  3659. // Step 1: Try Standard Geolocation
  3660. navigator.geolocation.getCurrentPosition(
  3661. (position) => resolveSuccess(position, resolve),
  3662. async (error) => {
  3663. ConsoleLogEnabled("Geolocation error:", error);
  3664.  
  3665. // Step 2: Check Permission Status (if supported)
  3666. if (navigator.permissions) {
  3667. try {
  3668. const permissionStatus = await navigator.permissions.query({
  3669. name: "geolocation"
  3670. });
  3671. ConsoleLogEnabled("Geolocation permission status:", permissionStatus.state);
  3672.  
  3673. if (permissionStatus.state === "denied") {
  3674. ConsoleLogEnabled("User denied location access.");
  3675. return fetchFallbackLocation(resolve);
  3676. }
  3677. } catch (permError) {
  3678. ConsoleLogEnabled("Error checking permission status:", permError);
  3679. }
  3680. }
  3681.  
  3682. // Step 3: Try Alternative Geolocation API (If Available)
  3683. try {
  3684. navigator.geolocation.getCurrentPosition(
  3685. (position) => resolveSuccess(position, resolve), // ✅ Corrected to use resolveSuccess
  3686. () => fetchFallbackLocation(resolve), {
  3687. maximumAge: 5000
  3688. }
  3689. );
  3690. return;
  3691. } catch (altError) {
  3692. ConsoleLogEnabled("Alternative Geolocation API failed:", altError);
  3693. }
  3694.  
  3695. // Step 4: If all else fails, fallback to server
  3696. fetchFallbackLocation(resolve);
  3697. }, {
  3698. timeout: 10000,
  3699. maximumAge: 0,
  3700. }
  3701. );
  3702. });
  3703. }
  3704.  
  3705. // 🎯 Success Handler
  3706. function resolveSuccess(position, resolve) {
  3707. notifications("We successfully detected your location.", "success", "🌎", "5000");
  3708. disableLoadMoreButton(true);
  3709. disableFilterButton(true);
  3710. Loadingbar(true);
  3711. resolve({
  3712. latitude: position.coords.latitude,
  3713. longitude: position.coords.longitude,
  3714. });
  3715. }
  3716.  
  3717. // 🌍 Fetches fallback location from a callback server
  3718. function fetchFallbackLocation(resolve) {
  3719. ConsoleLogEnabled("Fetching location from the fallback server...");
  3720.  
  3721. fetch("https://ipapi.co/json/") // Example fallback API
  3722. .then((response) => response.json())
  3723. .then((data) => {
  3724. ConsoleLogEnabled("Fallback location received:", data);
  3725. notifications("Using approximate location from IP address.", "info", "📍", "5000");
  3726.  
  3727. resolve({
  3728. latitude: data.latitude || 40.7128, // Default to New York if API fails
  3729. longitude: data.longitude || -74.0060,
  3730. });
  3731. })
  3732. .catch((error) => {
  3733. ConsoleLogEnabled("Error fetching fallback location:", error);
  3734. notifications("Could not determine location. Please report and issue on Greasyfork. Assuming New York.", "error", "⚠️", "15000");
  3735.  
  3736. resolve({
  3737. latitude: 40.7128,
  3738. longitude: -74.0060,
  3739. });
  3740. });
  3741. }
  3742.  
  3743.  
  3744.  
  3745.  
  3746. /*********************************************************************************************************************************************************************************************************************************************
  3747. Functions for the 7th button.
  3748.  
  3749. *********************************************************************************************************************************************************************************************************************************************/
  3750. async function auto_join_small_server() {
  3751. // Disable the "Load More" button and show the loading bar
  3752. Loadingbar(true);
  3753. disableFilterButton(true);
  3754. disableLoadMoreButton();
  3755.  
  3756. // Get the game ID from the URL
  3757. const gameId = window.location.pathname.split('/')[2];
  3758.  
  3759. // Retry mechanism for 429 errors
  3760. let retries = 3; // Number of retries
  3761. let success = false;
  3762.  
  3763. while (retries > 0 && !success) {
  3764. try {
  3765. // Fetch server data using GM_xmlhttpRequest
  3766. const data = await new Promise((resolve, reject) => {
  3767. GM_xmlhttpRequest({
  3768. method: "GET",
  3769. url: `https://games.roblox.com/v1/games/${gameId}/servers/public?sortOrder=1&excludeFullGames=true&limit=100`,
  3770. onload: function(response) {
  3771. if (response.status === 429) {
  3772. reject('429: Too Many Requests');
  3773. } else if (response.status >= 200 && response.status < 300) {
  3774. resolve(JSON.parse(response.responseText));
  3775. } else {
  3776. reject(`HTTP error: ${response.status}`);
  3777. }
  3778. },
  3779. onerror: function(error) {
  3780. reject(error);
  3781. },
  3782. });
  3783. });
  3784.  
  3785. // Find the server with the lowest player count
  3786. let minPlayers = Infinity;
  3787. let targetServer = null;
  3788.  
  3789. for (const server of data.data) {
  3790. if (server.playing < minPlayers) {
  3791. minPlayers = server.playing;
  3792. targetServer = server;
  3793. }
  3794. }
  3795.  
  3796. if (targetServer) {
  3797. // Join the server with the lowest player count
  3798. showLoadingOverlay();
  3799. Roblox.GameLauncher.joinGameInstance(gameId, targetServer.id);
  3800. notifications(`Joining a server with ${targetServer.playing} player(s).`, 'success', '🚀');
  3801. success = true; // Mark as successful
  3802. } else {
  3803. notifications('No available servers found.', 'error', '⚠️');
  3804. break; // Exit the loop if no servers are found
  3805. }
  3806. } catch (error) {
  3807. if (error === '429: Too Many Requests' && retries > 0) {
  3808. ConsoleLogEnabled('Rate limited. Retrying in 10 seconds...');
  3809. notifications('Rate limited. Retrying in 10 seconds...', 'warning', '⏳', '10000');
  3810. await delay(10000); // Wait 10 seconds before retrying
  3811. retries--;
  3812. } else {
  3813. ConsoleLogEnabled('Error fetching server data:', error);
  3814. notifications('Error: Failed to fetch server data. Please try again later.', 'error', '⚠️', '5000');
  3815. break; // Exit the loop if it's not a 429 error or no retries left
  3816. }
  3817. }
  3818. }
  3819.  
  3820. // Hide the loading bar and enable the filter button
  3821. Loadingbar(false);
  3822. disableFilterButton(false);
  3823. }
  3824. /*********************************************************************************************************************************************************************************************************************************************
  3825. Functions for the 8th button.
  3826.  
  3827. *********************************************************************************************************************************************************************************************************************************************/
  3828.  
  3829. function find_user_server_tab() {
  3830. // Create the overlay (backdrop)
  3831. const overlay = document.createElement('div');
  3832. overlay.style.cssText = `
  3833. position: fixed;
  3834. top: 0;
  3835. left: 0;
  3836. width: 100%;
  3837. height: 100%;
  3838. background-color: rgba(0, 0, 0, 0.5);
  3839. z-index: 9999;
  3840. opacity: 0;
  3841. transition: opacity 0.3s ease;
  3842. `;
  3843. document.body.appendChild(overlay);
  3844.  
  3845. // Create the popup container
  3846. const popup = document.createElement('div');
  3847. popup.className = 'player-count-popup';
  3848. popup.style.cssText = `
  3849. position: fixed;
  3850. top: 50%;
  3851. left: 50%;
  3852. transform: translate(-50%, -50%);
  3853. background-color: rgb(30, 32, 34);
  3854. padding: 20px;
  3855. border-radius: 10px;
  3856. z-index: 10000;
  3857. box-shadow: 0 0 15px rgba(0, 0, 0, 0.7);
  3858. display: flex;
  3859. flex-direction: column;
  3860. align-items: center;
  3861. gap: 15px;
  3862. width: 300px;
  3863. opacity: 0;
  3864. transition: opacity 0.3s ease, transform 0.3s ease;
  3865. `;
  3866.  
  3867. // Add a close button in the top-right corner (bigger size)
  3868. const closeButton = document.createElement('button');
  3869. closeButton.innerHTML = '&times;'; // Using '×' for the close icon
  3870. closeButton.style.cssText = `
  3871. position: absolute;
  3872. top: 10px;
  3873. right: 10px;
  3874. background: transparent;
  3875. border: none;
  3876. color: #ffffff;
  3877. font-size: 24px; /* Increased font size */
  3878. cursor: pointer;
  3879. width: 36px; /* Increased size */
  3880. height: 36px; /* Increased size */
  3881. border-radius: 50%;
  3882. display: flex;
  3883. align-items: center;
  3884. justify-content: center;
  3885. transition: background-color 0.3s ease, color 0.3s ease;
  3886. `;
  3887. closeButton.addEventListener('mouseenter', () => {
  3888. closeButton.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
  3889. closeButton.style.color = '#ff4444';
  3890. });
  3891. closeButton.addEventListener('mouseleave', () => {
  3892. closeButton.style.backgroundColor = 'transparent';
  3893. closeButton.style.color = '#ffffff';
  3894. });
  3895.  
  3896. // Add a title
  3897. const title = document.createElement('h3');
  3898. title.textContent = 'Enter Username';
  3899. title.style.cssText = `
  3900. color: white;
  3901. margin: 0;
  3902. font-size: 18px;
  3903. font-weight: 500;
  3904. `;
  3905. popup.appendChild(title);
  3906.  
  3907. // Add an input box for the username
  3908. const usernameInput = document.createElement('input');
  3909. usernameInput.type = 'text';
  3910. usernameInput.placeholder = 'Username, not display';
  3911. usernameInput.style.cssText = `
  3912. width: 80%;
  3913. padding: 8px;
  3914. font-size: 16px;
  3915. border: 1px solid #444;
  3916. border-radius: 5px;
  3917. background-color: #1a1a1a;
  3918. color: white;
  3919. outline: none;
  3920. `;
  3921. popup.appendChild(usernameInput);
  3922.  
  3923. // Add a confirm button with dark, blackish style
  3924. const confirmButton = document.createElement('button');
  3925. confirmButton.textContent = 'Confirm';
  3926. confirmButton.style.cssText = `
  3927. padding: 8px 20px;
  3928. font-size: 16px;
  3929. background-color: #1a1a1a; /* Dark blackish color */
  3930. color: white;
  3931. border: none;
  3932. border-radius: 5px;
  3933. cursor: pointer;
  3934. transition: background-color 0.3s ease, transform 0.2s ease;
  3935. `;
  3936. confirmButton.addEventListener('mouseenter', () => {
  3937. confirmButton.style.backgroundColor = '#333'; /* Slightly lighter on hover */
  3938. confirmButton.style.transform = 'scale(1.05)';
  3939. });
  3940. confirmButton.addEventListener('mouseleave', () => {
  3941. confirmButton.style.backgroundColor = '#1a1a1a';
  3942. confirmButton.style.transform = 'scale(1)';
  3943. });
  3944.  
  3945. // Handle confirm button click
  3946. confirmButton.addEventListener('click', () => {
  3947. const username = usernameInput.value.trim();
  3948. if (username) {
  3949. // Check if the username is between 3 and 20 characters
  3950. if (username.length >= 3 && username.length <= 20) {
  3951. // Call the function with the username
  3952. FindPlayerGameServer(username);
  3953. notifications("Searching for the user's server...", "info", "🧐", "5000");
  3954. fadeOutAndRemove_7th(popup, overlay);
  3955. } else {
  3956. // Show an error notification if the username is not within the required length
  3957. notifications('Error: Username must be between 3 and 20 characters', 'error', '⚠️');
  3958. }
  3959. } else {
  3960. notifications('Error: Please enter a username', 'error', '⚠️', '2500');
  3961. }
  3962. });
  3963.  
  3964. // Append the popup to the body
  3965. document.body.appendChild(popup);
  3966.  
  3967. // Fade in the overlay and popup
  3968. setTimeout(() => {
  3969. overlay.style.opacity = '1';
  3970. popup.style.opacity = '1';
  3971. popup.style.transform = 'translate(-50%, -50%) scale(1)';
  3972. }, 10);
  3973.  
  3974. /*******************************************************
  3975. name of function: fadeOutAndRemove_7th
  3976. description: Fades out and removes the popup and overlay.
  3977. *******************************************************/
  3978. function fadeOutAndRemove_7th(popup, overlay) {
  3979. popup.style.opacity = '0';
  3980. popup.style.transform = 'translate(-50%, -50%) scale(0.9)';
  3981. overlay.style.opacity = '0';
  3982. setTimeout(() => {
  3983. popup.remove();
  3984. overlay.remove();
  3985. }, 300); // Match the duration of the transition
  3986. }
  3987.  
  3988. // Close the popup when clicking outside
  3989. overlay.addEventListener('click', () => {
  3990. fadeOutAndRemove_7th(popup, overlay);
  3991. });
  3992.  
  3993. // Close the popup when the close button is clicked
  3994. closeButton.addEventListener('click', () => {
  3995. fadeOutAndRemove_7th(popup, overlay);
  3996. });
  3997.  
  3998. popup.appendChild(confirmButton);
  3999. popup.appendChild(closeButton);
  4000. }
  4001.  
  4002.  
  4003. async function FindPlayerGameServer(playerName) {
  4004. disableLoadMoreButton();
  4005. Loadingbar(true);
  4006. disableFilterButton(true);
  4007. const wait = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds));
  4008.  
  4009. const fetchData = async (url, options = {}) => {
  4010. if (!options.headers) options.headers = {};
  4011. return fetch(url, options)
  4012. .then(response => response.json())
  4013. .catch(error => ConsoleLogEnabled("Fetch error:", error));
  4014. };
  4015.  
  4016. const fetchDataGM = (url, options = {}) => {
  4017. return new Promise((resolve, reject) => {
  4018. GM_xmlhttpRequest({
  4019. method: options.method || 'GET',
  4020. url: url,
  4021. headers: options.headers || {},
  4022. anonymous: true, // Prevents sending cookies
  4023. nocache: true, // Prevents caching
  4024. onload: function(response) {
  4025. try {
  4026. const parsedData = JSON.parse(response.responseText);
  4027. resolve(parsedData);
  4028. } catch (error) {
  4029. ConsoleLogEnabled("JSON parsing error:", error);
  4030. reject(error);
  4031. }
  4032. },
  4033. onerror: function(error) {
  4034. ConsoleLogEnabled("Request error:", error);
  4035. reject(error);
  4036. }
  4037. });
  4038. });
  4039. };
  4040.  
  4041. ConsoleLogEnabled(`Initiating search for player: ${playerName}`);
  4042.  
  4043. const gameId = window.location.href.split("/")[4];
  4044. ConsoleLogEnabled(`Game ID identified: ${gameId}`);
  4045.  
  4046. let userId;
  4047.  
  4048. try {
  4049. ConsoleLogEnabled(`Retrieving user ID for player: ${playerName}`);
  4050. const userProfile = await fetch(`https://www.roblox.com/users/profile?username=${playerName}`);
  4051. if (!userProfile.ok) {
  4052. notifications("Error: User does not exist on Roblox!", "error", "⚠️", "2500");
  4053. Loadingbar(false);
  4054. disableFilterButton(false);
  4055. throw `Player "${playerName}" not found`;
  4056. }
  4057. userId = userProfile.url.match(/\d+/)[0];
  4058. ConsoleLogEnabled(`User ID retrieved: ${userId}`);
  4059. } catch (error) {
  4060. ConsoleLogEnabled("Error:", error);
  4061. return `Error: ${error}`;
  4062. }
  4063.  
  4064. ConsoleLogEnabled(`Fetching avatar thumbnail for user ID: ${userId}`);
  4065. const avatarThumbnail = (await fetchData(`https://thumbnails.roblox.com/v1/users/avatar-headshot?userIds=${userId}&format=Png&size=150x150`)).data[0].imageUrl;
  4066. ConsoleLogEnabled(`Avatar thumbnail URL: ${avatarThumbnail}`);
  4067.  
  4068. let pageCursor = null;
  4069. let playerFound = false;
  4070. let totalServersChecked = 0;
  4071. let startTime = Date.now();
  4072.  
  4073. // Show the search progress popup with the player's thumbnail
  4074. const progressPopup = showSearchProgressPopup(avatarThumbnail);
  4075.  
  4076. while (true) {
  4077. let apiUrl = `https://games.roblox.com/v1/games/${gameId}/servers/0?limit=100`;
  4078. if (pageCursor) apiUrl += "&cursor=" + pageCursor;
  4079.  
  4080. ConsoleLogEnabled(`Accessing servers with URL: ${apiUrl}`);
  4081. const serverList = await fetchDataGM(apiUrl, {
  4082. credentials: "omit"
  4083. }).catch(() => null);
  4084.  
  4085. if (serverList && serverList.data) {
  4086. ConsoleLogEnabled(`Discovered ${serverList.data.length} servers in this set.`);
  4087. for (let index = 0; index < serverList.data.length; index++) {
  4088. const server = serverList.data[index];
  4089. if (!playerFound) {
  4090. totalServersChecked++;
  4091.  
  4092. // Calculate time elapsed
  4093. const timeElapsed = Math.floor((Date.now() - startTime) / 1000);
  4094.  
  4095. // Update the progress popup
  4096. updateSearchProgressPopup(
  4097. progressPopup,
  4098. totalServersChecked,
  4099. timeElapsed
  4100. );
  4101.  
  4102. if (server.playerTokens.length === 0) {
  4103. ConsoleLogEnabled(`Server ${index + 1} is empty. Proceeding to next server.`);
  4104. continue;
  4105. }
  4106.  
  4107. ConsoleLogEnabled(`Inspecting server ${index + 1} hosting ${server.playing} players...`);
  4108.  
  4109. let thumbnailData;
  4110. while (true) {
  4111. let requestBody = [];
  4112.  
  4113. const generateRequestEntry = (playerToken) => ({
  4114. requestId: `0:${playerToken}:AvatarHeadshot:150x150:png:regular`,
  4115. type: "AvatarHeadShot",
  4116. targetId: 0,
  4117. token: playerToken,
  4118. format: "png",
  4119. size: "150x150"
  4120. });
  4121.  
  4122. server.playerTokens.forEach(token => {
  4123. requestBody.push(generateRequestEntry(token));
  4124. });
  4125.  
  4126. try {
  4127. ConsoleLogEnabled(`Fetching thumbnails for ${server.playerTokens.length} player(s)...`);
  4128. thumbnailData = await fetchData("https://thumbnails.roblox.com/v1/batch", {
  4129. method: "POST",
  4130. headers: {
  4131. "Content-Type": "application/json",
  4132. Accept: "application/json"
  4133. },
  4134. body: JSON.stringify(requestBody)
  4135. });
  4136. ConsoleLogEnabled("Thumbnail Data Response:", thumbnailData);
  4137. break;
  4138. } catch (error) {
  4139. ConsoleLogEnabled("Thumbnail retrieval error:", error);
  4140. await wait(1000);
  4141. }
  4142. }
  4143.  
  4144. if (!thumbnailData.data) {
  4145. ConsoleLogEnabled("No thumbnail data available. Moving to next server.");
  4146. continue;
  4147. }
  4148.  
  4149. for (let thumbIndex = 0; thumbIndex < thumbnailData.data.length; thumbIndex++) {
  4150. const thumbnail = thumbnailData.data[thumbIndex];
  4151. if (thumbnail && thumbnail.imageUrl === avatarThumbnail) {
  4152. playerFound = true;
  4153. ConsoleLogEnabled(`Player located in server ${index + 1}!`);
  4154. notifications("Found User's Server! Joining Server...", "success", "🚀", "5000");
  4155. showLoadingOverlay();
  4156. Roblox.GameLauncher.joinGameInstance(gameId, server.id);
  4157. fadeOutAndRemove_7th_progress(progressPopup.popup, progressPopup.overlay);
  4158. Loadingbar(false);
  4159. disableFilterButton(false);
  4160. return {
  4161. gameId,
  4162. serverId: server.id,
  4163. currentPlayers: server.playing,
  4164. maximumPlayers: server.maxPlayers,
  4165. };
  4166. }
  4167. }
  4168. } else {
  4169. break;
  4170. }
  4171. }
  4172.  
  4173. pageCursor = serverList.nextPageCursor;
  4174. if (!pageCursor || playerFound) break;
  4175. else {
  4176. ConsoleLogEnabled("Pausing for 0.5 seconds before next server batch...");
  4177. await wait(500);
  4178. }
  4179. } else {
  4180. ConsoleLogEnabled("Server fetch failed. Retrying in 5 seconds...");
  4181. notifications("Got rate limited. Waiting 8 seconds...", "info", "❗", "8000")
  4182. await wait(8000);
  4183. }
  4184. }
  4185.  
  4186. if (!playerFound) {
  4187. // Wait for 2 seconds before calling another function
  4188. setTimeout(() => {
  4189. fadeOutAndRemove_7th_progress(progressPopup.popup, progressPopup.overlay);
  4190. notifications("User not found playing this game!", "error", "⚠️", "5000");
  4191. }, 2000); // 2000 milliseconds = 2 seconds
  4192.  
  4193. // Existing logic (unchanged)
  4194. Loadingbar(false);
  4195. disableFilterButton(false);
  4196. ConsoleLogEnabled(`Player not found in the searched servers (${totalServersChecked} servers checked)`);
  4197. // Update the progress popup with the final count
  4198. updateSearchProgressPopup(
  4199. progressPopup,
  4200. totalServersChecked,
  4201. Math.floor((Date.now() - startTime) / 1000),
  4202. true
  4203. );
  4204. return `Player not found in the searched servers (${totalServersChecked} servers checked)`;
  4205. }
  4206. }
  4207.  
  4208. /*******************************************************
  4209. name of function: showSearchProgressPopup
  4210. description: Creates and displays a popup showing the search progress.
  4211. *******************************************************/
  4212. function showSearchProgressPopup(avatarThumbnail) {
  4213. // Create the overlay (backdrop)
  4214. const overlay = document.createElement('div');
  4215. overlay.style.cssText = `
  4216. position: fixed;
  4217. top: 0;
  4218. left: 0;
  4219. width: 100%;
  4220. height: 100%;
  4221. background-color: rgba(0, 0, 0, 0.5);
  4222. z-index: 9999;
  4223. opacity: 0;
  4224. transition: opacity 0.3s ease;
  4225. `;
  4226. document.body.appendChild(overlay);
  4227.  
  4228. // Create the popup container
  4229. const popup = document.createElement('div');
  4230. popup.className = 'search-progress-popup';
  4231. popup.style.cssText = `
  4232. position: fixed;
  4233. top: 50%;
  4234. left: 50%;
  4235. transform: translate(-50%, -50%);
  4236. background-color: rgb(30, 32, 34);
  4237. padding: 20px;
  4238. border-radius: 10px;
  4239. z-index: 10000; /* Higher than overlay */
  4240. box-shadow: 0 0 15px rgba(0, 0, 0, 0.7);
  4241. display: flex;
  4242. flex-direction: column;
  4243. align-items: center;
  4244. gap: 15px;
  4245. width: 300px;
  4246. opacity: 0; /* Start invisible */
  4247. transition: opacity 0.3s ease, transform 0.3s ease;
  4248. `;
  4249.  
  4250. // Add a title
  4251. const title = document.createElement('h3');
  4252. title.textContent = 'Searching for Player...';
  4253. title.style.cssText = `
  4254. color: white;
  4255. margin: 0;
  4256. font-size: 18px;
  4257. font-weight: 500;
  4258. `;
  4259. popup.appendChild(title);
  4260.  
  4261. // Add the player's thumbnail
  4262. const thumbnail = document.createElement('img');
  4263. thumbnail.src = avatarThumbnail;
  4264. thumbnail.style.cssText = `
  4265. width: 100px;
  4266. height: 100px;
  4267. border-radius: 50%;
  4268. object-fit: cover;
  4269. `;
  4270. popup.appendChild(thumbnail);
  4271.  
  4272. // Add a progress text for servers searched
  4273. const serversSearchedText = document.createElement('p');
  4274. serversSearchedText.textContent = 'Servers searched: 0';
  4275. serversSearchedText.style.cssText = `
  4276. color: white;
  4277. margin: 0;
  4278. font-size: 16px;
  4279. `;
  4280. popup.appendChild(serversSearchedText);
  4281.  
  4282. // Add a text for time elapsed
  4283. const timeElapsedText = document.createElement('p');
  4284. timeElapsedText.textContent = 'Time elapsed: 0s';
  4285. timeElapsedText.style.cssText = `
  4286. color: white;
  4287. margin: 0;
  4288. font-size: 16px;
  4289. `;
  4290. popup.appendChild(timeElapsedText);
  4291.  
  4292. // Append the popup to the body
  4293. document.body.appendChild(popup);
  4294.  
  4295. // Fade in the overlay and popup
  4296. setTimeout(() => {
  4297. overlay.style.opacity = '1';
  4298. popup.style.opacity = '1';
  4299. popup.style.transform = 'translate(-50%, -50%) scale(1)';
  4300. }, 10);
  4301.  
  4302. return {
  4303. popup,
  4304. overlay,
  4305. serversSearchedText,
  4306. timeElapsedText,
  4307. };
  4308. }
  4309.  
  4310. /*******************************************************
  4311. name of function: updateSearchProgressPopup
  4312. description: Updates the search progress popup with the current count.
  4313. *******************************************************/
  4314. function updateSearchProgressPopup(
  4315. progressPopup,
  4316. totalServersChecked,
  4317. timeElapsed,
  4318. isFinal = false
  4319. ) {
  4320. progressPopup.serversSearchedText.textContent = `Servers searched: ${totalServersChecked}`;
  4321. progressPopup.timeElapsedText.textContent = `Time elapsed: ${timeElapsed}s`;
  4322.  
  4323. if (isFinal) {
  4324. progressPopup.serversSearchedText.textContent += ' (Search completed)';
  4325. }
  4326. }
  4327.  
  4328. /*******************************************************
  4329. name of function: fadeOutAndRemove
  4330. description: Fades out and removes the popup and overlay.
  4331. *******************************************************/
  4332. function fadeOutAndRemove_7th_progress(popup, overlay) {
  4333. popup.style.opacity = '0';
  4334. popup.style.transform = 'translate(-50%, -50%) scale(0.9)';
  4335. overlay.style.opacity = '0';
  4336. setTimeout(() => {
  4337. popup.remove();
  4338. overlay.remove();
  4339. }, 300); // Match the duration of the transition
  4340. }
  4341.  
  4342.  
  4343. /*********************************************************************************************************************************************************************************************************************************************
  4344. End of: This is all the functions for the 8 buttons
  4345.  
  4346. *********************************************************************************************************************************************************************************************************************************************/
  4347.  
  4348.  
  4349.  
  4350. /*********************************************************************************************************************************************************************************************************************************************
  4351. The Universal Functions
  4352.  
  4353. *********************************************************************************************************************************************************************************************************************************************/
  4354.  
  4355.  
  4356.  
  4357.  
  4358. /*******************************************************
  4359. name of function: disableLoadMoreButton
  4360. description: Disables the "Load More" button
  4361. *******************************************************/
  4362. function disableLoadMoreButton() {
  4363. const loadMoreButton = document.querySelector('.rbx-running-games-load-more');
  4364. if (loadMoreButton) {
  4365. loadMoreButton.disabled = true;
  4366. loadMoreButton.style.opacity = '0.5'; // Optional: Make the button look disabled
  4367. loadMoreButton.style.cursor = 'not-allowed'; // Optional: Change cursor to indicate disabled state
  4368. loadMoreButton.title = 'Disabled by Roblox Locator'; // Set tooltip text
  4369. } else {
  4370. ConsoleLogEnabled('Load More button not found!');
  4371. ConsoleLogEnabled('Maybe moved by another extension!');
  4372. }
  4373. }
  4374.  
  4375.  
  4376.  
  4377. /*******************************************************
  4378. name of function: Loadingbar
  4379. description: Shows or hides a loading bar (now using pulsing boxes)
  4380. *******************************************************/
  4381. function Loadingbar(disable) {
  4382. const serverListSection = document.querySelector('#rbx-running-games');
  4383. const serverCardsContainer = document.querySelector('#rbx-public-game-server-item-container');
  4384. const emptyGameInstancesContainer = document.querySelector('.section-content-off.empty-game-instances-container');
  4385. const noServersMessage = emptyGameInstancesContainer?.querySelector('.no-servers-message');
  4386.  
  4387. // Check if serverCardsContainer does not exist
  4388. if (!serverCardsContainer && emptyGameInstancesContainer && noServersMessage?.textContent.includes('Unable to load servers')) {
  4389. notifications('Unable to load servers. Please refresh the page.', 'warning', '⚠️', '5000');
  4390. return;
  4391. }
  4392.  
  4393. if (disable) {
  4394. if (serverCardsContainer) {
  4395. serverCardsContainer.innerHTML = ''; // Clear contents
  4396. serverCardsContainer.removeAttribute('style'); // Remove inline styles if present
  4397. }
  4398.  
  4399. // Prevent duplicate loading bars
  4400. if (document.querySelector('#loading-bar')) return;
  4401.  
  4402. // Create and display the loading boxes
  4403. const loadingContainer = document.createElement('div');
  4404. loadingContainer.id = 'loading-bar';
  4405. loadingContainer.style.cssText = `
  4406. display: flex;
  4407. justify-content: center;
  4408. align-items: center;
  4409. gap: 5px;
  4410. margin-top: 10px;
  4411. `;
  4412.  
  4413. const fragment = document.createDocumentFragment();
  4414. for (let i = 0; i < 3; i++) {
  4415. const box = document.createElement('div');
  4416. box.style.cssText = `
  4417. width: 10px;
  4418. height: 10px;
  4419. background-color: white;
  4420. margin: 0 5px;
  4421. border-radius: 2px;
  4422. animation: pulse 1.2s ${i * 0.2}s infinite;
  4423. `;
  4424. fragment.appendChild(box);
  4425. }
  4426. loadingContainer.appendChild(fragment);
  4427.  
  4428. if (serverListSection) {
  4429. serverListSection.appendChild(loadingContainer);
  4430. }
  4431.  
  4432. // Inject CSS only once
  4433. if (!document.querySelector('#loading-style')) {
  4434. const styleSheet = document.createElement('style');
  4435. styleSheet.id = 'loading-style';
  4436. styleSheet.textContent = `
  4437. @keyframes pulse {
  4438. 0%, 100% { transform: scale(1); }
  4439. 50% { transform: scale(1.5); }
  4440. }
  4441. `;
  4442. document.head.appendChild(styleSheet);
  4443. }
  4444.  
  4445. // Remove the unwanted gradient div
  4446. document.querySelector('div[style*="background: linear-gradient(145deg"]')?.remove();
  4447.  
  4448. } else {
  4449. // Remove the loading bar
  4450. document.querySelector('#loading-bar')?.remove();
  4451. }
  4452. }
  4453.  
  4454.  
  4455.  
  4456.  
  4457. /*******************************************************
  4458. name of function: fetchPlayerThumbnails
  4459. description: Fetches player thumbnails for up to 5 players. Skips the batch if an error occurs.
  4460. *******************************************************/
  4461. async function fetchPlayerThumbnails(playerTokens) {
  4462. // Limit to the first 5 player tokens
  4463. const limitedTokens = playerTokens.slice(0, 5);
  4464.  
  4465. const body = limitedTokens.map(token => ({
  4466. requestId: `0:${token}:AvatarHeadshot:150x150:png:regular`,
  4467. type: "AvatarHeadShot",
  4468. targetId: 0,
  4469. token,
  4470. format: "png",
  4471. size: "150x150",
  4472. }));
  4473.  
  4474. try {
  4475. const response = await fetch("https://thumbnails.roblox.com/v1/batch", {
  4476. method: "POST",
  4477. headers: {
  4478. "Content-Type": "application/json",
  4479. Accept: "application/json",
  4480. },
  4481. body: JSON.stringify(body),
  4482. });
  4483.  
  4484. // Check if the response is successful
  4485. if (!response.ok) {
  4486. throw new Error(`HTTP error! Status: ${response.status}`);
  4487. }
  4488.  
  4489. const data = await response.json();
  4490. return data.data || []; // Return the data or an empty array if no data is present
  4491. } catch (error) {
  4492. ConsoleLogEnabled('Error fetching player thumbnails:', error);
  4493. return []; // Return an empty array if an error occurs
  4494. }
  4495. }
  4496.  
  4497.  
  4498. /*******************************************************
  4499. name of function: disableFilterButton
  4500. description: Disables or enables the filter button based on the input.
  4501. *******************************************************/
  4502. function disableFilterButton(disable) {
  4503. const filterButton = document.querySelector('.RL-filter-button');
  4504. const refreshButtons = document.querySelectorAll('.btn-more.rbx-refresh.refresh-link-icon.btn-control-xs.btn-min-width');
  4505. const filterOverlayId = 'filter-button-overlay';
  4506. const refreshOverlayClass = 'refresh-button-overlay';
  4507.  
  4508. if (filterButton) {
  4509. const parent = filterButton.parentElement;
  4510.  
  4511. if (disable) {
  4512. // Disable the filter button with an overlay
  4513. filterButton.disabled = true;
  4514. filterButton.style.opacity = '0.5';
  4515. filterButton.style.cursor = 'not-allowed';
  4516.  
  4517. // Create an overlay if it doesn't exist
  4518. let overlay = document.getElementById(filterOverlayId);
  4519. if (!overlay) {
  4520. overlay = document.createElement('div');
  4521. overlay.id = filterOverlayId;
  4522. overlay.style.position = 'absolute';
  4523. overlay.style.top = '-10px';
  4524. overlay.style.left = '-10px';
  4525. overlay.style.width = 'calc(100% + 20px)';
  4526. overlay.style.height = 'calc(100% + 20px)';
  4527. overlay.style.backgroundColor = 'transparent';
  4528. overlay.style.zIndex = '9999';
  4529. overlay.style.pointerEvents = 'all'; // Block clicks
  4530. parent.style.position = 'relative';
  4531. parent.appendChild(overlay);
  4532. }
  4533. } else {
  4534. // Enable the filter button
  4535. filterButton.disabled = false;
  4536. filterButton.style.opacity = '1';
  4537. filterButton.style.cursor = 'pointer';
  4538.  
  4539. // Remove the overlay if it exists
  4540. const overlay = document.getElementById(filterOverlayId);
  4541. if (overlay) {
  4542. overlay.remove();
  4543. }
  4544. }
  4545. } else {
  4546. console.log('Filter button not found!');
  4547. }
  4548.  
  4549. if (refreshButtons.length > 0) {
  4550. refreshButtons.forEach((refreshButton) => {
  4551. const refreshParent = refreshButton.parentElement;
  4552.  
  4553. if (disable) {
  4554. // Create an overlay over the refresh button
  4555. let refreshOverlay = refreshParent.querySelector(`.${refreshOverlayClass}`);
  4556. if (!refreshOverlay) {
  4557. refreshOverlay = document.createElement('div');
  4558. refreshOverlay.className = refreshOverlayClass;
  4559. refreshOverlay.style.position = 'absolute';
  4560. refreshOverlay.style.top = '-10px';
  4561. refreshOverlay.style.left = '-10px';
  4562. refreshOverlay.style.width = 'calc(100% + 20px)';
  4563. refreshOverlay.style.height = 'calc(100% + 20px)';
  4564. refreshOverlay.style.backgroundColor = 'transparent';
  4565. refreshOverlay.style.zIndex = '9999';
  4566. refreshOverlay.style.pointerEvents = 'all'; // Block clicks
  4567. refreshParent.style.position = 'relative';
  4568. refreshParent.appendChild(refreshOverlay);
  4569. }
  4570. } else {
  4571. // Remove the overlay if it exists
  4572. const refreshOverlay = refreshParent.querySelector(`.${refreshOverlayClass}`);
  4573. if (refreshOverlay) {
  4574. refreshOverlay.remove();
  4575. }
  4576. }
  4577. });
  4578. } else {
  4579. ConsoleLogEnabled('Refresh button not found!');
  4580. }
  4581. }
  4582.  
  4583.  
  4584.  
  4585. async function rbx_card(serverId, playerTokens, maxPlayers, playing, gameId) {
  4586. // Fetch player thumbnails (up to 5)
  4587. const thumbnails = await fetchPlayerThumbnails(playerTokens);
  4588.  
  4589. // Create the server card container
  4590. const cardItem = document.createElement('li');
  4591. cardItem.className = 'rbx-game-server-item col-md-3 col-sm-4 col-xs-6';
  4592.  
  4593. // Create the player thumbnails container
  4594. const playerThumbnailsContainer = document.createElement('div');
  4595. playerThumbnailsContainer.className = 'player-thumbnails-container';
  4596.  
  4597. // Add player thumbnails to the container (up to 5)
  4598. thumbnails.forEach(thumbnail => {
  4599. const playerAvatar = document.createElement('span');
  4600. playerAvatar.className = 'avatar avatar-headshot-md player-avatar';
  4601.  
  4602. const thumbnailImage = document.createElement('span');
  4603. thumbnailImage.className = 'thumbnail-2d-container avatar-card-image';
  4604.  
  4605. const img = document.createElement('img');
  4606. img.src = thumbnail.imageUrl;
  4607. img.alt = '';
  4608. img.title = '';
  4609.  
  4610. thumbnailImage.appendChild(img);
  4611. playerAvatar.appendChild(thumbnailImage);
  4612. playerThumbnailsContainer.appendChild(playerAvatar);
  4613. });
  4614.  
  4615. // Add the 6th placeholder for remaining players
  4616. if (playing > 5) {
  4617. const remainingPlayers = playing - 5;
  4618. const placeholder = document.createElement('span');
  4619. placeholder.className = 'avatar avatar-headshot-md player-avatar hidden-players-placeholder';
  4620. placeholder.textContent = `+${remainingPlayers}`;
  4621. placeholder.style.cssText = `
  4622. background-color: #6a6f81; /* Grayish-blue background */
  4623. color: white;
  4624. display: flex;
  4625. align-items: center;
  4626. justify-content: center;
  4627. border-radius: 50%; /* Fully round */
  4628. font-size: 16px; /* Larger font size */
  4629. width: 60px; /* Larger width */
  4630. height: 60px; /* Larger height */
  4631. `;
  4632. playerThumbnailsContainer.appendChild(placeholder);
  4633. }
  4634.  
  4635. // Create the server details container
  4636. const serverDetails = document.createElement('div');
  4637. serverDetails.className = 'rbx-game-server-details game-server-details';
  4638.  
  4639. // Add server status (e.g., "15 of 15 people max")
  4640. const serverStatus = document.createElement('div');
  4641. serverStatus.className = 'text-info rbx-game-status rbx-game-server-status text-overflow';
  4642. serverStatus.textContent = `${playing} of ${maxPlayers} people max`;
  4643. serverDetails.appendChild(serverStatus);
  4644.  
  4645. // Add the player count gauge
  4646. const gaugeContainer = document.createElement('div');
  4647. gaugeContainer.className = 'server-player-count-gauge border';
  4648.  
  4649. const gaugeInner = document.createElement('div');
  4650. gaugeInner.className = 'gauge-inner-bar border';
  4651. gaugeInner.style.width = `${(playing / maxPlayers) * 100}%`;
  4652.  
  4653. gaugeContainer.appendChild(gaugeInner);
  4654. serverDetails.appendChild(gaugeContainer);
  4655.  
  4656. // Create a container for the buttons
  4657. const buttonContainer = document.createElement('div');
  4658. buttonContainer.className = 'button-container';
  4659. buttonContainer.style.cssText = `
  4660. display: flex;
  4661. gap: 8px; /* Space between buttons */
  4662. `;
  4663.  
  4664. // Add the "Join" button
  4665. const joinButton = document.createElement('button');
  4666. joinButton.type = 'button';
  4667. joinButton.className = 'btn-full-width btn-control-xs rbx-game-server-join game-server-join-btn btn-primary-md btn-min-width';
  4668. joinButton.textContent = 'Join';
  4669.  
  4670. // Add click event to join the server
  4671. joinButton.addEventListener('click', () => {
  4672. showLoadingOverlay();
  4673. Roblox.GameLauncher.joinGameInstance(gameId, serverId);
  4674. });
  4675.  
  4676. buttonContainer.appendChild(joinButton);
  4677.  
  4678. // Add the "Invite" button
  4679. const inviteButton = document.createElement('button');
  4680. inviteButton.type = 'button';
  4681. inviteButton.className = 'btn-full-width btn-control-xs rbx-game-server-invite game-server-invite-btn btn-secondary-md btn-min-width';
  4682. inviteButton.textContent = 'Invite';
  4683.  
  4684. // Add click event to log the invite link
  4685. inviteButton.addEventListener('click', () => {
  4686. const inviteLink = `https://oqarshi.github.io/Invite/?placeid=${gameId}&serverid=${serverId}`;
  4687. //ConsoleLogEnabled('Copied invite link:', inviteLink);
  4688. navigator.clipboard.writeText(inviteLink).then(() => {
  4689. notifications('Success! Invite link copied to clipboard!', 'success', '🎉', '2000');
  4690. //ConsoleLogEnabled('Invite link copied to clipboard');
  4691. }).catch(err => {
  4692. ConsoleLogEnabled('Failed to copy invite link:', err);
  4693. notifications('Failed! Invite link copied to clipboard!', 'error', '⚠️', '2000');
  4694. });
  4695. });
  4696.  
  4697. buttonContainer.appendChild(inviteButton);
  4698.  
  4699. // Add the button container to the server details
  4700. serverDetails.appendChild(buttonContainer);
  4701.  
  4702. // Assemble the card
  4703. const cardContainer = document.createElement('div');
  4704. cardContainer.className = 'card-item';
  4705. cardContainer.appendChild(playerThumbnailsContainer);
  4706. cardContainer.appendChild(serverDetails);
  4707.  
  4708. cardItem.appendChild(cardContainer);
  4709.  
  4710. // Add the card to the server list
  4711. const serverList = document.querySelector('#rbx-public-game-server-item-container');
  4712. serverList.appendChild(cardItem);
  4713. }
  4714.  
  4715. /*********************************************************************************************************************************************************************************************************************************************
  4716. End of function for the notification function
  4717.  
  4718. *********************************************************************************************************************************************************************************************************************************************/
  4719.  
  4720.  
  4721. /*********************************************************************************************************************************************************************************************************************************************
  4722. Launching Function
  4723.  
  4724. *********************************************************************************************************************************************************************************************************************************************/
  4725.  
  4726. function showLoadingOverlay() {
  4727. // Create the content div (no overlay background)
  4728. const content = document.createElement('div');
  4729. content.style.position = 'fixed';
  4730. content.style.top = 'calc(50% - 15px)'; // Move 15px higher
  4731. content.style.left = '50%';
  4732. content.style.transform = 'translate(-50%, -50%)'; // Center the box horizontally
  4733. content.style.width = '400px';
  4734. content.style.height = '250px';
  4735. content.style.backgroundColor = '#1e1e1e'; // Dark background
  4736. content.style.display = 'flex';
  4737. content.style.flexDirection = 'column';
  4738. content.style.justifyContent = 'center';
  4739. content.style.alignItems = 'center';
  4740. content.style.boxShadow = '0 4px 20px rgba(0, 0, 0, 0.5)'; // Subtle shadow
  4741. content.style.borderRadius = '12px'; // Slightly rounded corners
  4742. content.style.zIndex = '1000000'; // z-index set to 1 million
  4743. content.style.opacity = '0'; // Start with 0 opacity for fade-in
  4744. content.style.transition = 'opacity 0.5s ease'; // Fade-in transition
  4745. content.style.fontFamily = 'Arial, sans-serif'; // Clean font
  4746.  
  4747. // Create the loading text
  4748. const loadingText = document.createElement('p');
  4749. loadingText.textContent = 'Joining Roblox Game...';
  4750. loadingText.style.fontSize = '20px';
  4751. loadingText.style.color = '#fff'; // White text for contrast
  4752. loadingText.style.marginTop = '20px'; // Spacing between image and text
  4753. loadingText.style.fontWeight = 'bold'; // Bold text
  4754.  
  4755. // Create the base64 image
  4756. const base64Image = document.createElement('img');
  4757. base64Image.src = window.Base64Images.logo;
  4758. base64Image.style.width = '80px'; // Slightly larger image
  4759. base64Image.style.height = '80px'; // Slightly larger image
  4760. base64Image.style.borderRadius = '8px'; // Rounded corners for the image
  4761.  
  4762. // Create the loading boxes container
  4763. const loadingBoxes = document.createElement('div');
  4764. loadingBoxes.style.display = 'flex';
  4765. loadingBoxes.style.alignItems = 'center';
  4766. loadingBoxes.style.justifyContent = 'center';
  4767. loadingBoxes.style.marginTop = '15px'; // Spacing between text and boxes
  4768.  
  4769. // Create the three loading boxes
  4770. for (let i = 0; i < 3; i++) {
  4771. const box = document.createElement('div');
  4772. box.style.width = '10px';
  4773. box.style.height = '10px';
  4774. box.style.backgroundColor = '#fff'; // White boxes
  4775. box.style.margin = '0 5px'; // Spacing between boxes
  4776. box.style.borderRadius = '2px'; // Slightly rounded corners
  4777. box.style.animation = `pulse 1.2s ${i * 0.2}s infinite`; // Animation with delay
  4778. loadingBoxes.appendChild(box);
  4779. }
  4780.  
  4781. // Define the pulse animation using CSS
  4782. const style = document.createElement('style');
  4783. style.textContent = `
  4784. @keyframes pulse {
  4785. 0%, 100% { transform: scale(1); }
  4786. 50% { transform: scale(1.5); }
  4787. }
  4788. `;
  4789. document.head.appendChild(style); // Add the animation to the document
  4790.  
  4791. // Append the image, text, and loading boxes to the content div
  4792. content.appendChild(base64Image);
  4793. content.appendChild(loadingText);
  4794. content.appendChild(loadingBoxes);
  4795.  
  4796. // Append the content div to the body
  4797. document.body.appendChild(content);
  4798.  
  4799. // Trigger fade-in animation
  4800. setTimeout(() => {
  4801. content.style.opacity = '1';
  4802. }, 10); // Small delay to trigger the transition
  4803.  
  4804. // Remove the content after 8 seconds with fade-out animation
  4805. setTimeout(() => {
  4806. content.style.opacity = '0'; // Fade out
  4807. setTimeout(() => {
  4808. document.body.removeChild(content); // Remove after fade-out completes
  4809. }, 500); // Wait for the fade-out transition to finish
  4810. }, 8000); // 8000 milliseconds = 8 seconds
  4811. }
  4812.  
  4813.  
  4814. /*********************************************************************************************************************************************************************************************************************************************
  4815. End of function for the launching function
  4816.  
  4817. *********************************************************************************************************************************************************************************************************************************************/
  4818.  
  4819. const serverRegionsByIp = {
  4820. "128.116.0.0": { city: "Hong Kong", country: { name: "Hong Kong", code: "HK" }, latitude: 22.3193, longitude: 114.1694 },
  4821. "128.116.1.0": { city: "Los Angeles", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 34.0522, longitude: -118.2437 },
  4822. "128.116.2.0": { city: "Warsaw", country: { name: "Poland", code: "PL" }, region: { name: "Mazowieckie", code: "14" }, latitude: 52.2297, longitude: 21.0122 },
  4823. "128.116.3.0": { city: "Warsaw", country: { name: "Poland", code: "PL" }, region: { name: "Mazowieckie", code: "14" }, latitude: 52.2297, longitude: 21.0122 },
  4824. "128.116.4.0": { city: "Paris", country: { name: "France", code: "FR" }, region: { name: "Île-de-France", code: "IDF" }, latitude: 48.8566, longitude: 2.3522 },
  4825. "128.116.5.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  4826. "128.116.6.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
  4827. "128.116.7.0": { city: "Mumbai", country: { name: "India", code: "IN" }, region: { name: "Mahārāshtra", code: "MH" }, latitude: 19.0760, longitude: 72.8777 },
  4828. "128.116.8.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  4829. "128.116.9.0": { city: "Mumbai", country: { name: "India", code: "IN" }, region: { name: "Mahārāshtra", code: "MH" }, latitude: 19.0760, longitude: 72.8777 },
  4830. "128.116.10.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  4831. "128.116.11.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  4832. "128.116.12.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  4833. "128.116.13.0": { city: "Amsterdam", country: { name: "Netherlands", code: "NL" }, region: { name: "Noord-Holland", code: "NH" }, latitude: 52.3676, longitude: 4.9041 },
  4834. "128.116.14.0": { city: "Hong Kong", country: { name: "Hong Kong", code: "HK" }, latitude: 22.3193, longitude: 114.1694 },
  4835. "128.116.15.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
  4836. "128.116.16.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
  4837. "128.116.17.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
  4838. "128.116.18.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 },
  4839. "128.116.19.0": { city: "Paris", country: { name: "France", code: "FR" }, region: { name: "Île-de-France", code: "IDF" }, latitude: 48.8566, longitude: 2.3522 },
  4840. "128.116.20.0": { city: "Paris", country: { name: "France", code: "FR" }, region: { name: "Île-de-France", code: "IDF" }, latitude: 48.8566, longitude: 2.3522 },
  4841. "128.116.21.0": { city: "Amsterdam", country: { name: "Netherlands", code: "NL" }, region: { name: "Noord-Holland", code: "NH" }, latitude: 52.3676, longitude: 4.9041 },
  4842. "128.116.22.0": { city: "Atlanta", country: { name: "United States", code: "US" }, region: { name: "Georgia", code: "GA" }, latitude: 33.7490, longitude: -84.3880 },
  4843. "128.116.23.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
  4844. "128.116.24.0": { city: "Atlanta", country: { name: "United States", code: "US" }, region: { name: "Georgia", code: "GA" }, latitude: 33.7490, longitude: -84.3880 },
  4845. "128.116.25.0": { city: "Atlanta", country: { name: "United States", code: "US" }, region: { name: "Georgia", code: "GA" }, latitude: 33.7490, longitude: -84.3880 },
  4846. "128.116.26.0": { city: "Paris", country: { name: "France", code: "FR" }, region: { name: "Île-de-France", code: "IDF" }, latitude: 48.8566, longitude: 2.3522 },
  4847. "128.116.27.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  4848. "128.116.28.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  4849. "128.116.29.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  4850. "128.116.30.0": { city: "Hong Kong", country: { name: "Hong Kong", code: "HK" }, latitude: 22.3193, longitude: 114.1694 },
  4851. "128.116.31.0": { city: "Warsaw", country: { name: "Poland", code: "PL" }, region: { name: "Mazowieckie", code: "14" }, latitude: 52.2297, longitude: 21.0122 },
  4852. "128.116.32.0": { city: "New York City", country: { name: "United States", code: "US" }, region: { name: "New York", code: "NY" }, latitude: 40.7128, longitude: -74.0060 },
  4853. "128.116.33.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 },
  4854. "128.116.34.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  4855. "128.116.35.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 },
  4856. "128.116.36.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 },
  4857. "128.116.37.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 },
  4858. "128.116.38.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 },
  4859. "128.116.39.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  4860. "128.116.40.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  4861. "128.116.41.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  4862. "128.116.42.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  4863. "128.116.43.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  4864. "128.116.44.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  4865. "128.116.45.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 },
  4866. "128.116.46.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  4867. "128.116.47.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  4868. "128.116.48.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  4869. "128.116.49.0": { city: "Los Angeles", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 34.0522, longitude: -118.2437 },
  4870. "128.116.50.0": { city: "Singapore", country: { name: "Singapore", code: "SG" }, latitude: 1.3521, longitude: 103.8198 },
  4871. "128.116.51.0": { city: "Sydney", country: { name: "Australia", code: "AU" }, region: { name: "New South Wales", code: "NSW" }, latitude: -33.8688, longitude: 151.2093 },
  4872. "128.116.52.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  4873. "128.116.53.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  4874. "128.116.54.0": { city: "Amsterdam", country: { name: "Netherlands", code: "NL" }, region: { name: "Noord-Holland", code: "NH" }, latitude: 52.3676, longitude: 4.9041 },
  4875. "128.116.55.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
  4876. "128.116.56.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  4877. "128.116.57.0": { city: "San Jose", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3382, longitude: -121.8863 },
  4878. "128.116.58.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
  4879. "128.116.59.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
  4880. "128.116.60.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
  4881. "128.116.61.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  4882. "128.116.62.0": { city: "Seattle", country: { name: "United States", code: "US" }, region: { name: "Washington", code: "WA" }, latitude: 47.6062, longitude: -122.3321 },
  4883. "128.116.63.0": { city: "Los Angeles", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 34.0522, longitude: -118.2437 },
  4884. "128.116.64.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  4885. "128.116.65.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
  4886. "128.116.66.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
  4887. "128.116.67.0": { city: "San Jose", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3382, longitude: -121.8863 },
  4888. "128.116.68.0": { city: "Santa Clara", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3541, longitude: -121.9552 },
  4889. "128.116.69.0": { city: "Santa Clara", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3541, longitude: -121.9552 },
  4890. "128.116.70.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  4891. "128.116.71.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  4892. "128.116.72.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 },
  4893. "128.116.73.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 },
  4894. "128.116.74.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  4895. "128.116.75.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  4896. "128.116.76.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  4897. "128.116.77.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  4898. "128.116.78.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  4899. "128.116.79.0": { city: "Singapore", country: { name: "Singapore", code: "SG" }, latitude: 1.3521, longitude: 103.8198 },
  4900. "128.116.80.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  4901. "128.116.81.0": { city: "Santa Clara", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3541, longitude: -121.9552 },
  4902. "128.116.82.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
  4903. "128.116.83.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
  4904. "128.116.84.0": { city: "Elk Grove Village", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 42.0039, longitude: -87.9706 },
  4905. "128.116.85.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 },
  4906. "128.116.86.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  4907. "128.116.87.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  4908. "128.116.88.0": { city: "Elk Grove Village", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 42.0039, longitude: -87.9706 },
  4909. "128.116.89.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 },
  4910. "128.116.90.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  4911. "128.116.91.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  4912. "128.116.92.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  4913. "128.116.93.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  4914. "128.116.94.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  4915. "128.116.95.0": { city: "Dallas", country: { name: "United States", code: "US" }, region: { name: "Texas", code: "TX" }, latitude: 32.7767, longitude: -96.7970 },
  4916. "128.116.96.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  4917. "128.116.97.0": { city: "Singapore", country: { name: "Singapore", code: "SG" }, latitude: 1.3521, longitude: 103.8198 },
  4918. "128.116.98.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  4919. "128.116.99.0": { city: "Atlanta", country: { name: "United States", code: "US" }, region: { name: "Georgia", code: "GA" }, latitude: 33.7490, longitude: -84.3880 },
  4920. "128.116.100.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  4921. "128.116.101.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  4922. "128.116.102.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  4923. "128.116.103.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  4924. "128.116.104.0": { city: "Mumbai", country: { name: "India", code: "IN" }, region: { name: "Mahārāshtra", code: "MH" }, latitude: 19.0760, longitude: 72.8777 },
  4925. "128.116.105.0": { city: "Santa Clara", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3541, longitude: -121.9552 },
  4926. "128.116.106.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  4927. "128.116.107.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  4928. "128.116.108.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  4929. "128.116.109.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  4930. "128.116.110.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  4931. "128.116.111.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  4932. "128.116.112.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  4933. "128.116.113.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  4934. "128.116.114.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  4935. "128.116.115.0": { city: "Seattle", country: { name: "United States", code: "US" }, region: { name: "Washington", code: "WA" }, latitude: 47.6062, longitude: -122.3321 },
  4936. "128.116.116.0": { city: "Los Angeles", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 34.0522, longitude: -118.2437 },
  4937. "128.116.117.0": { city: "San Jose", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3382, longitude: -121.8863 },
  4938. "128.116.118.0": { city: "Hong Kong", country: { name: "Hong Kong", code: "HK" }, latitude: 22.3193, longitude: 114.1694 },
  4939. "128.116.119.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 },
  4940. "128.116.120.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
  4941. "128.116.121.0": { city: "Amsterdam", country: { name: "Netherlands", code: "NL" }, region: { name: "Noord-Holland", code: "NH" }, latitude: 52.3676, longitude: 4.9041 },
  4942. "128.116.122.0": { city: "Paris", country: { name: "France", code: "FR" }, region: { name: "Île-de-France", code: "IDF" }, latitude: 48.8566, longitude: 2.3522 },
  4943. "128.116.123.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  4944. "128.116.124.0": { city: "Warsaw", country: { name: "Poland", code: "PL" }, region: { name: "Mazowieckie", code: "14" }, latitude: 52.2297, longitude: 21.0122 },
  4945. "128.116.125.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  4946. "128.116.126.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
  4947. "128.116.127.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 },
  4948. };
  4949. // find_game_id function does nothing lmao but its ere i guees
  4950. function find_game_id() {
  4951. ConsoleLogEnabled('Trying to find game id');
  4952. const gameIdMatch = window.location.pathname.match(/\/games\/(\d+)\//);
  4953. if (!gameIdMatch) {
  4954. ConsoleLogEnabled("Game ID not found in URL!");
  4955. return;
  4956. }
  4957.  
  4958. const gameId = gameIdMatch[1];
  4959. }
  4960.  
  4961. /*******************************************************
  4962. name of function: Initiate the observer
  4963. description: Start observing the document for changes
  4964. *******************************************************/
  4965.  
  4966. // end of the check for the url
  4967. }
  4968.  
  4969. /*******************************************************
  4970. End of code for the random hop button and the filter button on roblox.com/games/*
  4971. *******************************************************/
  4972.  
  4973.  
  4974.  
  4975.  
  4976. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址