RoLocate

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

目前为 2025-02-22 提交的版本。查看 最新版本

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

QingJ © 2025

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