RoLocate

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

  1. // ==UserScript==
  2. // @name RoLocate
  3. // @namespace https://oqarshi.github.io/
  4. // @version 35.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. // @grant GM_getValue
  12. // @grant GM_listValues
  13. // @grant GM_setValue
  14. // @grant GM_deleteValue
  15. // @require https://update.greatest.deepsurf.us/scripts/526611/1574250/Rolocate%20Base64%20Image%20Library.js
  16. // ==/UserScript==
  17.  
  18.  
  19.  
  20. (function() {
  21. 'use strict';
  22.  
  23.  
  24.  
  25. function initializeLocalStorage() {
  26. // Define default settings
  27. const defaultSettings = {
  28. enableLogs: false, // disabled by default
  29. removeads: false, // disabled by default
  30. togglefilterserversbutton: true, // enable by default
  31. toggleserverhopbutton: true, // enable by default
  32. AutoRunServerRegions: false, // disabled by default
  33. ShowOldGreeting: false, // disabled by default
  34. togglerecentserverbutton: true, // enable by default
  35. quicknav: false, // disabled by default
  36. prioritylocation: "automatic", // automatic by default
  37. };
  38.  
  39. // Loop through default settings and set them in localStorage if they don't exist
  40. Object.entries(defaultSettings).forEach(([key, value]) => {
  41. const storageKey = `ROLOCATE_${key}`;
  42. if (localStorage.getItem(storageKey) === null) {
  43. localStorage.setItem(storageKey, value);
  44. }
  45. });
  46. }
  47.  
  48. //// testing for locations not in production
  49. //(async () => {
  50. // console.log("[GM Storage Dump] --- Start ---");
  51. // const keys = await GM_listValues();
  52. // for (const key of keys) {
  53. // console.log(`[GM] ${key}:`, await GM_getValue(key));
  54. // }
  55. // console.log("[GM Storage Dump] --- End ---");
  56. //})();//
  57. //// testing for locations
  58. //(async () => {
  59. // const keys = await GM_listValues();
  60. // for (const key of keys) {
  61. // GM_deleteValue(key);
  62. // console.log(`Deleted ${key}`);
  63. // }
  64. //})();
  65.  
  66.  
  67. function initializeCoordinatesStorage() {
  68. // Check if coordinates are already stored
  69. try {
  70. const storedCoords = GM_getValue("ROLOCATE_coordinates");
  71. if (!storedCoords) {
  72. // Set default empty coordinates
  73. GM_setValue("ROLOCATE_coordinates", JSON.stringify({
  74. lat: "",
  75. lng: ""
  76. }));
  77. } else {
  78. // Validate existing coordinates
  79. const parsedCoords = JSON.parse(storedCoords);
  80. if ((!parsedCoords.lat || !parsedCoords.lng) && localStorage.getItem("ROLOCATE_prioritylocation") === "manual") {
  81. // If manual mode but no coordinates, revert to automatic
  82. localStorage.setItem("ROLOCATE_prioritylocation", "automatic");
  83. }
  84. }
  85. } catch (e) {
  86. console.error("Error initializing coordinates storage:", e);
  87. // Set default empty coordinates if parsing fails
  88. GM_setValue("ROLOCATE_coordinates", JSON.stringify({
  89. lat: "",
  90. lng: ""
  91. }));
  92. }
  93. }
  94.  
  95.  
  96. function getSettingsContent(section) {
  97. if (section === "home") {
  98. return `
  99. <div class="home-section">
  100. <img class="rolocate-logo" src="${window.Base64Images.logo}" alt="ROLOCATE Logo">
  101. <div class="version">Rolocate: Version 35.3</div>
  102. <div class="section-separator"></div>
  103. <p>Rolocate Settings Menu.</p>
  104. </div>
  105. `;
  106. }
  107.  
  108. if (section === "appearance") {
  109. return `
  110. <div class="appearance-section">
  111. <label class="toggle-slider section-hover">
  112. <input type="checkbox" id="ShowOldGreeting">
  113. <span class="slider"></span>
  114. Show Old Greeting
  115. </label>
  116. <div class="hint-text">
  117. <p>Restores the classic Roblox greeting style on your home page</p>
  118. </div>
  119. </div>
  120. `;
  121. }
  122.  
  123. if (section === "advanced") {
  124. return `
  125. <div class="advanced-section">
  126. <span class="warning_advanced">For Experienced Users Only🧠🙃</span>
  127. <div class="section-separator"></div>
  128.  
  129. <label class="toggle-slider section-hover">
  130. <input type="checkbox" id="enableLogs">
  131. <span class="slider"></span>
  132. Enable Console Logs
  133. </label>
  134.  
  135. <label class="toggle-slider section-hover">
  136. <input type="checkbox" id="togglefilterserversbutton">
  137. <span class="slider"></span>
  138. Enable Server Filters
  139. </label>
  140.  
  141. <label class="toggle-slider section-hover">
  142. <input type="checkbox" id="toggleserverhopbutton">
  143. <span class="slider"></span>
  144. Enable Server Hop Button
  145. </label>
  146.  
  147. <div class="location-settings section-hover">
  148. <div class="setting-header">
  149. <span>Set Default Location Mode</span>
  150. <span class="help-icon" title="Choose how your location will be determined">?</span>
  151. </div>
  152.  
  153. <select id="prioritylocation-select">
  154. <option value="manual" style="color: rgb(255, 40, 40);">Manual</option>
  155. <option value="automatic" style="color: rgb(255, 40, 40);">Automatic</option>
  156. </select>
  157.  
  158. <div id="location-hint">
  159. <strong>Manual:</strong> Set your location manually below
  160. <strong>Automatic:</strong> Auto detect your device's location
  161. </div>
  162.  
  163. <div id="manual-coordinates" style="margin-top: 15px; display: none;">
  164. <div class="coordinates-inputs" style="display: flex; gap: 10px; margin-bottom: 12px;">
  165. <div style="flex: 1;">
  166. <label for="latitude" style="display: block; margin-bottom: 8px; font-size: 14px;">Latitude</label>
  167. <input type="text" id="latitude" placeholder="e.g. 40.7128"
  168. style="width: 100%; padding: 10px 12px; border-radius: 8px; border: 1px solid rgba(255, 255, 255, 0.05);; background: rgba(255,255,255,0.05); color: #e0e0e0;">
  169. </div>
  170. <div style="flex: 1;">
  171. <label for="longitude" style="display: block; margin-bottom: 8px; font-size: 14px;">Longitude</label>
  172. <input type="text" id="longitude" placeholder="e.g. -74.0060"
  173. style="width: 100%; padding: 10px 12px; border-radius: 8px; border: 1px solid rgba(255, 255, 255, 0.05);; background: rgba(255,255,255,0.05); color: #e0e0e0;">
  174. </div>
  175. </div>
  176. <button id="save-coordinates" class="edit-nav-button" style="width: 100%; margin-top: 8px;">
  177. Save Coordinates
  178. </button>
  179. <div class="hint-text" style="margin-top: 12px; font-size: 13px; color: #a0a0a0;">
  180. Enter your location's decimal coordinates, or if you're not comfortable sharing them, use the nearest Roblox server coordinates (e.g., Los Angeles: 34.0549, -118.2426).
  181. </div>
  182. </div>
  183. </div>
  184. </div>
  185. `;
  186. }
  187.  
  188. if (section === "about") {
  189. return `
  190. <div class="about-section">
  191. <div class="section-separator"></div>
  192. <h3 class="red-accent">Credits:</h3>
  193. <p>This project was created by:</p>
  194. <ul>
  195. <li><strong>Developer:</strong> <a href="https://www.roblox.com/users/545334824/profile" target="_blank" style="text-decoration: underline; color: #007bff;">Oqarshi</a></li>
  196. <li><strong>Rolocate Source Code:</strong> <a href="https://greatest.deepsurf.us/en/scripts/523727-rolocate/code" target="_blank" style="text-decoration: underline; color: #007bff;">GreasyFork</a></li>
  197. <li><strong>Invite & FAQ Source Code:</strong> <a href="https://github.com/Oqarshi/Invite" target="_blank" style="text-decoration: underline; color: #007bff;">GitHub</a></li>
  198. <li><strong>FAQ Website:</strong> <a href="https://oqarshi.github.io/Invite/rolocate/index.html" target="_blank" style="text-decoration: underline; color: #007bff;">RoLocate FAQ</a></li>
  199. <li><strong>Suggest or Report Issues:</strong> <a href="https://greatest.deepsurf.us/en/scripts/523727-rolocate/feedback" target="_blank" style="text-decoration: underline; color: #007bff;">Submit Feedback</a></li>
  200. <li><strong>Inspiration:</strong> <a href="https://chromewebstore.google.com/detail/btroblox-making-roblox-be/hbkpclpemjeibhioopcebchdmohaieln" target="_blank" style="text-decoration: underline; color: #007bff;">Btroblox Team</a></li>
  201. </ul>
  202. </div>
  203. `;
  204. }
  205.  
  206. if (section === "help") {
  207. return `
  208. <div class="help-section">
  209. <div class="section-separator"></div>
  210. <h3 class="red-accent">General Tab</h3>
  211. <ul>
  212. <li><strong>Auto Run Server Regions:</strong> <span>Replaces Roblox's 8 default servers with at least 8 servers, providing detailed info such as location and ping.</span></li>
  213. <li><strong>Remove All Roblox Ads:</strong> <span>Blocks most ads on the Roblox site. Still experimental.</span></li>
  214. <li><strong>Recent Servers:</strong> <span>Shows the most recent servers you have joined in the past 3 days.</span></li>
  215. <li><strong>Quick Navigation:</strong> <span>Ability to add quick navigations to the leftside panel of the Roblox page.</span></li>
  216. </ul>
  217.  
  218. <div class="section-separator"></div>
  219.  
  220. <h3 class="red-accent">Appearance Tab</h3>
  221. <ul>
  222. <li><strong>Show Old Greeting:</strong> <span>Shows the old greeting Roblox had on their home page.</span></li>
  223. </ul>
  224.  
  225. <div class="section-separator"></div>
  226.  
  227. <h3 class="red-accent">Advanced Tab</h3>
  228. <ul>
  229. <li><strong>Enable Console Logs:</strong> <span>Enables console.log messages from the script.</span></li>
  230. <li><strong>Enable Server Filters:</strong> <span>Enables server filter features on the game page.</span></li>
  231. <li><strong>Enable Server Hop Button:</strong> <span>Enables server hop feature on the game page.</span></li>
  232. <li><strong>Set default location:</strong> <span>Enables the user to set a default location for Roblox server regions. Turn this on if the script cannot automatically detect your location.</span></li>
  233. </ul>
  234. </div>
  235. `;
  236. }
  237.  
  238. // General tab (default)
  239. return `
  240. <div class="general-section">
  241. <label class="toggle-slider section-hover">
  242. <input type="checkbox" id="AutoRunServerRegions">
  243. <span class="slider"></span>
  244. Auto Run Server Regions
  245. </label>
  246.  
  247. <label class="toggle-slider section-hover">
  248. <input type="checkbox" id="removeads">
  249. <span class="slider"></span>
  250. Remove All Roblox Ads
  251. </label>
  252.  
  253. <label class="toggle-slider section-hover">
  254. <input type="checkbox" id="togglerecentserverbutton">
  255. <span class="slider"></span>
  256. Recent Servers
  257. </label>
  258.  
  259. <div class="quicknav-container section-hover">
  260. <label class="toggle-slider slider-element">
  261. <input type="checkbox" id="quicknav">
  262. <span class="slider"></span>
  263. Quick Navigation
  264. </label>
  265. <button id="edit-quicknav-btn" class="edit-nav-button">
  266. Edit Quick Nav
  267. </button>
  268. </div>
  269. </div>
  270. `;
  271. }
  272.  
  273.  
  274. function openSettingsMenu() {
  275. if (document.getElementById("userscript-settings-menu")) return;
  276.  
  277. // Initialize localStorage with default values if they don't exist
  278. initializeLocalStorage();
  279. initializeCoordinatesStorage();
  280.  
  281. // Create overlay
  282. const overlay = document.createElement("div");
  283. overlay.id = "userscript-settings-menu";
  284. overlay.innerHTML = `
  285. <div class="settings-container">
  286. <button id="close-settings" class="close-hover">✖</button>
  287. <div class="settings-sidebar">
  288. <h2>RoLocate</h2>
  289. <ul>
  290. <li class="active" data-section="home">🏠 Home</li>
  291. <li data-section="general">⚙️ General</li>
  292. <li data-section="appearance">🎨 Appearance</li>
  293. <li data-section="advanced">🚀 Advanced</li>
  294. <li data-section="help">📙 Help</li>
  295. <li data-section="about">ℹ️ About</li>
  296. </ul>
  297. </div>
  298. <div class="settings-content">
  299. <h2 id="settings-title">Home</h2>
  300. <div id="settings-body" class="animated-content">${getSettingsContent("home")}</div>
  301. </div>
  302. </div>
  303. `;
  304.  
  305. document.body.appendChild(overlay);
  306.  
  307. // Inject CSS styles
  308. const style = document.createElement("style");
  309. style.textContent = `
  310. @keyframes fadeIn {
  311. from { opacity: 0; transform: scale(0.96); }
  312. to { opacity: 1; transform: scale(1); }
  313. }
  314.  
  315. @keyframes fadeOut {
  316. from { opacity: 1; transform: scale(1); }
  317. to { opacity: 0; transform: scale(0.96); }
  318. }
  319.  
  320. @keyframes sectionFade {
  321. from { opacity: 0; transform: translateY(12px); }
  322. to { opacity: 1; transform: translateY(0); }
  323. }
  324.  
  325. @keyframes slideIn {
  326. from { transform: translateX(-20px); opacity: 0; }
  327. to { transform: translateX(0); opacity: 1; }
  328. }
  329.  
  330. #userscript-settings-menu {
  331. position: fixed;
  332. top: 0; left: 0;
  333. width: 100vw; height: 100vh;
  334. background: rgba(0,0,0,0.6);
  335. display: flex;
  336. align-items: center;
  337. justify-content: center;
  338. z-index: 10000;
  339. animation: fadeIn 0.7s cubic-bezier(0.19, 1, 0.22, 1);
  340. }
  341.  
  342. .settings-container {
  343. display: flex;
  344. position: relative;
  345. width: 580px; /* Reduced from 680px */
  346. height: 420px; /* Reduced from 480px */
  347. background: linear-gradient(145deg, #1a1a1a, #232323);
  348. border-radius: 12px; /* Slightly smaller radius */
  349. overflow: hidden;
  350. box-shadow: 0 25px 50px -12px rgba(0,0,0,0.7);
  351. font-family: 'Inter', 'Segoe UI', Arial, sans-serif;
  352. border: 1px solid rgba(255, 255, 255, 0.05);
  353. }
  354.  
  355. #close-settings {
  356. position: absolute;
  357. top: 12px; /* Reduced from 16px */
  358. right: 12px; /* Reduced from 16px */
  359. background: transparent;
  360. border: none;
  361. color: #c0c0c0;
  362. font-size: 20px; /* Reduced from 22px */
  363. cursor: pointer;
  364. z-index: 10001;
  365. transition: all 0.5s ease;
  366. width: 30px; /* Reduced from 34px */
  367. height: 30px; /* Reduced from 34px */
  368. border-radius: 50%;
  369. display: flex;
  370. align-items: center;
  371. justify-content: center;
  372. }
  373.  
  374. #close-settings:hover {
  375. color: #ff3b47;
  376. background: rgba(255, 59, 71, 0.1);
  377. transform: rotate(90deg);
  378. }
  379.  
  380. .settings-sidebar {
  381. width: 32%; /* Reduced from 35% */
  382. background: #272727;
  383. padding: 18px 12px; /* Reduced from 24px 15px */
  384. color: white;
  385. display: flex;
  386. flex-direction: column;
  387. align-items: center;
  388. box-shadow: 6px 0 12px -6px rgba(0,0,0,0.3);
  389. position: relative;
  390. overflow: hidden;
  391. }
  392.  
  393. .settings-sidebar h2 {
  394. margin-bottom: 16px; /* Reduced from 20px */
  395. font-weight: 600;
  396. font-size: 22px; /* Reduced from 24px */
  397. text-shadow: 0 1px 3px rgba(0,0,0,0.5);
  398. text-decoration: none;
  399. position: relative;
  400. text-align: center;
  401. }
  402.  
  403. .settings-sidebar h2::after {
  404. content: "";
  405. position: absolute;
  406. left: 50%;
  407. transform: translateX(-50%);
  408. bottom: -6px; /* Reduced from -8px */
  409. width: 36px; /* Reduced from 40px */
  410. height: 3px;
  411. background: white;
  412. border-radius: 2px;
  413. }
  414.  
  415. .settings-sidebar ul {
  416. list-style: none;
  417. padding: 0;
  418. width: 100%;
  419. margin-top: 5px; /* Reduced from 10px */
  420. }
  421.  
  422. .settings-sidebar li {
  423. padding: 10px 12px; /* Reduced from 14px */
  424. margin: 6px 0; /* Reduced from 8px 0 */
  425. text-align: left;
  426. cursor: pointer;
  427. transition: all 0.5s cubic-bezier(0.19, 1, 0.22, 1);
  428. border-radius: 8px;
  429. font-weight: 500;
  430. font-size: 17px; /* increased from 15px */
  431. position: relative;
  432. animation: slideIn 0.5s cubic-bezier(0.19, 1, 0.22, 1);
  433. animation-fill-mode: both;
  434. display: flex;
  435. align-items: center;
  436. }
  437.  
  438. .settings-sidebar li:hover {
  439. background: #444;
  440. transform: translateX(5px);
  441. }
  442.  
  443. .settings-sidebar .active {
  444. background: #444;
  445. color: white;
  446. transform: translateX(0);
  447. }
  448.  
  449. .settings-sidebar .active:hover {
  450. transform: translateX(0);
  451. }
  452.  
  453. .settings-sidebar li:hover::before {
  454. height: 100%;
  455. }
  456.  
  457. .settings-sidebar .active::before {
  458. background: #dc3545;
  459. }
  460.  
  461. /* Custom Scrollbar */
  462. .settings-content {
  463. flex: 1;
  464. padding: 24px; /* Reduced from 32px */
  465. color: white;
  466. text-align: center;
  467. max-height: 100%;
  468. overflow-y: auto;
  469. scrollbar-width: thin;
  470. scrollbar-color: darkgreen black;
  471. background: #1e1e1e;
  472. position: relative;
  473. }
  474.  
  475. /* Webkit (Chrome, Safari) Scrollbar */
  476. .settings-content::-webkit-scrollbar {
  477. width: 6px; /* Reduced from 8px */
  478. }
  479. .settings-content::-webkit-scrollbar-track {
  480. background: #333;
  481. border-radius: 3px; /* Reduced from 4px */
  482. }
  483. .settings-content::-webkit-scrollbar-thumb {
  484. background: linear-gradient(180deg, #dc3545, #b02a37);
  485. border-radius: 3px; /* Reduced from 4px */
  486. }
  487. .settings-content::-webkit-scrollbar-thumb:hover {
  488. background: linear-gradient(180deg, #ff3b47, #dc3545);
  489. }
  490.  
  491. .settings-content h2 {
  492. margin-bottom: 24px; /* Reduced from 30px */
  493. font-weight: 600;
  494. font-size: 22px; /* Reduced from 24px */
  495. color: white;
  496. text-shadow: 0 1px 3px rgba(0,0,0,0.4);
  497. letter-spacing: 0.5px;
  498. position: relative;
  499. display: inline-block;
  500. padding-bottom: 6px; /* Reduced from 8px */
  501. }
  502.  
  503. .settings-content h2::after {
  504. content: "";
  505. position: absolute;
  506. bottom: 0;
  507. left: 0;
  508. width: 100%;
  509. height: 2px;
  510. background: white;
  511. border-radius: 2px;
  512. }
  513.  
  514. .settings-content div {
  515. animation: sectionFade 0.7s cubic-bezier(0.19, 1, 0.22, 1);
  516. }
  517.  
  518. /* Toggle Slider Styles */
  519. .toggle-slider {
  520. display: flex;
  521. align-items: center;
  522. margin: 12px 0; /* Reduced from 16px 0 */
  523. cursor: pointer;
  524. padding: 8px 14px; /* Reduced from 10px 16px */
  525. background: rgba(255, 255, 255, 0.03);
  526. border-radius: 6px; /* Reduced from 8px */
  527. transition: all 0.5s ease;
  528. user-select: none;
  529. border: 1px solid rgba(255, 255, 255, 0.05);
  530. }
  531.  
  532. .toggle-slider:hover {
  533. background: rgba(255, 255, 255, 0.05);
  534. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  535. transform: translateY(-2px);
  536. }
  537.  
  538. .toggle-slider input {
  539. display: none;
  540. }
  541.  
  542. .toggle-slider .slider {
  543. position: relative;
  544. display: inline-block;
  545. width: 42px; /* Reduced from 48px */
  546. height: 22px; /* Reduced from 24px */
  547. background-color: rgba(255, 255, 255, 0.2);
  548. border-radius: 22px;
  549. margin-right: 12px; /* Reduced from 14px */
  550. transition: all 0.5s cubic-bezier(0.19, 1, 0.22, 1);
  551. box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2);
  552. }
  553.  
  554. .toggle-slider .slider::before {
  555. content: "";
  556. position: absolute;
  557. height: 16px; /* Reduced from 18px */
  558. width: 16px; /* Reduced from 18px */
  559. left: 3px;
  560. bottom: 3px;
  561. background-color: white;
  562. border-radius: 50%;
  563. transition: all 0.5s cubic-bezier(0.19, 1, 0.22, 1);
  564. box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
  565. }
  566.  
  567. .toggle-slider input:checked + .slider {
  568. background-color: #4CAF50;
  569. box-shadow: 0 0 0 1px rgba(220, 53, 69, 0.05), inset 0 1px 3px rgba(0, 0, 0, 0.2);
  570. }
  571.  
  572. .toggle-slider input:checked + .slider::before {
  573. transform: translateX(20px); /* Reduced from 24px */
  574. }
  575.  
  576. .toggle-slider input:checked + .slider::after {
  577. opacity: 1;
  578. }
  579.  
  580. .rolocate-logo {
  581. width: 90px !important; /* Reduced from 110px */
  582. height: 90px !important; /* Reduced from 110px */
  583. object-fit: contain;
  584. border-radius: 14px; /* Reduced from 16px */
  585. display: block;
  586. margin: 0 auto 16px auto; /* Reduced from 20px */
  587. box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
  588. transition: all 0.5s ease;
  589. border: 2px solid rgba(220, 53, 69, 0.4);
  590. }
  591.  
  592. .rolocate-logo:hover {
  593. transform: scale(1.05);
  594. }
  595.  
  596. .version {
  597. font-size: 13px; /* Reduced from 14px */
  598. color: #aaa;
  599. margin-bottom: 24px; /* Reduced from 30px */
  600. display: inline-block;
  601. padding: 5px 14px; /* Reduced from 6px 16px */
  602. background: rgba(220, 53, 69, 0.1);
  603. border-radius: 18px; /* Reduced from 20px */
  604. border: 1px solid rgba(220, 53, 69, 0.2);
  605. }
  606.  
  607. .settings-content ul {
  608. text-align: left;
  609. list-style-type: none;
  610. padding: 0;
  611. margin-top: 16px; /* Reduced from 20px */
  612. }
  613.  
  614. .settings-content ul li {
  615. margin: 12px 0; /* Reduced from 16px 0 */
  616. padding: 10px 14px; /* Reduced from 12px 16px */
  617. background: rgba(255, 255, 255, 0.03);
  618. border-radius: 6px; /* Reduced from 8px */
  619. transition: all 0.4s ease;
  620. }
  621.  
  622. .settings-content ul li:hover {
  623. background: rgba(255, 255, 255, 0.05);
  624. border-left: 3px solid #4CAF50;
  625. transform: translateX(5px);
  626. }
  627.  
  628. .settings-content ul li strong {
  629. color: #4CAF50;
  630. }
  631.  
  632. .warning_advanced {
  633. font-size: 14px; /* Reduced from 16px */
  634. color: #ff3b47;
  635. font-weight: bold;
  636. padding: 8px 14px; /* Reduced from 10px 16px */
  637. background: rgba(220, 53, 69, 0.1);
  638. border-radius: 6px;
  639. margin-bottom: 16px; /* Reduced from 20px */
  640. display: inline-block;
  641. border: 1px solid rgba(220, 53, 69, 0.2);
  642. }
  643.  
  644. .average_text {
  645. font-size: 16px; /* Reduced from 18px */
  646. color: #e0e0e0;
  647. font-weight: 500;
  648. margin-top: 12px; /* Reduced from 15px */
  649. line-height: 1.5;
  650. letter-spacing: 0.3px;
  651. background: linear-gradient(90deg, #ff3b47, #ff6b74);
  652. -webkit-background-clip: text;
  653. -webkit-text-fill-color: transparent;
  654. display: inline-block;
  655. }
  656.  
  657. .quicknav-container {
  658. display: flex;
  659. align-items: center;
  660. gap: 12px; /* Reduced from 16px */
  661. margin: 16px 0; /* Reduced from 20px 0 */
  662. flex-wrap: wrap;
  663. }
  664.  
  665. .edit-nav-button {
  666. padding: 6px 14px; /* Reduced from 8px 16px */
  667. background: #4CAF50;
  668. color: white;
  669. border: none;
  670. border-radius: 6px; /* Reduced from 8px */
  671. cursor: pointer;
  672. font-family: 'Inter', 'Helvetica', sans-serif;
  673. font-size: 12px; /* Reduced from 13px */
  674. font-weight: 600;
  675. letter-spacing: 0.5px;
  676. text-transform: uppercase;
  677. transition: all 0.5s cubic-bezier(0.19, 1, 0.22, 1);
  678. height: auto;
  679. line-height: 1.5;
  680. position: relative;
  681. overflow: hidden;
  682. }
  683.  
  684. .edit-nav-button:hover {
  685. transform: translateY(-3px);
  686. background: linear-gradient(135deg, #1e8449 0%, #196f3d 100%);
  687. }
  688.  
  689. .edit-nav-button:hover::before {
  690. left: 100%;
  691. }
  692.  
  693. .edit-nav-button:active {
  694. background: linear-gradient(135deg, #1e8449 0%, #196f3d 100%);
  695. transform: translateY(1px);
  696. }
  697.  
  698. /* Dropdown styling */
  699. select {
  700. width: 100%;
  701. padding: 10px 14px; /* Reduced from 12px 16px */
  702. border-radius: 6px; /* Reduced from 8px */
  703. background: rgba(255, 255, 255, 0.05);
  704. color: #e0e0e0;
  705. font-size: 14px; /* Reduced from 14px */
  706. appearance: none;
  707. background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="%23dc3545" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>');
  708. background-repeat: no-repeat;
  709. background-position: right 14px center;
  710. background-size: 14px;
  711. transition: all 0.5s ease;
  712. cursor: pointer;
  713. box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
  714. border-color: rgba(255, 255, 255, 0.05);
  715. }
  716.  
  717. /* Dropdown hint styling */
  718. #location-hint {
  719. margin-top: 10px; /* Reduced from 12px */
  720. font-size: 12px; /* Reduced from 13px */
  721. color: #c0c0c0;
  722. background: rgba(255, 255, 255, 0.05);
  723. border-radius: 6px; /* Reduced from 8px */
  724. padding: 10px 14px; /* Reduced from 12px 16px */
  725. border: 1px solid rgba(255, 255, 255, 0.05);
  726. line-height: 1.6;
  727. transition: all 0.5s ease;
  728. }
  729.  
  730. /* Section separator */
  731. .section-separator {
  732. width: 100%;
  733. height: 1px;
  734. background: linear-gradient(90deg, transparent, #272727, transparent);
  735. margin: 24px 0; /* Reduced from 30px 0 */
  736. }
  737.  
  738.  
  739. /* Help section styles */
  740. .help-section h3, .about-section h3 {
  741. color: white;
  742. margin-top: 20px; /* Reduced from 25px */
  743. margin-bottom: 12px; /* Reduced from 15px */
  744. font-size: 16px; /* Reduced from 18px */
  745. text-align: left;
  746. }
  747.  
  748. /* Hint text styling */
  749. .hint-text {
  750. font-size: 13px; /* Reduced from 14px */
  751. color: #a0a0a0;
  752. margin-top: 6px; /* Reduced from 8px */
  753. margin-left: 16px; /* Reduced from 20px */
  754. text-align: left;
  755. }
  756.  
  757. /* Location settings styling */
  758. .location-settings {
  759. background: rgba(255, 255, 255, 0.03);
  760. border-radius: 6px; /* Reduced from 8px */
  761. padding: 14px; /* Reduced from 16px */
  762. margin-top: 16px; /* Reduced from 20px */
  763. border: 1px solid rgba(255, 255, 255, 0.05);
  764. transition: all 0.5s ease;
  765. }
  766.  
  767. .setting-header {
  768. display: flex;
  769. justify-content: space-between;
  770. align-items: center;
  771. margin-bottom: 10px; /* Reduced from 12px */
  772. }
  773.  
  774. .setting-header span {
  775. font-size: 14px; /* Reduced from 15px */
  776. font-weight: 500;
  777. }
  778.  
  779. .help-icon {
  780. display: inline-flex;
  781. align-items: center;
  782. justify-content: center;
  783. width: 18px; /* Reduced from 20px */
  784. height: 18px; /* Reduced from 20px */
  785. background: rgba(220, 53, 69, 0.2);
  786. border-radius: 50%;
  787. font-size: 11px; /* Reduced from 12px */
  788. color: #ff3b47;
  789. cursor: help;
  790. transition: all 0.5s ease;
  791. }
  792.  
  793. /* Manual coordinates input styling */
  794. #manual-coordinates {
  795. margin-top: 12px !important; /* Reduced from 15px */
  796. }
  797.  
  798. .coordinates-inputs {
  799. gap: 8px !important; /* Reduced from 10px */
  800. margin-bottom: 10px !important; /* Reduced from 12px */
  801. }
  802.  
  803. #manual-coordinates input {
  804. padding: 8px 10px !important; /* Reduced from 10px 12px */
  805. border-radius: 6px !important; /* Reduced from 8px */
  806. font-size: 13px !important; /* Reduced from default */
  807. }
  808.  
  809. #manual-coordinates label {
  810. margin-bottom: 6px !important; /* Reduced from 8px */
  811. font-size: 13px !important; /* Reduced from 14px */
  812. }
  813.  
  814. #save-coordinates {
  815. margin-top: 6px !important; /* Reduced from 8px */
  816. }
  817.  
  818. /* Animated content */
  819. .animated-content {
  820. animation: sectionFade 0.7s cubic-bezier(0.19, 1, 0.22, 1);
  821. }
  822. `;
  823.  
  824. document.head.appendChild(style);
  825.  
  826. // Enhanced sidebar navigation with animation
  827. document.querySelectorAll(".settings-sidebar li").forEach((li, index) => {
  828. // Add staggered animation delay
  829. li.style.animationDelay = `${0.05 * (index + 1)}s`;
  830.  
  831. li.addEventListener("click", function() {
  832. const currentActive = document.querySelector(".settings-sidebar .active");
  833. if (currentActive) currentActive.classList.remove("active");
  834. this.classList.add("active");
  835.  
  836. const section = this.getAttribute("data-section");
  837. const settingsBody = document.getElementById("settings-body");
  838. const settingsTitle = document.getElementById("settings-title");
  839.  
  840. // Apply fade-out animation
  841. settingsBody.style.opacity = "0";
  842. settingsBody.style.transform = "translateY(10px)";
  843. settingsTitle.style.opacity = "0";
  844. settingsTitle.style.transform = "translateY(10px)";
  845.  
  846. setTimeout(() => {
  847. // Update content
  848. settingsTitle.textContent = section.charAt(0).toUpperCase() + section.slice(1);
  849. settingsBody.innerHTML = getSettingsContent(section);
  850.  
  851. // Show the edit button when Quick Nav is checked
  852. if (section === "general") {
  853. const quickNavCheckbox = document.getElementById("quicknav");
  854. const editButton = document.getElementById("edit-quicknav-btn");
  855.  
  856. if (quickNavCheckbox && editButton) {
  857. // Initialize button visibility
  858. editButton.style.display = localStorage.getItem("ROLOCATE_quicknav") === "true" ? "block" : "none";
  859.  
  860. // Update button visibility when checkbox changes
  861. quickNavCheckbox.addEventListener("change", function() {
  862. editButton.style.display = this.checked ? "block" : "none";
  863. });
  864. }
  865. }
  866.  
  867. // Apply fade-in animation
  868. settingsBody.style.transition = "all 0.4s cubic-bezier(0.19, 1, 0.22, 1)";
  869. settingsTitle.style.transition = "all 0.4s cubic-bezier(0.19, 1, 0.22, 1)";
  870.  
  871. // Trigger reflow to restart animation
  872. void settingsBody.offsetWidth;
  873. void settingsTitle.offsetWidth;
  874.  
  875. settingsBody.style.opacity = "1";
  876. settingsBody.style.transform = "translateY(0)";
  877. settingsTitle.style.opacity = "1";
  878. settingsTitle.style.transform = "translateY(0)";
  879.  
  880. // Apply stored settings to ensure toggles match localStorage
  881. applyStoredSettings();
  882. }, 200);
  883. });
  884. });
  885.  
  886. // Close button with enhanced animation
  887. document.getElementById("close-settings").addEventListener("click", function() {
  888. // Check if manual mode is selected with empty coordinates
  889. const priorityLocation = localStorage.getItem("ROLOCATE_prioritylocation");
  890. if (priorityLocation === "manual") {
  891. try {
  892. const coords = JSON.parse(GM_getValue("ROLOCATE_coordinates", '{"lat":"","lng":""}'));
  893. if (!coords.lat || !coords.lng) {
  894. notifications('Please set the latitude and longitude values for the manual location, or set it to automatic.', 'error', '⚠️', 8000);
  895. return; // Prevent closing
  896. }
  897. } catch (e) {
  898. console.error("Error checking coordinates:", e);
  899. notifications('Error checking location settings', 'error', '⚠️', 8000);
  900. return; // Prevent closing
  901. }
  902. }
  903.  
  904. // Proceed with closing if validation passes
  905. const menu = document.getElementById("userscript-settings-menu");
  906. menu.style.animation = "fadeOut 0.4s cubic-bezier(0.19, 1, 0.22, 1) forwards";
  907.  
  908. // Add rotation to close button when closing
  909. this.style.transform = "rotate(90deg)";
  910.  
  911. setTimeout(() => menu.remove(), 400);
  912. });
  913.  
  914. // Apply stored settings immediately when opened
  915. applyStoredSettings();
  916.  
  917. // Add "Edit Quick Nav" button functionality
  918. setTimeout(() => {
  919. const editButton = document.getElementById("edit-quicknav-btn");
  920. if (editButton) {
  921. // Initialize button visibility
  922. const quickNavEnabled = localStorage.getItem("ROLOCATE_quicknav") === "true";
  923. editButton.style.display = quickNavEnabled ? "block" : "none";
  924.  
  925. // Add click handler for edit button
  926. editButton.addEventListener("click", function() {
  927. // Here you'd open a modal or implement the edit quick nav functionality
  928. alert("Quick Navigation Editor will open here!");
  929. // Alternatively, implement a proper modal for editing quick nav links
  930. });
  931. }
  932. }, 100);
  933.  
  934. // Add ripple effect to buttons
  935. const buttons = document.querySelectorAll(".edit-nav-button, .settings-button");
  936. buttons.forEach(button => {
  937. button.addEventListener("mousedown", function(e) {
  938. const ripple = document.createElement("span");
  939. const rect = this.getBoundingClientRect();
  940.  
  941. const size = Math.max(rect.width, rect.height);
  942. const x = e.clientX - rect.left - size / 2;
  943. const y = e.clientY - rect.top - size / 2;
  944.  
  945. ripple.style.cssText = `
  946. position: absolute;
  947. background: rgba(255,255,255,0.4);
  948. border-radius: 50%;
  949. pointer-events: none;
  950. width: ${size}px;
  951. height: ${size}px;
  952. top: ${y}px;
  953. left: ${x}px;
  954. transform: scale(0);
  955. transition: transform 0.6s, opacity 0.6s;
  956. `;
  957.  
  958. this.appendChild(ripple);
  959.  
  960. setTimeout(() => {
  961. ripple.style.transform = "scale(2)";
  962. ripple.style.opacity = "0";
  963. setTimeout(() => ripple.remove(), 600);
  964. }, 10);
  965. });
  966. });
  967. }
  968.  
  969.  
  970. function showQuickNavPopup() {
  971. // Remove existing quick nav if it exists
  972. const existingNav = document.getElementById("premium-quick-nav");
  973. if (existingNav) existingNav.remove();
  974.  
  975. // POPUP CREATION
  976. // Create overlay
  977. const overlay = document.createElement("div");
  978. overlay.id = "quicknav-overlay";
  979. overlay.style.position = "fixed";
  980. overlay.style.top = "0";
  981. overlay.style.left = "0";
  982. overlay.style.width = "100%";
  983. overlay.style.height = "100%";
  984. overlay.style.backgroundColor = "rgba(0,0,0,0)"; // Darker overlay for dark mode
  985. overlay.style.backdropFilter = "blur(1px)";
  986. overlay.style.zIndex = "10000";
  987. overlay.style.opacity = "0";
  988. overlay.style.transition = "opacity 0.3s ease";
  989.  
  990. // Create popup
  991. const popup = document.createElement("div");
  992. popup.id = "premium-quick-nav-popup";
  993. popup.style.position = "fixed";
  994. popup.style.top = "50%";
  995. popup.style.left = "50%";
  996. popup.style.transform = "translate(-50%, -50%) scale(0.95)";
  997. popup.style.opacity = "0";
  998. popup.style.background = "linear-gradient(145deg, #0a0a0a, #121212)"; // Darker background for dark mode
  999. popup.style.color = "white";
  1000. popup.style.padding = "32px";
  1001. popup.style.borderRadius = "16px";
  1002. popup.style.boxShadow = "0 20px 40px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.05)";
  1003. popup.style.zIndex = "10001";
  1004. popup.style.width = "600px";
  1005. popup.style.maxWidth = "90%";
  1006. popup.style.maxHeight = "85vh";
  1007. popup.style.overflowY = "auto";
  1008. popup.style.transition = "transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.4s ease";
  1009.  
  1010. // Get saved quick navs (if any)
  1011. const saved = JSON.parse(localStorage.getItem("ROLOCATE_quicknav_settings") || "[]");
  1012.  
  1013. // Build header
  1014. const header = `
  1015. <div style="position: relative; margin-bottom: 24px; text-align: center;">
  1016. <h2 style="margin: 0 0 8px; font-size: 28px; font-weight: 600; background: linear-gradient(90deg, #4CAF50, #8BC34A); -webkit-background-clip: text; background-clip: text; color: transparent;">Quick Navigation</h2>
  1017. <p style="margin: 0; font-size: 16px; color: #a0a0a0; font-weight: 300;">Configure up to 9 custom navigation shortcuts</p>
  1018. <div style="width: 60px; height: 4px; background: linear-gradient(90deg, #4CAF50, #8BC34A); margin: 16px auto; border-radius: 2px;"></div>
  1019.  
  1020. <img src="${window.Base64Images.logo}" alt="Logo" style="position: absolute; bottom: -581px; left: 0; height: 40px; margin: 12px; border-radius: 12px; transition: all 0.3s ease-in-out; box-shadow: 0 0 10px rgba(255, 0, 0, 0.6);"
  1021. onmouseover="this.style.transform='scale(1.2)'; this.style.boxShadow='0 0 15px rgba(255, 0, 0, 1)';"
  1022. onmouseout="this.style.transform='scale(1)'; this.style.boxShadow='0 0 10px rgba(255, 0, 0, 0.6)';" />
  1023. </div>
  1024. `;
  1025.  
  1026.  
  1027.  
  1028. // Build inputs for 9 links in a 3x3 grid
  1029. const inputsGrid = `
  1030. <div class="quicknav-inputs-grid" style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 24px;">
  1031. ${Array.from({length: 9}, (_, i) => `
  1032. <div class="quicknav-input-group" style="background: rgba(255,255,255,0.03); padding: 16px; border-radius: 12px; border: 1px solid rgba(255,255,255,0.05);">
  1033. <p style="font-weight: 500; font-size: 14px; margin: 0 0 8px; color: #A5D6A7;">${i + 1}</p>
  1034. <input type="text" id="quicknav-name-${i}" placeholder="Name" value="${saved[i]?.name || ""}"
  1035. style="width: 100%; padding: 10px 12px; margin-bottom: 8px; border-radius: 8px; border: none; background: rgba(255,255,255,0.05); color: white; font-size: 14px; transition: all 0.2s;">
  1036. <input type="text" id="quicknav-link-${i}" placeholder="URL" value="${saved[i]?.link || ""}"
  1037. style="width: 100%; padding: 10px 12px; border-radius: 8px; border: none; background: rgba(255,255,255,0.05); color: white; font-size: 14px; transition: all 0.2s;">
  1038. </div>
  1039. `).join("")}
  1040. </div>
  1041. `;
  1042.  
  1043. // Build footer with buttons
  1044. const footer = `
  1045. <div style="display: flex; justify-content: flex-end; gap: 12px;">
  1046. <button id="cancel-quicknav" style="background: transparent; color: #a0a0a0; border: 1px solid rgba(255,255,255,0.1); padding: 12px 20px; border-radius: 8px; cursor: pointer; font-weight: 500; transition: all 0.2s;">
  1047. Cancel
  1048. </button>
  1049. <button id="save-quicknav" style="background: linear-gradient(90deg, #4CAF50, #388E3C); color: white; border: none; padding: 12px 24px; border-radius: 8px; cursor: pointer; font-weight: 500; box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3); transition: all 0.2s;">
  1050. Save Changes
  1051. </button>
  1052. </div>
  1053. `;
  1054.  
  1055. // Combine all sections
  1056. popup.innerHTML = header + inputsGrid + footer;
  1057.  
  1058. // Add elements to DOM
  1059. document.body.appendChild(overlay);
  1060. document.body.appendChild(popup);
  1061.  
  1062. // POPUP EVENTS
  1063. // Add input hover and focus effects
  1064. popup.querySelectorAll('input').forEach(input => {
  1065. input.addEventListener('focus', () => {
  1066. input.style.background = 'rgba(255,255,255,0.1)';
  1067. input.style.boxShadow = '0 0 0 2px rgba(76, 175, 80, 0.4)';
  1068. });
  1069.  
  1070. input.addEventListener('blur', () => {
  1071. input.style.background = 'rgba(255,255,255,0.05)';
  1072. input.style.boxShadow = 'none';
  1073. });
  1074.  
  1075. input.addEventListener('mouseover', () => {
  1076. if (document.activeElement !== input) {
  1077. input.style.background = 'rgba(255,255,255,0.08)';
  1078. }
  1079. });
  1080.  
  1081. input.addEventListener('mouseout', () => {
  1082. if (document.activeElement !== input) {
  1083. input.style.background = 'rgba(255,255,255,0.05)';
  1084. }
  1085. });
  1086. });
  1087.  
  1088. // Add button hover effects
  1089. const saveBtn = popup.querySelector('#save-quicknav');
  1090. saveBtn.addEventListener('mouseover', () => {
  1091. saveBtn.style.background = 'linear-gradient(90deg, #66BB6A, #4CAF50)';
  1092. saveBtn.style.boxShadow = '0 4px 15px rgba(76, 175, 80, 0.4)';
  1093. saveBtn.style.transform = 'translateY(-1px)';
  1094. });
  1095.  
  1096. saveBtn.addEventListener('mouseout', () => {
  1097. saveBtn.style.background = 'linear-gradient(90deg, #4CAF50, #388E3C)';
  1098. saveBtn.style.boxShadow = '0 4px 12px rgba(76, 175, 80, 0.3)';
  1099. saveBtn.style.transform = 'translateY(0)';
  1100. });
  1101.  
  1102. const cancelBtn = popup.querySelector('#cancel-quicknav');
  1103. cancelBtn.addEventListener('mouseover', () => {
  1104. cancelBtn.style.background = 'rgba(255,255,255,0.05)';
  1105. });
  1106.  
  1107. cancelBtn.addEventListener('mouseout', () => {
  1108. cancelBtn.style.background = 'transparent';
  1109. });
  1110.  
  1111. // Animate in
  1112. setTimeout(() => {
  1113. overlay.style.opacity = "1";
  1114. popup.style.opacity = "1";
  1115. popup.style.transform = "translate(-50%, -50%) scale(1)";
  1116. }, 10);
  1117.  
  1118. // POPUP CLOSE FUNCTION
  1119. function closePopup() {
  1120. overlay.style.opacity = "0";
  1121. popup.style.opacity = "0";
  1122. popup.style.transform = "translate(-50%, -50%) scale(0.95)";
  1123. setTimeout(() => {
  1124. overlay.remove();
  1125. popup.remove();
  1126. }, 300);
  1127. }
  1128.  
  1129. // Save on click
  1130. popup.querySelector("#save-quicknav").addEventListener("click", () => {
  1131. const quickNavSettings = [];
  1132. for (let i = 0; i < 9; i++) {
  1133. const name = document.getElementById(`quicknav-name-${i}`).value.trim();
  1134. const link = document.getElementById(`quicknav-link-${i}`).value.trim();
  1135. if (name && link) {
  1136. quickNavSettings.push({
  1137. name,
  1138. link
  1139. });
  1140. }
  1141. }
  1142. localStorage.setItem("ROLOCATE_quicknav_settings", JSON.stringify(quickNavSettings));
  1143. closePopup();
  1144. });
  1145.  
  1146. // Cancel button
  1147. popup.querySelector("#cancel-quicknav").addEventListener("click", closePopup);
  1148.  
  1149. // Close when clicking overlay
  1150. overlay.addEventListener("click", (e) => {
  1151. if (e.target === overlay) {
  1152. closePopup();
  1153. }
  1154. });
  1155.  
  1156. // Close with ESC key
  1157. document.addEventListener("keydown", function escClose(e) {
  1158. if (e.key === "Escape") {
  1159. closePopup();
  1160. document.removeEventListener("keydown", escClose);
  1161. }
  1162. });
  1163.  
  1164. // AUTO-INIT AND KEYBOARD SHORTCUT
  1165. // Set up keyboard shortcut (Alt+Q)
  1166. document.addEventListener("keydown", function keyboardShortcut(e) {
  1167. if (e.altKey && e.key === "q") {
  1168. showQuickNavPopup();
  1169. }
  1170. });
  1171. }
  1172.  
  1173.  
  1174.  
  1175.  
  1176. function applyStoredSettings() {
  1177. // Handle all checkboxes
  1178. document.querySelectorAll("input[type='checkbox']").forEach(checkbox => {
  1179. const storageKey = `ROLOCATE_${checkbox.id}`;
  1180. const savedValue = localStorage.getItem(storageKey);
  1181. checkbox.checked = savedValue === "true";
  1182.  
  1183. checkbox.addEventListener("change", () => {
  1184. localStorage.setItem(storageKey, checkbox.checked);
  1185.  
  1186. if (checkbox.id === "quicknav") {
  1187. const editBtn = document.getElementById("edit-quicknav-btn");
  1188. if (editBtn) {
  1189. editBtn.style.display = checkbox.checked ? "inline-block" : "none";
  1190. }
  1191. }
  1192. });
  1193.  
  1194. if (checkbox.id === "quicknav" && checkbox.checked) {
  1195. const editBtn = document.getElementById("edit-quicknav-btn");
  1196. if (editBtn) {
  1197. editBtn.style.display = "inline-block";
  1198. }
  1199. }
  1200. });
  1201.  
  1202. // Handle dropdown for prioritylocation-select
  1203. const prioritySelect = document.getElementById("prioritylocation-select");
  1204. if (prioritySelect) {
  1205. const storageKey = "ROLOCATE_prioritylocation";
  1206. const savedValue = localStorage.getItem(storageKey) || "automatic";
  1207. prioritySelect.value = savedValue;
  1208.  
  1209. // Show/hide coordinates inputs based on selected value
  1210. const manualCoordinates = document.getElementById("manual-coordinates");
  1211. if (manualCoordinates) {
  1212. manualCoordinates.style.display = savedValue === "manual" ? "block" : "none";
  1213.  
  1214. // Set input values from stored coordinates if available
  1215. if (savedValue === "manual") {
  1216. try {
  1217. const savedCoords = JSON.parse(GM_getValue("ROLOCATE_coordinates", '{"lat":"","lng":""}'));
  1218. document.getElementById("latitude").value = savedCoords.lat || "";
  1219. document.getElementById("longitude").value = savedCoords.lng || "";
  1220.  
  1221. // If manual mode but no coordinates saved, revert to automatic
  1222. if (!savedCoords.lat || !savedCoords.lng) {
  1223. prioritySelect.value = "automatic";
  1224. localStorage.setItem(storageKey, "automatic");
  1225. manualCoordinates.style.display = "none";
  1226. }
  1227. } catch (e) {
  1228. console.error("Error loading saved coordinates:", e);
  1229. }
  1230. }
  1231. }
  1232.  
  1233. prioritySelect.addEventListener("change", () => {
  1234. const newValue = prioritySelect.value;
  1235. localStorage.setItem(storageKey, newValue);
  1236.  
  1237. // Show/hide coordinates inputs based on new value
  1238. if (manualCoordinates) {
  1239. manualCoordinates.style.display = newValue === "manual" ? "block" : "none";
  1240.  
  1241. // When switching to manual mode, load any saved coordinates
  1242. if (newValue === "manual") {
  1243. try {
  1244. const savedCoords = JSON.parse(GM_getValue("ROLOCATE_coordinates", '{"lat":"","lng":""}'));
  1245. document.getElementById("latitude").value = savedCoords.lat || "";
  1246. document.getElementById("longitude").value = savedCoords.lng || "";
  1247.  
  1248. // If no coordinates exist, keep the inputs empty
  1249. } catch (e) {
  1250. console.error("Error loading saved coordinates:", e);
  1251. }
  1252. }
  1253. }
  1254. });
  1255. }
  1256.  
  1257. // Button click handlers
  1258. const editQuickNavBtn = document.getElementById("edit-quicknav-btn");
  1259. if (editQuickNavBtn) {
  1260. editQuickNavBtn.addEventListener("click", () => {
  1261. showQuickNavPopup();
  1262. });
  1263. }
  1264.  
  1265. // Save coordinates button handler
  1266. const saveCoordinatesBtn = document.getElementById("save-coordinates");
  1267. if (saveCoordinatesBtn) {
  1268. saveCoordinatesBtn.addEventListener("click", () => {
  1269. const latInput = document.getElementById("latitude");
  1270. const lngInput = document.getElementById("longitude");
  1271. const lat = latInput.value.trim();
  1272. const lng = lngInput.value.trim();
  1273.  
  1274. // If manual mode but no coordinates provided, revert to automatic
  1275. if (!lat || !lng) {
  1276. const prioritySelect = document.getElementById("prioritylocation-select");
  1277. if (prioritySelect) {
  1278. prioritySelect.value = "automatic";
  1279. localStorage.setItem("ROLOCATE_prioritylocation", "automatic");
  1280. document.getElementById("manual-coordinates").style.display = "none";
  1281.  
  1282. // show feedback to user even if they dont see it
  1283. saveCoordinatesBtn.textContent = "Reverted to Automatic!";
  1284. saveCoordinatesBtn.style.background = "#4CAF50";
  1285.  
  1286. setTimeout(() => {
  1287. saveCoordinatesBtn.textContent = "Save Coordinates";
  1288. saveCoordinatesBtn.style.background = "background: #4CAF50;";
  1289. }, 2000);
  1290. }
  1291. return;
  1292. }
  1293.  
  1294. // Validate coordinates
  1295. const latNum = parseFloat(lat);
  1296. const lngNum = parseFloat(lng);
  1297. if (isNaN(latNum) || isNaN(lngNum) || latNum < -90 || latNum > 90 || lngNum < -180 || lngNum > 180) {
  1298. alert("Invalid coordinates! Latitude must be between -90 and 90, and longitude between -180 and 180.");
  1299. return;
  1300. }
  1301.  
  1302. // Save valid coordinates
  1303. const coordinates = {
  1304. lat,
  1305. lng
  1306. };
  1307. GM_setValue("ROLOCATE_coordinates", JSON.stringify(coordinates));
  1308.  
  1309. // Ensure we're in manual mode
  1310. localStorage.setItem("ROLOCATE_prioritylocation", "manual");
  1311. if (prioritySelect) {
  1312. prioritySelect.value = "manual";
  1313. }
  1314.  
  1315. // Provide feedback
  1316. saveCoordinatesBtn.textContent = "Saved!";
  1317. saveCoordinatesBtn.style.background = "linear-gradient(135deg, #1e8449 0%, #196f3d 100%);";
  1318.  
  1319. setTimeout(() => {
  1320. saveCoordinatesBtn.textContent = "Save Coordinates";
  1321. saveCoordinatesBtn.style.background = "background: #4CAF50;";
  1322. }, 2000);
  1323. });
  1324. }
  1325. }
  1326.  
  1327.  
  1328.  
  1329.  
  1330. function AddSettingsButton() {
  1331. const base64Logo = window.Base64Images.logo;
  1332. const navbarGroup = document.querySelector('.nav.navbar-right.rbx-navbar-icon-group');
  1333. if (!navbarGroup || document.getElementById('custom-logo')) return;
  1334.  
  1335. const li = document.createElement('li');
  1336. li.id = 'custom-logo-container';
  1337. li.style.position = 'relative';
  1338.  
  1339. li.innerHTML = `
  1340. <img id="custom-logo"
  1341. style="
  1342. margin-top: 6px;
  1343. margin-left: 6px;
  1344. width: 26px;
  1345. cursor: pointer;
  1346. border-radius: 4px;
  1347. transition: all 0.2s ease-in-out;
  1348. "
  1349. src="${base64Logo}">
  1350. <span id="custom-tooltip"
  1351. style="
  1352. visibility: hidden;
  1353. background-color: black;
  1354. color: white;
  1355. text-align: center;
  1356. padding: 5px;
  1357. border-radius: 5px;
  1358. position: absolute;
  1359. top: 35px;
  1360. left: 50%;
  1361. transform: translateX(-50%);
  1362. white-space: nowrap;
  1363. font-size: 12px;
  1364. opacity: 0;
  1365. transition: opacity 0.2s ease-in-out;
  1366. ">
  1367. Settings
  1368. </span>
  1369. `;
  1370.  
  1371. const logo = li.querySelector('#custom-logo');
  1372. const tooltip = li.querySelector('#custom-tooltip');
  1373.  
  1374. logo.addEventListener('click', () => openSettingsMenu());
  1375.  
  1376. logo.addEventListener('mouseover', () => {
  1377. logo.style.width = '30px';
  1378. logo.style.border = '2px solid white';
  1379. tooltip.style.visibility = 'visible';
  1380. tooltip.style.opacity = '1';
  1381. });
  1382.  
  1383. logo.addEventListener('mouseout', () => {
  1384. logo.style.width = '26px';
  1385. logo.style.border = 'none';
  1386. tooltip.style.visibility = 'hidden';
  1387. tooltip.style.opacity = '0';
  1388. });
  1389.  
  1390. navbarGroup.appendChild(li);
  1391. }
  1392.  
  1393.  
  1394.  
  1395.  
  1396. /*************************************************************************
  1397. Premium Notification System
  1398. *************************************************************************/
  1399. function notifications(message, type = 'info', emoji = '', duration = 3000) {
  1400. // Helper function to manipulate colors - supports hex, rgb, and rgba
  1401. function adjustColor(color, percent) {
  1402. // Handle hex colors
  1403. if (color.startsWith('#')) {
  1404. let num = parseInt(color.slice(1), 16),
  1405. amt = Math.round(2.55 * percent),
  1406. R = (num >> 16) + amt,
  1407. G = ((num >> 8) & 0xFF) + amt,
  1408. B = (num & 0xFF) + amt;
  1409. R = Math.max(Math.min(255, R), 0);
  1410. G = Math.max(Math.min(255, G), 0);
  1411. B = Math.max(Math.min(255, B), 0);
  1412. return "#" + ((1 << 24) + (R << 16) + (G << 8) + B).toString(16).slice(1);
  1413. }
  1414. // Handle rgb/rgba colors
  1415. else if (color.startsWith('rgb')) {
  1416. const isRGBA = color.startsWith('rgba');
  1417. const parts = color.match(/\d+(\.\d+)?/g).map(Number);
  1418.  
  1419. for (let i = 0; i < 3; i++) {
  1420. parts[i] = Math.max(0, Math.min(255, parts[i] + (2.55 * percent)));
  1421. }
  1422.  
  1423. return isRGBA ?
  1424. `rgba(${parts[0]}, ${parts[1]}, ${parts[2]}, ${parts[3]})` :
  1425. `rgb(${parts[0]}, ${parts[1]}, ${parts[2]})`;
  1426. }
  1427.  
  1428. return color; // Return original if format not recognized
  1429. }
  1430.  
  1431. // Inject CSS styles for the toast system once
  1432. if (!document.getElementById('premium-toast-styles')) {
  1433. const style = document.createElement('style');
  1434. style.id = 'premium-toast-styles';
  1435. style.innerHTML = `
  1436. @keyframes toast-slide-in {
  1437. 0% { opacity: 0; transform: translateX(50px); }
  1438. 100% { opacity: 1; transform: translateX(0); }
  1439. }
  1440.  
  1441. @keyframes toast-slide-out {
  1442. 0% { opacity: 1; transform: translateX(0); }
  1443. 100% { opacity: 0; transform: translateX(50px); }
  1444. }
  1445.  
  1446. @keyframes progress-shrink {
  1447. 0% { width: 100%; }
  1448. 100% { width: 0%; }
  1449. }
  1450.  
  1451. @keyframes emoji-pop {
  1452. 0% { transform: scale(0.8); opacity: 0.7; }
  1453. 40% { transform: scale(1.3); opacity: 1; }
  1454. 60% { transform: scale(0.9); opacity: 0.95; }
  1455. 80% { transform: scale(1.1); opacity: 1; }
  1456. 100% { transform: scale(1); opacity: 1; }
  1457. }
  1458.  
  1459. @keyframes emoji-float {
  1460. 0% { transform: translateY(0); }
  1461. 50% { transform: translateY(-4px); }
  1462. 100% { transform: translateY(0); }
  1463. }
  1464.  
  1465. @keyframes emoji-glow {
  1466. 0% { text-shadow: 0 0 5px rgba(255,255,255,0); }
  1467. 50% { text-shadow: 0 0 10px rgba(255,255,255,0.5); }
  1468. 100% { text-shadow: 0 0 5px rgba(255,255,255,0); }
  1469. }
  1470.  
  1471. #toast-container {
  1472. position: fixed;
  1473. top: 24px;
  1474. right: 24px;
  1475. z-index: 999999;
  1476. display: flex;
  1477. flex-direction: column;
  1478. gap: 12px;
  1479. pointer-events: none;
  1480. }
  1481.  
  1482. .toast {
  1483. position: relative;
  1484. min-width: 320px;
  1485. max-width: 420px;
  1486. padding: 16px 20px;
  1487. border-radius: 12px;
  1488. box-shadow: 0 8px 20px rgba(0,0,0,0.18), 0 2px 8px rgba(0,0,0,0.15), 0 0 1px rgba(255,255,255,0.2);
  1489. animation: toast-slide-in 0.5s cubic-bezier(0.25, 1, 0.5, 1) forwards;
  1490. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
  1491. backdrop-filter: blur(10px);
  1492. word-wrap: break-word;
  1493. pointer-events: auto;
  1494. overflow: hidden;
  1495. display: flex;
  1496. flex-direction: column;
  1497. }
  1498.  
  1499. .toast.removing {
  1500. animation: toast-slide-out 0.5s cubic-bezier(0.55, 0, 0.1, 1) forwards;
  1501. }
  1502.  
  1503. .toast .toast-content {
  1504. display: flex;
  1505. align-items: center;
  1506. gap: 12px;
  1507. color: white;
  1508. font-size: 15px;
  1509. line-height: 1.5;
  1510. font-weight: 500;
  1511. letter-spacing: 0.2px;
  1512. }
  1513.  
  1514. .toast-emoji-wrapper {
  1515. position: relative;
  1516. display: flex;
  1517. justify-content: center;
  1518. align-items: center;
  1519. width: 32px;
  1520. height: 32px;
  1521. }
  1522.  
  1523. .toast-emoji {
  1524. font-size: 22px;
  1525. position: relative;
  1526. display: inline-block;
  1527. animation: emoji-pop 0.6s ease-out, emoji-float 3s ease-in-out infinite, emoji-glow 2s ease-in-out infinite;
  1528. transform-origin: center;
  1529. z-index: 2;
  1530. }
  1531.  
  1532. .toast .message {
  1533. flex: 1;
  1534. }
  1535.  
  1536. .toast-close-btn {
  1537. position: absolute;
  1538. top: 12px;
  1539. right: 12px;
  1540. width: 20px;
  1541. height: 20px;
  1542. cursor: pointer;
  1543. border-radius: 50%;
  1544. display: flex;
  1545. align-items: center;
  1546. justify-content: center;
  1547. background: rgba(255, 255, 255, 0.15);
  1548. transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
  1549. border: 1px solid rgba(255, 255, 255, 0.2);
  1550. }
  1551.  
  1552. .toast-close-btn:before, .toast-close-btn:after {
  1553. content: '';
  1554. position: absolute;
  1555. width: 12px;
  1556. height: 2px;
  1557. background: rgba(255, 255, 255, 0.9);
  1558. border-radius: 1px;
  1559. transition: all 0.3s ease;
  1560. }
  1561.  
  1562. .toast-close-btn:before {
  1563. transform: rotate(45deg);
  1564. }
  1565.  
  1566. .toast-close-btn:after {
  1567. transform: rotate(-45deg);
  1568. }
  1569.  
  1570. .toast-close-btn:hover {
  1571. background: rgba(255, 255, 255, 0.25);
  1572. transform: scale(1.1) rotate(90deg);
  1573. box-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
  1574. }
  1575.  
  1576. .toast-close-btn:hover:before, .toast-close-btn:hover:after {
  1577. background: rgba(255, 255, 255, 1);
  1578. }
  1579.  
  1580. .toast .progress-bar-container {
  1581. position: absolute;
  1582. bottom: 0;
  1583. left: 0;
  1584. height: 4px;
  1585. width: 100%;
  1586. background-color: rgba(255, 255, 255, 0.2);
  1587. overflow: hidden;
  1588. }
  1589.  
  1590. .toast .progress-bar {
  1591. height: 100%;
  1592. width: 100%;
  1593. background: linear-gradient(90deg, rgba(255,255,255,0.5), rgba(255,255,255,0.9));
  1594. animation-name: progress-shrink;
  1595. animation-timing-function: linear;
  1596. animation-fill-mode: forwards;
  1597. box-shadow: 0 0 8px rgba(255, 255, 255, 0.5);
  1598. }
  1599.  
  1600. .toast-icon {
  1601. width: 24px;
  1602. height: 24px;
  1603. display: flex;
  1604. align-items: center;
  1605. justify-content: center;
  1606. border-radius: 50%;
  1607. background: rgba(255, 255, 255, 0.25);
  1608. flex-shrink: 0;
  1609. box-shadow: 0 0 8px rgba(255, 255, 255, 0.2);
  1610. }
  1611.  
  1612. .toast.success {
  1613. background: linear-gradient(135deg, #43A047, #66BB6A);
  1614. border-left: 4px solid #2E7D32;
  1615. }
  1616.  
  1617. .toast.error {
  1618. background: linear-gradient(135deg, #E53935, #EF5350);
  1619. border-left: 4px solid #C62828;
  1620. }
  1621.  
  1622. .toast.info {
  1623. background: linear-gradient(135deg, #1E88E5, #42A5F5);
  1624. border-left: 4px solid #1565C0;
  1625. }
  1626.  
  1627. .toast.warning {
  1628. background: linear-gradient(135deg, #FB8C00, #FFA726);
  1629. border-left: 4px solid #EF6C00;
  1630. }
  1631. `;
  1632. document.head.appendChild(style);
  1633. }
  1634.  
  1635. // Create or get the container
  1636. let container = document.getElementById('toast-container');
  1637. if (!container) {
  1638. container = document.createElement('div');
  1639. container.id = 'toast-container';
  1640. document.body.appendChild(container);
  1641. }
  1642.  
  1643. // Create toast element
  1644. const toast = document.createElement('div');
  1645. toast.className = `toast ${type.toLowerCase()}`;
  1646.  
  1647. // Create content wrapper with optional emoji and icon
  1648. const content = document.createElement('div');
  1649. content.className = 'toast-content';
  1650.  
  1651. // Add type-specific icon
  1652. const icon = document.createElement('div');
  1653. icon.className = 'toast-icon';
  1654.  
  1655. // Set icon content based on type
  1656. let iconContent = '';
  1657. switch (type.toLowerCase()) {
  1658. case 'success':
  1659. iconContent = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>';
  1660. break;
  1661. case 'error':
  1662. iconContent = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>';
  1663. break;
  1664. case 'warning':
  1665. iconContent = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>';
  1666. break;
  1667. case 'info':
  1668. default:
  1669. iconContent = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>';
  1670. break;
  1671. }
  1672.  
  1673. icon.innerHTML = iconContent;
  1674. content.appendChild(icon);
  1675.  
  1676. // Add emoji if provided with enhanced animations
  1677. if (emoji) {
  1678. const emojiWrapper = document.createElement('div');
  1679. emojiWrapper.className = 'toast-emoji-wrapper';
  1680.  
  1681. const emojiSpan = document.createElement('span');
  1682. emojiSpan.className = 'toast-emoji';
  1683. emojiSpan.textContent = emoji;
  1684.  
  1685. emojiWrapper.appendChild(emojiSpan);
  1686. content.appendChild(emojiWrapper);
  1687. }
  1688.  
  1689. // Add message
  1690. const messageSpan = document.createElement('span');
  1691. messageSpan.className = 'message';
  1692. messageSpan.textContent = message;
  1693. content.appendChild(messageSpan);
  1694.  
  1695. toast.appendChild(content);
  1696.  
  1697. // Create the enhanced close button (X)
  1698. const closeBtn = document.createElement('div');
  1699. closeBtn.className = 'toast-close-btn';
  1700. closeBtn.addEventListener('click', () => removeToast(toast));
  1701. toast.appendChild(closeBtn);
  1702.  
  1703. // Create progress bar container and progress bar
  1704. const progressBarContainer = document.createElement('div');
  1705. progressBarContainer.className = 'progress-bar-container';
  1706.  
  1707. const progressBar = document.createElement('div');
  1708. progressBar.className = 'progress-bar';
  1709. progressBar.style.animationDuration = `${duration}ms`;
  1710.  
  1711. progressBarContainer.appendChild(progressBar);
  1712. toast.appendChild(progressBarContainer);
  1713.  
  1714. // Append toast to container
  1715. container.appendChild(toast);
  1716.  
  1717. // Auto-remove toast after the specified duration
  1718. const removeTimeout = setTimeout(() => removeToast(toast), duration);
  1719. let removeTimeoutRef = removeTimeout;
  1720.  
  1721. // Add hover pause functionality
  1722. toast.addEventListener('mouseenter', () => {
  1723. // Pause the progress bar animation
  1724. progressBar.style.animationPlayState = 'paused';
  1725. clearTimeout(removeTimeoutRef);
  1726.  
  1727. // Subtle scale effect on hover
  1728. toast.style.transform = 'scale(1.02)';
  1729. toast.style.transition = 'transform 0.3s ease';
  1730. });
  1731.  
  1732. toast.addEventListener('mouseleave', () => {
  1733. // Resume the progress bar animation
  1734. progressBar.style.animationPlayState = 'running';
  1735. // Reset scale
  1736. toast.style.transform = 'scale(1)';
  1737.  
  1738. // Calculate remaining time based on progress bar width percentage
  1739. const remainingPercentage = progressBar.offsetWidth / progressBarContainer.offsetWidth;
  1740. const remainingTime = duration * remainingPercentage;
  1741. // Set new timeout with remaining time
  1742. clearTimeout(removeTimeoutRef);
  1743. removeTimeoutRef = setTimeout(() => removeToast(toast), remainingTime);
  1744. });
  1745.  
  1746. // Function to fade out and remove toast
  1747. function removeToast(toastEl) {
  1748. clearTimeout(removeTimeoutRef);
  1749. toastEl.classList.add('removing');
  1750. setTimeout(() => toastEl.remove(), 500);
  1751. }
  1752.  
  1753. // Return an object with methods to control the toast
  1754. return {
  1755. remove: () => removeToast(toast),
  1756. update: (newMessage) => {
  1757. messageSpan.textContent = newMessage;
  1758. },
  1759. setType: (newType) => {
  1760. toast.className = `toast ${newType.toLowerCase()}`;
  1761. },
  1762. setDuration: (newDuration) => {
  1763. clearTimeout(removeTimeoutRef);
  1764. // Reset the progress bar animation
  1765. progressBar.style.animation = 'none';
  1766. setTimeout(() => {
  1767. progressBar.style.animation = `progress-shrink ${newDuration}ms linear forwards`;
  1768. removeTimeoutRef = setTimeout(() => removeToast(toast), newDuration);
  1769. }, 10);
  1770. },
  1771. updateEmoji: (newEmoji) => {
  1772. if (emoji) {
  1773. const emojiElement = toast.querySelector('.toast-emoji');
  1774. if (emojiElement) {
  1775. // Reset animation by cloning and replacing
  1776. const parent = emojiElement.parentNode;
  1777. const newEmojiElement = emojiElement.cloneNode(true);
  1778. newEmojiElement.textContent = newEmoji;
  1779. parent.replaceChild(newEmojiElement, emojiElement);
  1780. }
  1781. }
  1782. }
  1783. };
  1784. }
  1785.  
  1786.  
  1787. function Update_Popup() {
  1788. const VERSION = "V35.3";
  1789. const PREV_VERSION = "V34.3";
  1790.  
  1791. // Check if a version other than V35.3 exists and show the popup
  1792. const currentVersion = localStorage.getItem('version') || "V0.0"; // Get saved version or default to "V0.0"
  1793. if (currentVersion !== VERSION) {
  1794. localStorage.setItem('version', VERSION); // Set the new version
  1795. } else {
  1796. return; // If the current version is the latest, do not show the popup
  1797. }
  1798.  
  1799. // Remove any previous version flag if present
  1800. if (localStorage.getItem(PREV_VERSION)) {
  1801. localStorage.removeItem(PREV_VERSION);
  1802. }
  1803.  
  1804. const css = `
  1805. .first-time-popup {
  1806. display: flex;
  1807. position: fixed;
  1808. inset: 0;
  1809. background: rgba(0, 0, 0, 0.25); /* Increased opacity for darker background without blur */
  1810. justify-content: center;
  1811. align-items: center;
  1812. z-index: 1000;
  1813. opacity: 0;
  1814. animation: fadeIn 0.5s ease-in-out forwards;
  1815. }
  1816. .first-time-popup-content {
  1817. background: linear-gradient(135deg, rgba(30, 30, 40, 0.95) 0%, rgba(15, 15, 25, 0.98) 100%);
  1818. border-radius: 24px;
  1819. padding: 35px;
  1820. width: 450px;
  1821. max-width: 90%;
  1822. box-shadow: 0 15px 40px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.1);
  1823. text-align: center;
  1824. color: #fff;
  1825. transform: scale(0.85);
  1826. animation: scaleUp 0.6s cubic-bezier(0.165, 0.84, 0.44, 1) forwards;
  1827. position: relative;
  1828. overflow: hidden;
  1829. }
  1830. .first-time-popup-content::before {
  1831. content: '';
  1832. position: absolute;
  1833. top: 0;
  1834. left: 0;
  1835. right: 0;
  1836. height: 3px;
  1837. background: linear-gradient(90deg, #4da6ff, #9966ff, #4da6ff);
  1838. background-size: 200% 100%;
  1839. animation: shimmer 3s infinite linear;
  1840. }
  1841. .popup-header {
  1842. font-size: 24px;
  1843. font-weight: 800;
  1844. color: #fff;
  1845. text-transform: uppercase;
  1846. letter-spacing: 1.5px;
  1847. margin-bottom: 8px;
  1848. text-align: center;
  1849. text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
  1850. }
  1851. .popup-version {
  1852. font-size: 18px;
  1853. font-weight: bold;
  1854. color: #ffcc00;
  1855. margin-bottom: 20px;
  1856. display: inline-block;
  1857. padding: 5px 15px;
  1858. border-radius: 20px;
  1859. background: rgba(255, 204, 0, 0.1);
  1860. box-shadow: 0 0 0 1px rgba(255, 204, 0, 0.3);
  1861. }
  1862. .popup-info {
  1863. font-size: 15px;
  1864. color: #e0e0e0;
  1865. margin-bottom: 25px;
  1866. line-height: 1.7;
  1867. padding: 18px;
  1868. border-radius: 16px;
  1869. background: rgba(255, 255, 255, 0.03);
  1870. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1), inset 0 0 0 1px rgba(255, 255, 255, 0.05);
  1871. }
  1872. .popup-info p {
  1873. margin: 12px 0;
  1874. }
  1875. .popup-info a {
  1876. color: #4da6ff;
  1877. text-decoration: none;
  1878. font-weight: bold;
  1879. transition: all 0.3s ease;
  1880. padding: 2px 5px;
  1881. border-radius: 4px;
  1882. background: rgba(77, 166, 255, 0.1);
  1883. }
  1884. .popup-info a:hover {
  1885. color: #80bfff;
  1886. text-decoration: none;
  1887. background: rgba(77, 166, 255, 0.2);
  1888. box-shadow: 0 0 0 1px rgba(77, 166, 255, 0.3);
  1889. }
  1890. .popup-footer {
  1891. font-size: 14px;
  1892. color: rgba(255, 255, 255, 0.6);
  1893. font-weight: 500;
  1894. margin-top: 15px;
  1895. transition: opacity 0.4s ease-out;
  1896. padding: 8px;
  1897. border-radius: 8px;
  1898. background: rgba(0, 0, 0, 0.2);
  1899. }
  1900. .popup-footer.hidden {
  1901. opacity: 0;
  1902. visibility: hidden;
  1903. }
  1904. .popup-note {
  1905. font-size: 13px;
  1906. font-weight: bold;
  1907. color: #ff6666;
  1908. margin-top: 12px;
  1909. }
  1910. .popup-logo {
  1911. display: block;
  1912. margin: 0 auto 20px;
  1913. width: 90px;
  1914. height: auto;
  1915. border-radius: 18px;
  1916. box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.1);
  1917. transform: translateY(0);
  1918. transition: transform 0.3s ease;
  1919. }
  1920. .popup-logo:hover {
  1921. transform: translateY(-3px);
  1922. }
  1923. .developer-message {
  1924. display: inline-block;
  1925. padding: 10px 15px;
  1926. margin: 10px 0;
  1927. background: rgba(40, 167, 69, 0.1);
  1928. border-left: 3px solid #28a745;
  1929. color: #bfffca;
  1930. border-radius: 3px;
  1931. font-weight: 500;
  1932. text-align: left;
  1933. line-height: 1.5;
  1934. }
  1935. .feature-item {
  1936. display: flex;
  1937. align-items: center;
  1938. margin: 12px 0;
  1939. text-align: left;
  1940. }
  1941. .feature-icon {
  1942. margin-right: 10px;
  1943. color: #4da6ff;
  1944. font-size: 18px;
  1945. }
  1946. .feature-highlight {
  1947. display: inline-block;
  1948. padding: 2px 8px;
  1949. background: rgba(77, 166, 255, 0.15);
  1950. border-radius: 4px;
  1951. color: #ffffff;
  1952. font-weight: bold;
  1953. }
  1954. .first-time-popup-close {
  1955. position: absolute;
  1956. top: 15px;
  1957. right: 20px;
  1958. font-size: 26px;
  1959. font-weight: bold;
  1960. cursor: pointer;
  1961. color: rgba(255, 255, 255, 0.6);
  1962. opacity: 0.4;
  1963. transition: all 0.3s ease;
  1964. pointer-events: none;
  1965. width: 30px;
  1966. height: 30px;
  1967. display: flex;
  1968. align-items: center;
  1969. justify-content: center;
  1970. border-radius: 50%;
  1971. }
  1972. .first-time-popup-close.active {
  1973. opacity: 1;
  1974. pointer-events: auto;
  1975. background: rgba(255, 255, 255, 0.05);
  1976. }
  1977. .first-time-popup-close:hover {
  1978. color: #ff4d4d;
  1979. transform: rotate(90deg);
  1980. background: rgba(255, 77, 77, 0.1);
  1981. }
  1982. @keyframes fadeIn {
  1983. from { opacity: 0; }
  1984. to { opacity: 1; }
  1985. }
  1986. @keyframes fadeOut {
  1987. from { opacity: 1; }
  1988. to { opacity: 0; }
  1989. }
  1990. @keyframes scaleUp {
  1991. 0% { transform: scale(0.85); }
  1992. 70% { transform: scale(1.03); }
  1993. 100% { transform: scale(1); }
  1994. }
  1995. @keyframes scaleDown {
  1996. from { transform: scale(1); }
  1997. to { transform: scale(0.85); opacity: 0; }
  1998. }
  1999. @keyframes shimmer {
  2000. 0% { background-position: 0% 0; }
  2001. 100% { background-position: 200% 0; }
  2002. }
  2003. `;
  2004.  
  2005. const style = document.createElement('style');
  2006. style.type = 'text/css';
  2007. style.innerHTML = css;
  2008. document.head.appendChild(style);
  2009.  
  2010. const popupHTML = `
  2011. <div class="first-time-popup">
  2012. <div class="first-time-popup-content">
  2013. <span class="first-time-popup-close">&times;</span>
  2014. <img class="popup-logo" src="${window.Base64Images.logo}" alt="Rolocate Logo">
  2015. <div class="popup-header">Rolocate Update</div>
  2016. <div class="popup-version">${VERSION}</div>
  2017. <div class="popup-info">
  2018. <div class="developer-message">
  2019. <span style="font-weight: bold;">From the Developer:</span> Please report any issues on GreasyFork if something breaks! Thank you for your support.
  2020. </div>
  2021.  
  2022. <div class="feature-item">
  2023. <span class="feature-icon">✨</span>
  2024. <div>Settings menu slightly revamped</div>
  2025. </div>
  2026.  
  2027. <div class="feature-item">
  2028. <span class="feature-icon">🆕</span>
  2029. <div>New in Advanced Settings: <span class="feature-highlight">Set Default Location Mode</span> - Use <span class="feature-highlight">Manual</span> if automatic location detection isn't working</div>
  2030. </div>
  2031.  
  2032. <div class="feature-item">
  2033. <span class="feature-icon">🕷</span>
  2034. <div>Fixed a bug that infinitly checks for location. Issue at <a href="https://greatest.deepsurf.us/en/scripts/523727-rolocate/discussions/297042" target="_blank">Greasyfork.</a></div>
  2035. </div>
  2036.  
  2037. <div class="feature-item">
  2038. <span class="feature-icon">📚</span>
  2039. <div>Need help? Check out our <a href="https://oqarshi.github.io/Invite/rolocate/index.html" target="_blank">FAQ page</a> or create an issue on greasyfork! 😊</div>
  2040. </div>
  2041.  
  2042. <p style="margin-top: 15px; text-align: center; opacity: 0.8;">This message will not appear again until the next update.</p>
  2043. </div>
  2044. <div class="popup-footer">Closing enabled in <span id="countdown-timer"><strong>3</strong></span> seconds...</div>
  2045. </div>
  2046. </div>
  2047. `;
  2048.  
  2049. const popupContainer = document.createElement('div');
  2050. popupContainer.innerHTML = popupHTML;
  2051. document.body.appendChild(popupContainer);
  2052.  
  2053. const closeButton = document.querySelector('.first-time-popup-close');
  2054. const popup = document.querySelector('.first-time-popup');
  2055. const countdownTimer = document.getElementById('countdown-timer');
  2056. const footer = document.querySelector('.popup-footer');
  2057.  
  2058. let countdown = 3;
  2059. const countdownInterval = setInterval(() => {
  2060. countdown--;
  2061. countdownTimer.innerHTML = `<strong>${countdown}</strong>`;
  2062.  
  2063. if (countdown <= 0) {
  2064. clearInterval(countdownInterval);
  2065. closeButton.classList.add('active');
  2066. footer.classList.add('hidden');
  2067. }
  2068. }, 1000);
  2069.  
  2070. closeButton.addEventListener('click', () => {
  2071. popup.style.animation = 'fadeOut 0.4s ease-in-out forwards';
  2072. document.querySelector('.first-time-popup-content').style.animation = 'scaleDown 0.4s ease-in-out forwards';
  2073. setTimeout(() => {
  2074. popup.remove();
  2075. }, 400);
  2076. });
  2077. }
  2078.  
  2079.  
  2080. function removeAds() {
  2081. if (localStorage.getItem("ROLOCATE_removeads") !== "true") {
  2082. return;
  2083. }
  2084.  
  2085. const iframeSelector = `.ads-container iframe,.abp iframe,.abp-spacer iframe,.abp-container iframe,.top-abp-container iframe,
  2086. #AdvertisingLeaderboard iframe,#AdvertisementRight iframe,#MessagesAdSkyscraper iframe,.Ads_WideSkyscraper iframe,
  2087. .profile-ads-container iframe, #ad iframe, iframe[src*="roblox.com/user-sponsorship/"]`;
  2088.  
  2089. const iframes = document.getElementsByTagName("iframe");
  2090. const scripts = document.getElementsByTagName("script");
  2091. const doneMap = new WeakMap();
  2092.  
  2093. function removeElements() {
  2094. // Remove Iframes
  2095. for (let i = iframes.length; i--;) {
  2096. const iframe = iframes[i];
  2097. if (!doneMap.get(iframe) && iframe.matches(iframeSelector)) {
  2098. iframe.remove();
  2099. doneMap.set(iframe, true);
  2100. }
  2101. }
  2102.  
  2103. // Remove Scripts
  2104. for (let i = scripts.length; i--;) {
  2105. const script = scripts[i];
  2106. if (doneMap.get(script)) {
  2107. continue;
  2108. }
  2109. doneMap.set(script, true);
  2110.  
  2111. if (script.src && (
  2112. script.src.includes("imasdk.googleapis.com") ||
  2113. script.src.includes("googletagmanager.com") ||
  2114. script.src.includes("radar.cedexis.com") ||
  2115. script.src.includes("ns1p.net")
  2116. )) {
  2117. script.remove();
  2118. } else {
  2119. const cont = script.textContent;
  2120. if (!cont.includes("ContentJS") && (
  2121. cont.includes("scorecardresearch.com") ||
  2122. cont.includes("cedexis.com") ||
  2123. cont.includes("pingdom.net") ||
  2124. cont.includes("ns1p.net") ||
  2125. cont.includes("Roblox.Hashcash") ||
  2126. cont.includes("Roblox.VideoPreRollDFP") ||
  2127. cont.includes("Roblox.AdsHelper=") ||
  2128. cont.includes("googletag.enableServices()") ||
  2129. cont.includes("gtag('config'")
  2130. )) {
  2131. script.remove();
  2132. } else if (cont.includes("Roblox.EventStream.Init")) {
  2133. script.textContent = cont.replace(/"[^"]*"/g, "\"\"");
  2134. }
  2135. }
  2136. }
  2137.  
  2138. // Hide Sponsored Game Cards (existing method)
  2139. document.querySelectorAll(".game-card-native-ad").forEach(ad => {
  2140. const gameCard = ad.closest(".game-card-container");
  2141. if (gameCard) {
  2142. gameCard.style.display = "none";
  2143. }
  2144. });
  2145.  
  2146. // New: Block Sponsored Ads Game Card
  2147. document.querySelectorAll("div.gamecardcontainer").forEach(container => {
  2148. if (container.querySelector("div.game-card-native-ad")) {
  2149. container.style.display = "none";
  2150. }
  2151. });
  2152.  
  2153. // New: Block Sponsored Section On HomePage
  2154. document.querySelectorAll(".game-sort-carousel-wrapper").forEach(wrapper => {
  2155. const sponsoredLink = wrapper.querySelector('a[href*="Sponsored"]');
  2156. if (sponsoredLink) {
  2157. wrapper.style.display = "none";
  2158. }
  2159. });
  2160. }
  2161.  
  2162. // Observe DOM for dynamically added elements
  2163. new MutationObserver(removeElements).observe(document.body, {
  2164. childList: true,
  2165. subtree: true
  2166. });
  2167.  
  2168. removeElements(); // Initial run
  2169. }
  2170.  
  2171.  
  2172. function ConsoleLogEnabled(...args) {
  2173. if (localStorage.getItem("ROLOCATE_enableLogs") === "true") {
  2174. console.log("[ROLOCATE]", ...args);
  2175. }
  2176. }
  2177.  
  2178.  
  2179. async function showOldRobloxGreeting() {
  2180. ConsoleLogEnabled("Function showOldRobloxGreeting() started.");
  2181.  
  2182. // Check if the URL is roblox.com/home
  2183. if (!window.location.href.includes("roblox.com/home")) {
  2184. ConsoleLogEnabled("Not on roblox.com/home. Exiting function.");
  2185. return; // ⛔ Stops execution if not on the home page
  2186. }
  2187.  
  2188. // Check LocalStorage before proceeding
  2189. if (localStorage.getItem("ROLOCATE_ShowOldGreeting") !== "true") {
  2190. ConsoleLogEnabled("ShowOldGreeting is disabled. Exiting function.");
  2191. return; // ⛔ Stops execution if setting is off
  2192. }
  2193.  
  2194. ConsoleLogEnabled("Waiting 500ms before proceeding.");
  2195. await new Promise(r => setTimeout(r, 500));
  2196.  
  2197. function observeElement(selector) {
  2198. ConsoleLogEnabled(`Observing element: ${selector}`);
  2199. return new Promise((resolve) => {
  2200. const observer = new MutationObserver(() => {
  2201. const element = document.querySelector(selector);
  2202. if (element) {
  2203. ConsoleLogEnabled(`Element found: ${selector}`);
  2204. observer.disconnect();
  2205. resolve(element);
  2206. }
  2207. });
  2208. observer.observe(document.body, {
  2209. childList: true,
  2210. subtree: true
  2211. });
  2212. });
  2213. }
  2214.  
  2215. async function fetchAvatar(selector, fallbackImage) {
  2216. ConsoleLogEnabled(`Fetching avatar from selector: ${selector}`);
  2217. for (let attempt = 0; attempt < 3; attempt++) {
  2218. ConsoleLogEnabled(`Attempt ${attempt + 1} to fetch avatar.`);
  2219. const imgElement = document.querySelector(selector);
  2220. if (imgElement && imgElement.src !== fallbackImage) {
  2221. ConsoleLogEnabled(`Avatar found: ${imgElement.src}`);
  2222. return imgElement.src;
  2223. }
  2224. await new Promise(r => setTimeout(r, 1500));
  2225. }
  2226. ConsoleLogEnabled("Avatar not found, using fallback image.");
  2227. return fallbackImage;
  2228. }
  2229.  
  2230. let homeContainer = await observeElement("#HomeContainer .section:first-child");
  2231. ConsoleLogEnabled("Home container located.");
  2232.  
  2233. let userNameElement = document.querySelector("#navigation.rbx-left-col > ul > li > a .font-header-2");
  2234. ConsoleLogEnabled(`User name found: ${userNameElement ? userNameElement.innerText : "Unknown"}`);
  2235.  
  2236. let user = {
  2237. name: userNameElement ? `Hello, ${userNameElement.innerText}!` : "Hello, Roblox User!",
  2238. avatar: await fetchAvatar("#navigation.rbx-left-col > ul > li > a img", window.Base64Images.image_place_holder)
  2239. };
  2240.  
  2241. ConsoleLogEnabled(`Final user details: Name - ${user.name}, Avatar - ${user.avatar}`);
  2242.  
  2243. let headerContainer = document.createElement("div");
  2244. headerContainer.classList.add("new-header");
  2245. headerContainer.style.opacity = "0";
  2246.  
  2247. let profileFrame = document.createElement("div");
  2248. profileFrame.classList.add("profile-frame");
  2249.  
  2250. let profileImage = document.createElement("img");
  2251. profileImage.src = user.avatar;
  2252. profileImage.classList.add("profile-img");
  2253.  
  2254. profileFrame.appendChild(profileImage);
  2255.  
  2256. let userDetails = document.createElement("div");
  2257. userDetails.classList.add("user-details");
  2258.  
  2259. let userName = document.createElement("h1");
  2260. userName.classList.add("user-name");
  2261. userName.textContent = user.name;
  2262.  
  2263. userDetails.appendChild(userName);
  2264.  
  2265. headerContainer.appendChild(profileFrame);
  2266. headerContainer.appendChild(userDetails);
  2267.  
  2268. ConsoleLogEnabled("Replacing old home container with new header.");
  2269. homeContainer.replaceWith(headerContainer);
  2270.  
  2271. let styleTag = document.createElement("style");
  2272. styleTag.textContent = `
  2273. .new-header {
  2274. display: flex;
  2275. align-items: center;
  2276. margin-bottom: 30px;
  2277. transition: opacity 1.5s ease-in-out;
  2278. }
  2279. .profile-frame {
  2280. width: 150px;
  2281. height: 150px;
  2282. border-radius: 50%;
  2283. overflow: hidden;
  2284. border: 3px solid #121215;
  2285. display: flex;
  2286. justify-content: center;
  2287. align-items: center;
  2288. }
  2289. .profile-img {
  2290. width: 100%;
  2291. height: 100%;
  2292. object-fit: cover;
  2293. }
  2294. .user-details {
  2295. margin-left: 20px;
  2296. display: flex;
  2297. align-items: center;
  2298. }
  2299. .user-name {
  2300. font-size: 1.2em;
  2301. font-weight: bold;
  2302. color: white;
  2303. }
  2304. `;
  2305. document.head.appendChild(styleTag);
  2306. ConsoleLogEnabled("Style tag added.");
  2307.  
  2308. setTimeout(() => {
  2309. ConsoleLogEnabled("Fading in new header.");
  2310. headerContainer.style.opacity = "1";
  2311. }, 50);
  2312. }
  2313.  
  2314.  
  2315.  
  2316. let lastUrl = window.location.href.split("#")[0]; // Store only the base URL
  2317.  
  2318. function observeURLChanges() {
  2319. const observer = new MutationObserver(() => {
  2320. let currentUrl = window.location.href.split("#")[0]; // Ignore fragment changes
  2321.  
  2322. if (currentUrl !== lastUrl) {
  2323. ConsoleLogEnabled(`URL changed from ${lastUrl} to ${currentUrl}`);
  2324. lastUrl = currentUrl; // Update the stored URL
  2325.  
  2326. // Re-run functions when going back to home
  2327. if (currentUrl.includes("roblox.com/home")) {
  2328. ConsoleLogEnabled("Detected return to home page. Reloading greeting.");
  2329. showOldRobloxGreeting();
  2330. }
  2331. }
  2332. });
  2333.  
  2334. observer.observe(document.body, {
  2335. childList: true,
  2336. subtree: true
  2337. });
  2338. }
  2339.  
  2340.  
  2341. function quicknavbutton() {
  2342. if (localStorage.getItem('ROLOCATE_quicknav') === 'true') {
  2343. const settingsRaw = localStorage.getItem('ROLOCATE_quicknav_settings');
  2344. if (!settingsRaw) return;
  2345.  
  2346. let settings;
  2347. try {
  2348. settings = JSON.parse(settingsRaw);
  2349. } catch (e) {
  2350. console.error('Failed to parse ROLOCATE_quicknav_settings:', e);
  2351. return;
  2352. }
  2353.  
  2354. const sidebar = document.querySelector('.left-col-list');
  2355. if (!sidebar) return;
  2356.  
  2357. const premiumButton = sidebar.querySelector('.rbx-upgrade-now');
  2358.  
  2359. const style = document.createElement('style');
  2360. style.textContent = `
  2361. .rolocate-icon-custom {
  2362. display: inline-block;
  2363. width: 24px;
  2364. height: 24px;
  2365. margin-left: 3px;
  2366. background-image: url("${window.Base64Images.quicknav}");
  2367. background-size: contain;
  2368. background-repeat: no-repeat;
  2369. }
  2370.  
  2371. `;
  2372. document.head.appendChild(style);
  2373.  
  2374.  
  2375.  
  2376. settings.forEach(({
  2377. name,
  2378. link
  2379. }) => {
  2380. const li = document.createElement('li');
  2381.  
  2382. const a = document.createElement('a');
  2383. a.className = 'dynamic-overflow-container text-nav';
  2384. a.href = link;
  2385. a.target = '_self';
  2386.  
  2387. const divIcon = document.createElement('div');
  2388. const spanIcon = document.createElement('span');
  2389. spanIcon.className = 'rolocate-icon-custom';
  2390. divIcon.appendChild(spanIcon);
  2391.  
  2392. const spanText = document.createElement('span');
  2393. spanText.className = 'font-header-2 dynamic-ellipsis-item';
  2394. spanText.title = name;
  2395. spanText.textContent = name;
  2396.  
  2397. a.appendChild(divIcon);
  2398. a.appendChild(spanText);
  2399. li.appendChild(a);
  2400.  
  2401. if (premiumButton && premiumButton.parentElement === sidebar) {
  2402. sidebar.insertBefore(li, premiumButton);
  2403. } else {
  2404. sidebar.appendChild(li);
  2405. }
  2406. });
  2407. }
  2408. }
  2409.  
  2410.  
  2411. function validateManualMode() {
  2412. // Check if in manual mode
  2413. if (localStorage.getItem("ROLOCATE_prioritylocation") === "manual") {
  2414. ConsoleLogEnabled("Manual mode detected");
  2415.  
  2416. try {
  2417. // Get stored coordinates
  2418. const coords = JSON.parse(GM_getValue("ROLOCATE_coordinates", '{"lat":"","lng":""}'));
  2419. ConsoleLogEnabled("Coordinates fetched:", coords);
  2420.  
  2421. // If coordinates are empty, switch to automatic
  2422. if (!coords.lat || !coords.lng) {
  2423. localStorage.setItem("ROLOCATE_prioritylocation", "automatic");
  2424. ConsoleLogEnabled("No coordinates set. Switched to automatic mode.");
  2425. return true; // Indicates that a switch occurred
  2426. }
  2427. } catch (e) {
  2428. ConsoleLogEnabled("Error checking coordinates:", e);
  2429. // If there's an error reading coordinates, switch to automatic
  2430. localStorage.setItem("ROLOCATE_prioritylocation", "automatic");
  2431. ConsoleLogEnabled("Error encountered while fetching coordinates. Switched to automatic mode.");
  2432. return true;
  2433. }
  2434. }
  2435.  
  2436. ConsoleLogEnabled("No Errors detected.");
  2437. return false; // No switch occurred
  2438. }
  2439.  
  2440.  
  2441.  
  2442.  
  2443. // Run the initial setup
  2444. window.addEventListener("load", () => {
  2445. loadBase64Library(() => {
  2446. ConsoleLogEnabled("Loaded Base64Images. It is ready to use!");
  2447. });
  2448.  
  2449. AddSettingsButton(() => {
  2450. ConsoleLogEnabled("Loaded Settings button!");
  2451. });
  2452.  
  2453. Update_Popup();
  2454. initializeLocalStorage();
  2455. removeAds();
  2456. showOldRobloxGreeting();
  2457. quicknavbutton();
  2458. ConsoleLogEnabled("Loaded Settings!");
  2459. validateManualMode();
  2460.  
  2461. // Start observing URL changes
  2462. observeURLChanges();
  2463. });
  2464.  
  2465.  
  2466.  
  2467.  
  2468. function loadBase64Library(callback, timeout = 5000) {
  2469. let elapsed = 0;
  2470. (function waitForLibrary() {
  2471. if (typeof window.Base64Images === "undefined") {
  2472. if (elapsed < timeout) {
  2473. elapsed += 50;
  2474. setTimeout(waitForLibrary, 50);
  2475. } else {
  2476. ConsoleLogEnabled("Base64Images did not load within the timeout.");
  2477. notifications('An error occurred! No icons will show. Please refresh the page.', 'error', '⚠️', '8000')
  2478. }
  2479. } else {
  2480. if (callback) callback();
  2481. }
  2482. })();
  2483. }
  2484.  
  2485.  
  2486. /*******************************************************
  2487. The code for the random hop button and the filter button on roblox.com/games/*
  2488. *******************************************************/
  2489.  
  2490. if (window.location.href.startsWith("https://www.roblox.com/games/") && (localStorage.getItem("ROLOCATE_togglefilterserversbutton") === "true" || localStorage.getItem("ROLOCATE_toggleserverhopbutton") === "true" || localStorage.getItem("ROLOCATE_togglerecentserverbutton") === "true")) {
  2491. let Isongamespage = false; // Initially false
  2492. /*********************************************************************************************************************************************************************************************************************************************
  2493. 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
  2494.  
  2495. *********************************************************************************************************************************************************************************************************************************************/
  2496. //Testing
  2497. //HandleRecentServersAddGames("126884695634066", "853e79a5-1a2b-4178-94bf-a242de1aecd6");
  2498. //HandleRecentServersAddGames("126884695634066", "a08849f1-40e32-4b3215c-31231231a268-e948519caf39");
  2499. //HandleRecentServersAddGames("126884695634066", "a08849f1-40e32-4b5c-31236541231a268-e948519caf39");
  2500. //HandleRecentServersAddGames("126884695634066", "a08849f1-40e32-4b5c-31231287631a268-e948519caf39");
  2501. //HandleRecentServersAddGames("126884695634066", "a08849f1-40e32-4b5c-31231231a268-87e948519caf39");
  2502. //HandleRecentServersAddGames("126884695634066", "a08849f1-40e32-4b5c-31231231a268089-e948519caf39");
  2503. //document.querySelector('.recent-servers-section')?.remove(); // remove old list
  2504. //HandleRecentServers(); // re-render with updated order
  2505.  
  2506. function InitRobloxLaunchHandler() {
  2507. if (!window.location.href.startsWith('https://www.roblox.com/games/')) return;
  2508. if (window._robloxJoinInterceptorInitialized) return;
  2509. window._robloxJoinInterceptorInitialized = true;
  2510.  
  2511. const originalJoin = Roblox.GameLauncher.joinGameInstance;
  2512.  
  2513. Roblox.GameLauncher.joinGameInstance = function(gameId, serverId) {
  2514. ConsoleLogEnabled(`Intercepted join: Game ID = ${gameId}, Server ID = ${serverId}`);
  2515. HandleRecentServersAddGames(gameId, serverId);
  2516. document.querySelector('.recent-servers-section')?.remove(); // remove old list
  2517. HandleRecentServers(); // re-render with updated order
  2518. return originalJoin.apply(this, arguments);
  2519. };
  2520. }
  2521.  
  2522.  
  2523.  
  2524.  
  2525. function HandleRecentServersAddGames(gameId, serverId) {
  2526. const storageKey = "ROLOCATE_recentservers_button";
  2527. const stored = JSON.parse(localStorage.getItem(storageKey) || "{}");
  2528. const key = `${gameId}_${serverId}`;
  2529. stored[key] = Date.now(); // Always update timestamp
  2530. localStorage.setItem(storageKey, JSON.stringify(stored));
  2531. }
  2532.  
  2533. function HandleRecentServersURL() {
  2534. // Static-like variable to remember if we've already found an invalid URL
  2535. if (HandleRecentServersURL.alreadyInvalid) {
  2536. return; // Skip if previously marked as invalid
  2537. }
  2538.  
  2539. const url = window.location.href;
  2540.  
  2541. // Regex pattern to match ROLOCATE_GAMEID and SERVERID from the hash
  2542. const match = url.match(/ROLOCATE_GAMEID=(\d+)_SERVERID=([a-f0-9-]+)/i);
  2543.  
  2544. if (match && match.length === 3) {
  2545. const gameId = match[1];
  2546. const serverId = match[2];
  2547.  
  2548. // Call the handler with extracted values
  2549. HandleRecentServersAddGames(gameId, serverId);
  2550. InitRobloxLaunchHandler();
  2551. } else {
  2552. ConsoleLogEnabled("No gameId and serverId found in URL.");
  2553. InitRobloxLaunchHandler();
  2554. HandleRecentServersURL.alreadyInvalid = true; // Set internal flag
  2555. }
  2556. }
  2557.  
  2558.  
  2559.  
  2560. function HandleRecentServers() {
  2561. const serverList = document.querySelector('.server-list-options');
  2562. if (!serverList || document.querySelector('.recent-servers-section')) return;
  2563.  
  2564. const match = window.location.href.match(/\/games\/(\d+)\//);
  2565. if (!match) return;
  2566. const currentGameId = match[1];
  2567.  
  2568. const allHeaders = document.querySelectorAll('.server-list-header');
  2569. let friendsSectionHeader = null;
  2570.  
  2571. allHeaders.forEach(header => {
  2572. if (header.textContent.trim() === 'Servers My Friends Are In') {
  2573. friendsSectionHeader = header.closest('.container-header');
  2574. }
  2575. });
  2576.  
  2577. if (!friendsSectionHeader) return;
  2578.  
  2579. // Custom premium dark theme CSS variables
  2580. const theme = {
  2581. bgDark: '#14161a',
  2582. bgCard: '#1c1f25',
  2583. bgCardHover: '#22262e',
  2584. bgGradient: 'linear-gradient(145deg, #1e2228, #18191e)',
  2585. bgGradientHover: 'linear-gradient(145deg, #23272f, #1c1f25)',
  2586. accentPrimary: '#4d85ee',
  2587. accentSecondary: '#3464c9',
  2588. accentGradient: 'linear-gradient(to bottom, #4d85ee, #3464c9)',
  2589. accentGradientHover: 'linear-gradient(to bottom, #5990ff, #3b6fdd)',
  2590. textPrimary: '#e8ecf3',
  2591. textSecondary: '#a0a8b8',
  2592. textMuted: '#6c7484',
  2593. borderLight: 'rgba(255, 255, 255, 0.06)',
  2594. borderLightHover: 'rgba(255, 255, 255, 0.12)',
  2595. shadow: '0 5px 15px rgba(0, 0, 0, 0.25)',
  2596. shadowHover: '0 8px 25px rgba(0, 0, 0, 0.3)',
  2597. dangerColor: '#ff5b5b',
  2598. dangerColorHover: '#ff7575',
  2599. dangerGradient: 'linear-gradient(to bottom, #ff5b5b, #e04444)',
  2600. dangerGradientHover: 'linear-gradient(to bottom, #ff7575, #f55)'
  2601. };
  2602.  
  2603. const recentSection = document.createElement('div');
  2604. recentSection.className = 'recent-servers-section premium-dark';
  2605. recentSection.style.marginBottom = '24px';
  2606.  
  2607. const headerContainer = document.createElement('div');
  2608. headerContainer.className = 'container-header';
  2609.  
  2610. const headerInner = document.createElement('div');
  2611. headerInner.className = 'server-list-container-header';
  2612. headerInner.style.padding = '0 4px';
  2613.  
  2614. const headerTitle = document.createElement('h2');
  2615. headerTitle.className = 'server-list-header';
  2616. headerTitle.textContent = 'Recent Servers';
  2617. headerTitle.style.cssText = `
  2618. font-weight: 600;
  2619. color: ${theme.textPrimary};
  2620. letter-spacing: 0.5px;
  2621. position: relative;
  2622. display: inline-block;
  2623. padding-bottom: 4px;
  2624. `;
  2625.  
  2626. // Add premium underline accent to header
  2627. const headerAccent = document.createElement('span');
  2628. headerAccent.style.cssText = `
  2629. position: absolute;
  2630. bottom: 0;
  2631. left: 0;
  2632. width: 40px;
  2633. height: 2px;
  2634. background: ${theme.accentGradient};
  2635. border-radius: 2px;
  2636. `;
  2637. headerTitle.appendChild(headerAccent);
  2638.  
  2639. headerInner.appendChild(headerTitle);
  2640. headerContainer.appendChild(headerInner);
  2641.  
  2642. const contentContainer = document.createElement('div');
  2643. contentContainer.className = 'section-content-off empty-game-instances-container';
  2644. contentContainer.style.padding = '8px 4px';
  2645.  
  2646. const storageKey = "ROLOCATE_recentservers_button";
  2647. let stored = JSON.parse(localStorage.getItem(storageKey) || "{}");
  2648.  
  2649. // Auto-remove servers older than 3 days
  2650. const currentTime = Date.now();
  2651. const threeDaysInMs = 3 * 24 * 60 * 60 * 1000; // 3days in miliseconds
  2652. let storageUpdated = false;
  2653.  
  2654. Object.keys(stored).forEach(key => {
  2655. const serverTime = stored[key];
  2656. if (currentTime - serverTime > threeDaysInMs) {
  2657. delete stored[key];
  2658. storageUpdated = true;
  2659. }
  2660. });
  2661.  
  2662. if (storageUpdated) {
  2663. localStorage.setItem(storageKey, JSON.stringify(stored));
  2664. }
  2665.  
  2666. const keys = Object.keys(stored).filter(key => key.startsWith(`${currentGameId}_`));
  2667. if (keys.length === 0) {
  2668. const emptyMessage = document.createElement('div');
  2669. emptyMessage.className = 'no-servers-message';
  2670. emptyMessage.innerHTML = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="opacity: 0.7; margin-right: 10px;">
  2671. <path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="${theme.accentPrimary}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
  2672. <path d="M9.09 9C9.3251 8.33167 9.78915 7.76811 10.4 7.40913C11.0108 7.05016 11.7289 6.91894 12.4272 7.03871C13.1255 7.15849 13.7588 7.52152 14.2151 8.06353C14.6713 8.60553 14.9211 9.29152 14.92 10C14.92 12 11.92 13 11.92 13" stroke="${theme.accentPrimary}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
  2673. <path d="M12 17H12.01" stroke="${theme.accentPrimary}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
  2674. </svg>No Recent Servers Found`;
  2675. emptyMessage.style.cssText = `
  2676. color: ${theme.textSecondary};
  2677. text-align: center;
  2678. padding: 28px 0;
  2679. font-size: 14px;
  2680. letter-spacing: 0.3px;
  2681. font-weight: 500;
  2682. display: flex;
  2683. align-items: center;
  2684. justify-content: center;
  2685. background: rgba(20, 22, 26, 0.4);
  2686. backdrop-filter: blur(5px);
  2687. border-radius: 12px;
  2688. border: 1px solid rgba(77, 133, 238, 0.15);
  2689. box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.2);
  2690. `;
  2691. contentContainer.appendChild(emptyMessage);
  2692. } else {
  2693. keys.sort((a, b) => stored[b] - stored[a]);
  2694.  
  2695. // Create server cards wrapper
  2696. const cardsWrapper = document.createElement('div');
  2697. cardsWrapper.style.cssText = `
  2698. display: flex;
  2699. flex-direction: column;
  2700. gap: 12px;
  2701. margin: 2px 0;
  2702. `;
  2703.  
  2704. keys.forEach((key, index) => {
  2705. const [gameId, serverId] = key.split("_");
  2706. const timeStored = stored[key];
  2707. const date = new Date(timeStored);
  2708. const formattedTime = date.toLocaleString(undefined, {
  2709. hour: '2-digit',
  2710. minute: '2-digit',
  2711. year: 'numeric',
  2712. month: 'short',
  2713. day: 'numeric'
  2714. });
  2715.  
  2716. const serverCard = document.createElement('div');
  2717. serverCard.className = 'recent-server-card premium-dark';
  2718. serverCard.dataset.serverKey = key;
  2719. serverCard.style.cssText = `
  2720. display: flex;
  2721. justify-content: space-between;
  2722. align-items: center;
  2723. padding: 16px 22px;
  2724. height: 76px;
  2725. border-radius: 14px;
  2726. background: ${theme.bgGradient};
  2727. box-shadow: ${theme.shadow};
  2728. color: ${theme.textPrimary};
  2729. font-family: 'Segoe UI', 'Helvetica Neue', sans-serif;
  2730. font-size: 14px;
  2731. box-sizing: border-box;
  2732. width: 100%;
  2733. position: relative;
  2734. overflow: hidden;
  2735. border: 1px solid ${theme.borderLight};
  2736. transition: all 0.2s ease-out;
  2737. `;
  2738.  
  2739. // Add hover effect
  2740. serverCard.onmouseover = function() {
  2741. this.style.boxShadow = theme.shadowHover;
  2742. this.style.transform = 'translateY(-2px)';
  2743. this.style.borderColor = theme.borderLightHover;
  2744. this.style.background = theme.bgGradientHover;
  2745. };
  2746.  
  2747. serverCard.onmouseout = function() {
  2748. this.style.boxShadow = theme.shadow;
  2749. this.style.transform = 'translateY(0)';
  2750. this.style.borderColor = theme.borderLight;
  2751. this.style.background = theme.bgGradient;
  2752. };
  2753.  
  2754. // Add glass effect overlay
  2755. const glassOverlay = document.createElement('div');
  2756. glassOverlay.style.cssText = `
  2757. position: absolute;
  2758. left: 0;
  2759. top: 0;
  2760. right: 0;
  2761. height: 50%;
  2762. background: linear-gradient(to bottom, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0));
  2763. border-radius: 14px 14px 0 0;
  2764. pointer-events: none;
  2765. `;
  2766. serverCard.appendChild(glassOverlay);
  2767.  
  2768. // Server icon with glow
  2769. const serverIconWrapper = document.createElement('div');
  2770. serverIconWrapper.style.cssText = `
  2771. position: absolute;
  2772. left: 14px;
  2773. display: flex;
  2774. align-items: center;
  2775. justify-content: center;
  2776. width: 32px;
  2777. height: 32px;
  2778. `;
  2779.  
  2780. const serverIcon = document.createElement('div');
  2781. serverIcon.innerHTML = `
  2782. <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
  2783. <path d="M2 17L12 22L22 17" stroke="${theme.accentPrimary}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
  2784. <path d="M2 12L12 17L22 12" stroke="${theme.accentPrimary}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
  2785. <path d="M2 7L12 12L22 7L12 2L2 7Z" stroke="${theme.accentPrimary}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
  2786. </svg>
  2787. `;
  2788. serverIconWrapper.appendChild(serverIcon);
  2789.  
  2790. // Add subtle glow to the server icon
  2791. const iconGlow = document.createElement('div');
  2792. iconGlow.style.cssText = `
  2793. position: absolute;
  2794. width: 24px;
  2795. height: 24px;
  2796. border-radius: 50%;
  2797. background: ${theme.accentPrimary};
  2798. opacity: 0.15;
  2799. filter: blur(8px);
  2800. z-index: -1;
  2801. `;
  2802. serverIconWrapper.appendChild(iconGlow);
  2803.  
  2804. const left = document.createElement('div');
  2805. left.style.cssText = `
  2806. display: flex;
  2807. flex-direction: column;
  2808. justify-content: center;
  2809. margin-left: 12px;
  2810. `;
  2811.  
  2812. const lastPlayed = document.createElement('div');
  2813. lastPlayed.textContent = `Last Played: ${formattedTime}`;
  2814. lastPlayed.style.cssText = `
  2815. font-weight: 600;
  2816. font-size: 14px;
  2817. color: ${theme.textPrimary};
  2818. line-height: 1.3;
  2819. letter-spacing: 0.3px;
  2820. margin-left: 40px;
  2821. text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
  2822. `;
  2823.  
  2824. const metaInfo = document.createElement('div');
  2825. metaInfo.innerHTML = `<span style="color: ${theme.accentPrimary};">Game ID:</span> ${gameId} <span style="color: ${theme.textMuted};">•</span> <span style="color: ${theme.accentPrimary};">Server ID:</span> ${serverId}`;
  2826. metaInfo.style.cssText = `
  2827. font-size: 12px;
  2828. color: ${theme.textSecondary};
  2829. margin-top: 5px;
  2830. opacity: 0.9;
  2831. margin-left: 40px;
  2832. `;
  2833.  
  2834. left.appendChild(lastPlayed);
  2835. left.appendChild(metaInfo);
  2836. serverCard.appendChild(serverIconWrapper);
  2837.  
  2838. const buttonGroup = document.createElement('div');
  2839. buttonGroup.style.cssText = `
  2840. display: flex;
  2841. gap: 12px;
  2842. align-items: center;
  2843. z-index: 2;
  2844. `;
  2845.  
  2846. // Create the smaller remove button to be positioned on the left
  2847. const removeButton = document.createElement('button');
  2848. removeButton.innerHTML = `
  2849. <svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
  2850. <path d="M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
  2851. <path d="M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
  2852. </svg>
  2853. `;
  2854. removeButton.className = 'btn-control-xs remove-button';
  2855. removeButton.style.cssText = `
  2856. background: ${theme.dangerGradient};
  2857. color: white;
  2858. border: none;
  2859. padding: 6px;
  2860. border-radius: 8px;
  2861. font-size: 13px;
  2862. font-weight: 600;
  2863. cursor: pointer;
  2864. transition: all 0.15s ease;
  2865. letter-spacing: 0.4px;
  2866. box-shadow: 0 2px 8px rgba(255, 91, 91, 0.3);
  2867. display: flex;
  2868. align-items: center;
  2869. justify-content: center;
  2870. width: 30px;
  2871. height: 30px;
  2872. `;
  2873.  
  2874. // Add remove button hover effect
  2875. removeButton.onmouseover = function() {
  2876. this.style.background = theme.dangerGradientHover;
  2877. this.style.boxShadow = '0 4px 10px rgba(255, 91, 91, 0.4)';
  2878. this.style.transform = 'translateY(-1px)';
  2879. };
  2880.  
  2881. removeButton.onmouseout = function() {
  2882. this.style.background = theme.dangerGradient;
  2883. this.style.boxShadow = '0 2px 8px rgba(255, 91, 91, 0.3)';
  2884. this.style.transform = 'translateY(0)';
  2885. };
  2886.  
  2887. // Add remove button functionality
  2888. removeButton.addEventListener('click', function(e) {
  2889. e.stopPropagation();
  2890. const serverKey = this.closest('.recent-server-card').dataset.serverKey;
  2891.  
  2892. // Animate removal
  2893. serverCard.style.transition = 'all 0.3s ease-out';
  2894. serverCard.style.opacity = '0';
  2895. serverCard.style.height = '0';
  2896. serverCard.style.margin = '0';
  2897. serverCard.style.padding = '0';
  2898.  
  2899. setTimeout(() => {
  2900. serverCard.remove();
  2901.  
  2902. // Update localStorage
  2903. const storedData = JSON.parse(localStorage.getItem(storageKey) || "{}");
  2904. delete storedData[serverKey];
  2905. localStorage.setItem(storageKey, JSON.stringify(storedData));
  2906.  
  2907. // If no servers left, show empty message
  2908. if (document.querySelectorAll('.recent-server-card').length === 0) {
  2909. const emptyMessage = document.createElement('div');
  2910. emptyMessage.className = 'no-servers-message';
  2911. emptyMessage.innerHTML = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="opacity: 0.7; margin-right: 10px;">
  2912. <path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="${theme.accentPrimary}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
  2913. <path d="M9.09 9C9.3251 8.33167 9.78915 7.76811 10.4 7.40913C11.0108 7.05016 11.7289 6.91894 12.4272 7.03871C13.1255 7.15849 13.7588 7.52152 14.2151 8.06353C14.6713 8.60553 14.9211 9.29152 14.92 10C14.92 12 11.92 13 11.92 13" stroke="${theme.accentPrimary}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
  2914. <path d="M12 17H12.01" stroke="${theme.accentPrimary}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
  2915. </svg>No Recent Servers Found`;
  2916. emptyMessage.style.cssText = `
  2917. color: ${theme.textSecondary};
  2918. text-align: center;
  2919. padding: 28px 0;
  2920. font-size: 14px;
  2921. letter-spacing: 0.3px;
  2922. font-weight: 500;
  2923. display: flex;
  2924. align-items: center;
  2925. justify-content: center;
  2926. background: rgba(20, 22, 26, 0.4);
  2927. backdrop-filter: blur(5px);
  2928. border-radius: 12px;
  2929. border: 1px solid rgba(77, 133, 238, 0.15);
  2930. box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.2);
  2931. `;
  2932. cardsWrapper.appendChild(emptyMessage);
  2933. }
  2934. }, 300);
  2935. });
  2936.  
  2937. // Create a separator element
  2938. const separator = document.createElement('div');
  2939. separator.style.cssText = `
  2940. height: 24px;
  2941. width: 1px;
  2942. background-color: rgba(255, 255, 255, 0.15);
  2943. margin: 0 2px;
  2944. `;
  2945.  
  2946. const joinButton = document.createElement('button');
  2947. joinButton.innerHTML = `
  2948. <svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-right: 6px;">
  2949. <path d="M5 12H19" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
  2950. <path d="M12 5L19 12L12 19" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
  2951. </svg>
  2952. Join
  2953. `;
  2954. joinButton.className = 'btn-control-xs join-button';
  2955. joinButton.style.cssText = `
  2956. background: ${theme.accentGradient};
  2957. color: white;
  2958. border: none;
  2959. padding: 8px 18px;
  2960. border-radius: 10px;
  2961. font-size: 13px;
  2962. font-weight: 600;
  2963. cursor: pointer;
  2964. transition: all 0.15s ease;
  2965. letter-spacing: 0.4px;
  2966. box-shadow: 0 2px 10px rgba(52, 100, 201, 0.3);
  2967. display: flex;
  2968. align-items: center;
  2969. justify-content: center;
  2970. `;
  2971.  
  2972. // Add join button functionality
  2973. joinButton.addEventListener('click', function() {
  2974. try {
  2975. Roblox.GameLauncher.joinGameInstance(gameId, serverId);
  2976. } catch (error) {
  2977. ConsoleLogEnabled("Error joining game:", error);
  2978. }
  2979. });
  2980.  
  2981. // Add hover effect for join button
  2982. joinButton.onmouseover = function() {
  2983. this.style.background = theme.accentGradientHover;
  2984. this.style.boxShadow = '0 4px 12px rgba(77, 133, 238, 0.4)';
  2985. this.style.transform = 'translateY(-1px)';
  2986. };
  2987.  
  2988. joinButton.onmouseout = function() {
  2989. this.style.background = theme.accentGradient;
  2990. this.style.boxShadow = '0 2px 10px rgba(52, 100, 201, 0.3)';
  2991. this.style.transform = 'translateY(0)';
  2992. };
  2993.  
  2994. const inviteButton = document.createElement('button');
  2995. inviteButton.innerHTML = `
  2996. <svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-right: 6px;">
  2997. <path d="M16 18L18 20L22 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
  2998. <path d="M20 12V13.4C20 13.4 19.5 13 19 13C18.5 13 18 13.5 18 14C18 14.5 18.5 15 19 15C19.5 15 20 14.6 20 14.6V16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
  2999. <path d="M4 20C4 17 7 17 8 17C9 17 13 17 13 17" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
  3000. <path d="M9.5 10C10.8807 10 12 8.88071 12 7.5C12 6.11929 10.8807 5 9.5 5C8.11929 5 7 6.11929 7 7.5C7 8.88071 8.11929 10 9.5 10Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
  3001. </svg>
  3002. Invite
  3003. `;
  3004. inviteButton.className = 'btn-control-xs invite-button';
  3005. inviteButton.style.cssText = `
  3006. background: rgba(28, 31, 37, 0.6);
  3007. color: ${theme.textPrimary};
  3008. border: 1px solid rgba(255, 255, 255, 0.12);
  3009. padding: 8px 18px;
  3010. border-radius: 10px;
  3011. font-size: 13px;
  3012. font-weight: 500;
  3013. cursor: pointer;
  3014. transition: all 0.15s ease;
  3015. display: flex;
  3016. align-items: center;
  3017. justify-content: center;
  3018. backdrop-filter: blur(4px);
  3019. `;
  3020.  
  3021. // Add invite button functionality
  3022. inviteButton.addEventListener('click', function() {
  3023. const inviteUrl = `https://oqarshi.github.io/Invite/?placeid=${gameId}&serverid=${serverId}`;
  3024.  
  3025. // Copy to clipboard
  3026. navigator.clipboard.writeText(inviteUrl).then(
  3027. function() {
  3028. // Show feedback that URL was copied
  3029. const originalText = inviteButton.innerHTML;
  3030. inviteButton.innerHTML = `
  3031. <svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-right: 6px;">
  3032. <path d="M20 6L9 17L4 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
  3033. </svg>
  3034. Copied!
  3035. `;
  3036. ConsoleLogEnabled(`Invite link copied to clipboard`);
  3037. notifications('Success! Invite link copied to clipboard!', 'success', '🎉', ' 2000');
  3038. // Reset button after 2 seconds
  3039. setTimeout(() => {
  3040. inviteButton.innerHTML = originalText;
  3041. }, 2000);
  3042. },
  3043. function(err) {
  3044. ConsoleLogEnabled('Could not copy text: ', err);
  3045. }
  3046. );
  3047. });
  3048.  
  3049. // Add hover effect for invite button
  3050. inviteButton.onmouseover = function() {
  3051. this.style.background = 'rgba(35, 39, 46, 0.8)';
  3052. this.style.borderColor = 'rgba(255, 255, 255, 0.18)';
  3053. this.style.transform = 'translateY(-1px)';
  3054. };
  3055.  
  3056. inviteButton.onmouseout = function() {
  3057. this.style.background = 'rgba(28, 31, 37, 0.6)';
  3058. this.style.borderColor = 'rgba(255, 255, 255, 0.12)';
  3059. this.style.transform = 'translateY(0)';
  3060. };
  3061.  
  3062. // MODIFIED: Now add buttons in the new order: Remove, Separator, Join, Invite
  3063. buttonGroup.appendChild(removeButton);
  3064. buttonGroup.appendChild(separator);
  3065. buttonGroup.appendChild(joinButton);
  3066. buttonGroup.appendChild(inviteButton);
  3067.  
  3068. serverCard.appendChild(left);
  3069. serverCard.appendChild(buttonGroup);
  3070. cardsWrapper.appendChild(serverCard);
  3071.  
  3072. // Add subtle line accent
  3073. const lineAccent = document.createElement('div');
  3074. lineAccent.style.cssText = `
  3075. position: absolute;
  3076. left: 0;
  3077. top: 16px;
  3078. bottom: 16px;
  3079. width: 3px;
  3080. background: ${theme.accentGradient};
  3081. border-radius: 0 2px 2px 0;
  3082. `;
  3083. serverCard.appendChild(lineAccent);
  3084.  
  3085. // Add subtle corner accent
  3086. if (index === 0) {
  3087. const cornerAccent = document.createElement('div');
  3088. cornerAccent.style.cssText = `
  3089. position: absolute;
  3090. right: 0;
  3091. top: 0;
  3092. width: 40px;
  3093. height: 40px;
  3094. overflow: hidden;
  3095. pointer-events: none;
  3096. `;
  3097.  
  3098. const cornerInner = document.createElement('div');
  3099. cornerInner.style.cssText = `
  3100. position: absolute;
  3101. right: -20px;
  3102. top: -20px;
  3103. width: 40px;
  3104. height: 40px;
  3105. background: ${theme.accentPrimary};
  3106. transform: rotate(45deg);
  3107. opacity: 0.15;
  3108. `;
  3109.  
  3110. cornerAccent.appendChild(cornerInner);
  3111. serverCard.appendChild(cornerAccent);
  3112. }
  3113. });
  3114.  
  3115. contentContainer.appendChild(cardsWrapper);
  3116. }
  3117.  
  3118. recentSection.appendChild(headerContainer);
  3119. recentSection.appendChild(contentContainer);
  3120. friendsSectionHeader.parentNode.insertBefore(recentSection, friendsSectionHeader);
  3121. }
  3122.  
  3123.  
  3124.  
  3125.  
  3126. /*******************************************************
  3127. name of function: createPopup
  3128. description: Creates a popup with server filtering options and interactive buttons.
  3129. *******************************************************/
  3130.  
  3131. function createPopup() {
  3132. const popup = document.createElement('div');
  3133. popup.className = 'server-filters-dropdown-box'; // Unique class name
  3134. popup.style.cssText = `
  3135. position: absolute;
  3136. width: 210px;
  3137. height: 382px;
  3138. right: 0px;
  3139. top: 30px;
  3140. z-index: 1000;
  3141. border-radius: 5px;
  3142. background-color: rgb(30, 32, 34);
  3143. display: flex;
  3144. flex-direction: column;
  3145. padding: 5px;
  3146. `;
  3147.  
  3148. // Create the header section
  3149. const header = document.createElement('div');
  3150. header.style.cssText = `
  3151. display: flex;
  3152. align-items: center;
  3153. padding: 10px;
  3154. border-bottom: 1px solid #444;
  3155. margin-bottom: 5px;
  3156. `;
  3157.  
  3158. // Add the logo (base64 image)
  3159. const logo = document.createElement('img');
  3160. logo.src = window.Base64Images.logo;
  3161. logo.style.cssText = `
  3162. width: 24px;
  3163. height: 24px;
  3164. margin-right: 10px;
  3165. `;
  3166.  
  3167. // Add the title
  3168. const title = document.createElement('span');
  3169. title.textContent = 'RoLocate';
  3170. title.style.cssText = `
  3171. color: white;
  3172. font-size: 18px;
  3173. font-weight: bold;
  3174. `;
  3175.  
  3176. // Append logo and title to the header
  3177. header.appendChild(logo);
  3178. header.appendChild(title);
  3179.  
  3180. // Append the header to the popup
  3181. popup.appendChild(header);
  3182.  
  3183. // Define unique names, tooltips, experimental status, and explanations for each button
  3184. const buttonData = [{
  3185. name: "Smallest Servers",
  3186. tooltip: "**Reverses the order of the server list.** The emptiest servers will be displayed first.",
  3187. experimental: false
  3188. },
  3189. {
  3190. name: "Available Space",
  3191. tooltip: "**Filters out servers which are full.** Servers with space will only be shown.",
  3192. experimental: false
  3193. },
  3194. {
  3195. name: "Player Count",
  3196. 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.",
  3197. experimental: false
  3198. },
  3199. {
  3200. name: "Random Shuffle",
  3201. tooltip: "**Display servers in a completely random order.** Shows servers with space and servers with low player counts in a randomized order.",
  3202. experimental: false
  3203. },
  3204. {
  3205. name: "Server Region",
  3206. 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.",
  3207. experimental: true,
  3208. experimentalExplanation: "**Experimental**: Still in development and testing. Sometimes user location cannot be detected."
  3209. },
  3210. {
  3211. name: "Best Connection",
  3212. 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.",
  3213. experimental: true,
  3214. experimentalExplanation: "**Experimental**: Still in development and testing. it may be less accurate in regions with fewer Roblox servers"
  3215. },
  3216. {
  3217. name: "Join Small Server",
  3218. 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.",
  3219. experimental: false
  3220. },
  3221. {
  3222. name: "Locate Player",
  3223. 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.",
  3224. experimental: false,
  3225. disabled: true,
  3226. disabledExplanation: "**Disabled**: Due to the recent Roblox update, this feature no longer works. :("
  3227. }
  3228. ];
  3229.  
  3230. // Create buttons with unique names, tooltips, experimental status, and explanations
  3231. buttonData.forEach((data, index) => {
  3232. const buttonContainer = document.createElement('div');
  3233. buttonContainer.className = 'server-filter-option';
  3234. buttonContainer.classList.add(data.disabled ? "disabled" : "enabled");
  3235.  
  3236. // Create a wrapper for the button content that can have opacity applied
  3237. const buttonContentWrapper = document.createElement('div');
  3238. buttonContentWrapper.style.cssText = `
  3239. width: 100%;
  3240. height: 100%;
  3241. display: flex;
  3242. align-items: center;
  3243. justify-content: center;
  3244. ${data.disabled ? 'opacity: 0.7;' : ''}
  3245. `;
  3246.  
  3247. buttonContainer.style.cssText = `
  3248. width: 190px;
  3249. height: 30px;
  3250. background-color: ${data.disabled ? '#2c2c2c' : '#393B3D'};
  3251. margin: 5px;
  3252. border-radius: 5px;
  3253. padding: 3.5px;
  3254. position: relative;
  3255. cursor: ${data.disabled ? 'not-allowed' : 'pointer'};
  3256. display: flex;
  3257. align-items: center;
  3258. justify-content: center;
  3259. transition: background-color 0.3s ease;
  3260. `;
  3261.  
  3262. const tooltip = document.createElement('div');
  3263. tooltip.className = 'filter-tooltip';
  3264. tooltip.style.cssText = `
  3265. display: none;
  3266. position: absolute;
  3267. top: -10px;
  3268. left: 200px;
  3269. width: auto;
  3270. inline-size: 200px;
  3271. height: auto;
  3272. background-color: #191B1D;
  3273. color: white;
  3274. padding: 5px;
  3275. border-radius: 5px;
  3276. white-space: pre-wrap;
  3277. font-size: 14px;
  3278. opacity: 1;
  3279. z-index: 1001;
  3280. `;
  3281.  
  3282. // Parse tooltip text and replace **...** with bold HTML tags
  3283. tooltip.innerHTML = data.tooltip.replace(/\*\*(.*?)\*\*/g, "<b style='color: #068f00;'>$1</b>");
  3284.  
  3285. const buttonText = document.createElement('p');
  3286. buttonText.style.cssText = `
  3287. margin: 0;
  3288. color: white;
  3289. font-size: 16px;
  3290. `;
  3291. buttonText.textContent = data.name;
  3292.  
  3293. // Add "DISABLED" style if the button is disabled
  3294. if (data.disabled) {
  3295. // Show explanation tooltip (left side like experimental)
  3296. const disabledTooltip = document.createElement('div');
  3297. disabledTooltip.className = 'disabled-tooltip';
  3298. disabledTooltip.style.cssText = `
  3299. display: none;
  3300. position: absolute;
  3301. top: 0;
  3302. right: 200px;
  3303. width: 200px;
  3304. background-color: #191B1D;
  3305. color: white;
  3306. padding: 5px;
  3307. border-radius: 5px;
  3308. font-size: 14px;
  3309. white-space: pre-wrap;
  3310. z-index: 1001;
  3311. opacity: 1;
  3312. `;
  3313. disabledTooltip.innerHTML = data.disabledExplanation.replace(/\*\*(.*?)\*\*/g, '<span style="font-weight: bold; color: #ff5555;">$1</span>');
  3314.  
  3315. buttonContainer.appendChild(disabledTooltip);
  3316.  
  3317. // Add disabled indicator
  3318. const disabledIndicator = document.createElement('span');
  3319. disabledIndicator.textContent = 'DISABLED';
  3320. disabledIndicator.style.cssText = `
  3321. margin-left: 8px;
  3322. color: #ff5555;
  3323. font-size: 10px;
  3324. font-weight: bold;
  3325. background-color: rgba(255, 85, 85, 0.1);
  3326. padding: 1px 4px;
  3327. border-radius: 3px;
  3328. `;
  3329. buttonText.appendChild(disabledIndicator);
  3330.  
  3331. // Show on hover
  3332. buttonContainer.addEventListener('mouseenter', () => {
  3333. disabledTooltip.style.display = 'block';
  3334. });
  3335. buttonContainer.addEventListener('mouseleave', () => {
  3336. disabledTooltip.style.display = 'none';
  3337. });
  3338. }
  3339.  
  3340. // Add "EXP" label if the button is experimental
  3341. if (data.experimental) {
  3342. const expLabel = document.createElement('span');
  3343. expLabel.textContent = 'EXP';
  3344. expLabel.style.cssText = `
  3345. margin-left: 8px;
  3346. color: gold;
  3347. font-size: 12px;
  3348. font-weight: bold;
  3349. background-color: rgba(255, 215, 0, 0.1);
  3350. padding: 2px 6px;
  3351. border-radius: 3px;
  3352. `;
  3353. buttonText.appendChild(expLabel);
  3354. }
  3355.  
  3356. // Add experimental explanation tooltip (left side)
  3357. let experimentalTooltip = null;
  3358. if (data.experimental) {
  3359. experimentalTooltip = document.createElement('div');
  3360. experimentalTooltip.className = 'experimental-tooltip';
  3361. experimentalTooltip.style.cssText = `
  3362. display: none;
  3363. position: absolute;
  3364. top: 0;
  3365. right: 200px;
  3366. width: 200px;
  3367. background-color: #191B1D;
  3368. color: white;
  3369. padding: 5px;
  3370. border-radius: 5px;
  3371. font-size: 14px;
  3372. white-space: pre-wrap;
  3373. z-index: 1001;
  3374. opacity: 1;
  3375. `;
  3376.  
  3377. // Function to replace **text** with bold and gold styled text
  3378. const formatText = (text) => {
  3379. return text.replace(/\*\*(.*?)\*\*/g, '<span style="font-weight: bold; color: gold;">$1</span>');
  3380. };
  3381.  
  3382. // Apply the formatting to the experimental explanation
  3383. experimentalTooltip.innerHTML = formatText(data.experimentalExplanation);
  3384.  
  3385. buttonContainer.appendChild(experimentalTooltip);
  3386. }
  3387.  
  3388. // Append tooltip directly to button container so it won't inherit opacity
  3389. buttonContainer.appendChild(tooltip);
  3390.  
  3391. // Append button text to content wrapper
  3392. buttonContentWrapper.appendChild(buttonText);
  3393.  
  3394. // Append content wrapper to button container
  3395. buttonContainer.appendChild(buttonContentWrapper);
  3396.  
  3397. buttonContainer.addEventListener('mouseover', () => {
  3398. tooltip.style.display = 'block';
  3399. if (data.experimental) {
  3400. experimentalTooltip.style.display = 'block';
  3401. }
  3402. // Only change background color on hover if the button is not disabled
  3403. if (!data.disabled) {
  3404. buttonContainer.style.backgroundColor = '#4A4C4E'; // Hover effect
  3405. }
  3406. });
  3407. buttonContainer.addEventListener('mouseout', () => {
  3408. tooltip.style.display = 'none';
  3409. if (data.experimental) {
  3410. experimentalTooltip.style.display = 'none';
  3411. }
  3412. // Only revert background color if the button is not disabled
  3413. if (!data.disabled) {
  3414. buttonContainer.style.backgroundColor = '#393B3D'; // Revert to original color
  3415. }
  3416. });
  3417.  
  3418. buttonContainer.addEventListener('click', () => {
  3419. // Prevent click functionality for disabled buttons
  3420. if (data.disabled) {
  3421. return;
  3422. }
  3423.  
  3424. switch (index) {
  3425. case 0:
  3426. smallest_servers();
  3427. break;
  3428. case 1:
  3429. available_space_servers();
  3430. break;
  3431. case 2:
  3432. player_count_tab();
  3433. break;
  3434. case 3:
  3435. random_servers();
  3436. break;
  3437. case 4:
  3438. createServerCountPopup((totalLimit) => {
  3439. rebuildServerList(gameId, totalLimit);
  3440. });
  3441. break;
  3442. case 5:
  3443. rebuildServerList(gameId, 50, true);
  3444. break;
  3445. case 6:
  3446. auto_join_small_server();
  3447. break;
  3448. case 7:
  3449. find_user_server_tab();
  3450. break;
  3451. }
  3452. });
  3453.  
  3454. popup.appendChild(buttonContainer);
  3455. });
  3456.  
  3457. return popup;
  3458. }
  3459.  
  3460. /*******************************************************
  3461. name of function: ServerHop
  3462. description: Handles server hopping by fetching and joining a random server, excluding recently joined servers.
  3463. *******************************************************/
  3464. // Main function to handle the server hopping
  3465. function ServerHop() {
  3466. ConsoleLogEnabled("Starting server hop...");
  3467. showLoadingOverlay();
  3468.  
  3469. // Extract the game ID from the URL
  3470. const url = window.location.href;
  3471. const gameId = url.split("/")[4]; // Extracts the game ID, assuming URL is in the format: /games/{gameId}/Title
  3472.  
  3473. ConsoleLogEnabled(`Game ID: ${gameId}`);
  3474.  
  3475. // Array to store server IDs
  3476. let serverIds = [];
  3477. let nextPageCursor = null;
  3478. let pagesRequested = 0;
  3479.  
  3480. // Get the list of all recently joined servers in localStorage
  3481. const allStoredServers = Object.keys(localStorage)
  3482. .filter(key => key.startsWith("ROLOCATE_recentServers_"))
  3483. .map(key => JSON.parse(localStorage.getItem(key)));
  3484.  
  3485. // Remove any expired servers for all games (older than 15 minutes)
  3486. const currentTime = new Date().getTime();
  3487. allStoredServers.forEach(storedServers => {
  3488. const validServers = storedServers.filter(server => {
  3489. const lastJoinedTime = new Date(server.timestamp).getTime();
  3490. return (currentTime - lastJoinedTime) <= 15 * 60 * 1000; // 15 minutes
  3491. });
  3492.  
  3493. // Update localStorage with the valid (non-expired) servers
  3494. localStorage.setItem(`ROLOCATE_recentServers_${gameId}`, JSON.stringify(validServers));
  3495. });
  3496.  
  3497. // Get the list of recently joined servers for the current game
  3498. const storedServers = JSON.parse(localStorage.getItem(`ROLOCATE_recentServers_${gameId}`)) || [];
  3499.  
  3500. // Check if there are any recently joined servers and exclude them from selection
  3501. const validServers = storedServers.filter(server => {
  3502. const lastJoinedTime = new Date(server.timestamp).getTime();
  3503. return (currentTime - lastJoinedTime) <= 15 * 60 * 1000; // 15 minutes
  3504. });
  3505.  
  3506. if (validServers.length > 0) {
  3507. ConsoleLogEnabled(`Excluding servers joined in the last 15 minutes: ${validServers.map(s => s.serverId).join(', ')}`);
  3508. } else {
  3509. ConsoleLogEnabled("No recently joined servers within the last 15 minutes. Proceeding to pick a new server.");
  3510. }
  3511.  
  3512. // Function to fetch servers
  3513. function fetchServers(cursor) {
  3514. const url = `https://games.roblox.com/v1/games/${gameId}/servers/0?sortOrder=2&excludeFullGames=true&limit=100${cursor ? `&cursor=${cursor}` : ""}`;
  3515.  
  3516. GM_xmlhttpRequest({
  3517. method: "GET",
  3518. url: url,
  3519. onload: function(response) {
  3520. ConsoleLogEnabled("API Response:", response.responseText);
  3521.  
  3522. try {
  3523. const data = JSON.parse(response.responseText);
  3524.  
  3525. // If there's an error, log it and return without processing
  3526. if (data.errors) {
  3527. ConsoleLogEnabled("Skipping unreadable response:", data.errors[0].message);
  3528. return;
  3529. }
  3530.  
  3531. // After a successful request, wait 0.15 seconds before proceeding
  3532. setTimeout(() => {
  3533. if (!data || !data.data) {
  3534. ConsoleLogEnabled("Invalid response structure: 'data' is missing or undefined", data);
  3535. return;
  3536. }
  3537.  
  3538. data.data.forEach(server => {
  3539. if (validServers.some(vs => vs.serverId === server.id)) {
  3540. ConsoleLogEnabled(`Skipping previously joined server ${server.id}.`);
  3541. } else {
  3542. serverIds.push(server.id);
  3543. }
  3544. });
  3545.  
  3546. // Fetch next page if available and within limit
  3547. if (data.nextPageCursor && pagesRequested < 4) {
  3548. pagesRequested++;
  3549. ConsoleLogEnabled(`Fetching page ${pagesRequested}...`);
  3550. fetchServers(data.nextPageCursor);
  3551. } else {
  3552. pickRandomServer();
  3553. }
  3554. }, 150);
  3555.  
  3556. } catch (error) {
  3557. ConsoleLogEnabled("Error parsing response:", error);
  3558. }
  3559. },
  3560. onerror: function(error) {
  3561. ConsoleLogEnabled("Error fetching server data:", error);
  3562. }
  3563. });
  3564. }
  3565.  
  3566. // Function to pick a random server and join it
  3567. function pickRandomServer() {
  3568. if (serverIds.length > 0) {
  3569. const randomServerId = serverIds[Math.floor(Math.random() * serverIds.length)];
  3570. ConsoleLogEnabled(`Joining server: ${randomServerId}`);
  3571.  
  3572. // Join the game instance with the selected server ID
  3573. Roblox.GameLauncher.joinGameInstance(gameId, randomServerId);
  3574.  
  3575. // Store the selected server ID with the time and date in localStorage
  3576. const timestamp = new Date().toISOString();
  3577. const newServer = {
  3578. serverId: randomServerId,
  3579. timestamp
  3580. };
  3581. validServers.push(newServer);
  3582.  
  3583. // Save the updated list of recently joined servers to localStorage
  3584. localStorage.setItem(`ROLOCATE_recentServers_${gameId}`, JSON.stringify(validServers));
  3585.  
  3586. ConsoleLogEnabled(`Server ${randomServerId} stored with timestamp ${timestamp}`);
  3587. } else {
  3588. ConsoleLogEnabled("No servers found to join.");
  3589. notifications("You have joined all the servers recently. No servers found to join.", "error", "⚠️", "5000");
  3590. }
  3591. }
  3592.  
  3593. // Start the fetching process
  3594. fetchServers();
  3595. }
  3596.  
  3597.  
  3598. if (window.location.href.startsWith("https://www.roblox.com/games/")) {
  3599.  
  3600. window.addEventListener("load", () => {
  3601. // Extract game ID from URL
  3602. function findGameId() {
  3603. const match = window.location.href.match(/games\/(\d+)/);
  3604. return match ? match[1] : null;
  3605. }
  3606.  
  3607. // Auto-click "Servers" tab if enabled in localStorage
  3608. if (localStorage.ROLOCATE_AutoRunServerRegions === "true") {
  3609. setTimeout(() => {
  3610. const serversTab = document.querySelector("#tab-game-instances a");
  3611. if (serversTab) {
  3612. serversTab.click();
  3613. }
  3614. }, 1000);
  3615. }
  3616.  
  3617. // Auto-run server regions if enabled in localStorage
  3618. if (localStorage.ROLOCATE_AutoRunServerRegions === "true") {
  3619. setTimeout(() => {
  3620. const gameId = findGameId();
  3621. if (gameId) {
  3622. Loadingbar(true);
  3623. disableFilterButton(true);
  3624. disableLoadMoreButton();
  3625. rebuildServerList(gameId, 16);
  3626. }
  3627. }, 2000);
  3628. }
  3629. });
  3630.  
  3631.  
  3632.  
  3633.  
  3634. Isongamespage = true;
  3635.  
  3636. const observer = new MutationObserver((mutations, obs) => {
  3637. const serverListOptions = document.querySelector('.server-list-options');
  3638. const playButton = document.querySelector('.btn-common-play-game-lg.btn-primary-md');
  3639.  
  3640. if (serverListOptions && !document.querySelector('.RL-filter-button') && localStorage.getItem("ROLOCATE_togglefilterserversbutton") === "true") {
  3641. ConsoleLogEnabled("Added Filter Button");
  3642. const filterButton = document.createElement('a');
  3643. filterButton.className = 'RL-filter-button';
  3644. filterButton.style.cssText = `
  3645. color: white;
  3646. font-weight: bold;
  3647. text-decoration: none;
  3648. cursor: pointer;
  3649. margin-left: 10px;
  3650. padding: 5px 10px;
  3651. display: flex;
  3652. align-items: center;
  3653. gap: 5px;
  3654. position: relative;
  3655. margin-top: 4px;
  3656. `;
  3657.  
  3658. filterButton.addEventListener('mouseover', () => {
  3659. filterButton.style.textDecoration = 'underline';
  3660. });
  3661. filterButton.addEventListener('mouseout', () => {
  3662. filterButton.style.textDecoration = 'none';
  3663. });
  3664.  
  3665. const buttonText = document.createElement('span');
  3666. buttonText.className = 'RL-filter-text';
  3667. buttonText.textContent = 'Filters';
  3668. filterButton.appendChild(buttonText);
  3669.  
  3670. const icon = document.createElement('span');
  3671. icon.className = 'RL-filter-icon';
  3672. icon.textContent = '≡';
  3673. icon.style.cssText = `font-size: 18px;`;
  3674. filterButton.appendChild(icon);
  3675.  
  3676. serverListOptions.appendChild(filterButton);
  3677.  
  3678. let popup = null;
  3679. filterButton.addEventListener('click', (event) => {
  3680. event.stopPropagation();
  3681. if (popup) {
  3682. popup.remove();
  3683. popup = null;
  3684. } else {
  3685. popup = createPopup();
  3686. popup.style.top = `${filterButton.offsetHeight}px`;
  3687. popup.style.left = '0';
  3688. filterButton.appendChild(popup);
  3689. }
  3690. });
  3691.  
  3692. document.addEventListener('click', (event) => {
  3693. if (popup && !filterButton.contains(event.target)) {
  3694. popup.remove();
  3695. popup = null;
  3696. }
  3697. });
  3698. }
  3699. // new condition to trigger recent server logic
  3700. if (localStorage.getItem("ROLOCATE_togglerecentserverbutton") === "true") {
  3701. HandleRecentServers();
  3702. HandleRecentServersURL();
  3703. }
  3704.  
  3705. if (playButton && !document.querySelector('.custom-play-button') && localStorage.getItem("ROLOCATE_toggleserverhopbutton") === "true") {
  3706. ConsoleLogEnabled("Added Server Hop Button");
  3707. const buttonContainer = document.createElement('div');
  3708. buttonContainer.style.cssText = `
  3709. display: flex;
  3710. gap: 10px;
  3711. align-items: center;
  3712. width: 100%;
  3713. `;
  3714.  
  3715. playButton.style.cssText += `
  3716. flex: 3;
  3717. padding: 10px 12px;
  3718. text-align: center;
  3719. `;
  3720.  
  3721. const serverHopButton = document.createElement('button');
  3722. serverHopButton.className = 'custom-play-button';
  3723. serverHopButton.style.cssText = `
  3724. background-color: #335fff;
  3725. color: white;
  3726. border: none;
  3727. padding: 7.5px 12px;
  3728. cursor: pointer;
  3729. font-weight: bold;
  3730. border-radius: 8px;
  3731. flex: 1;
  3732. text-align: center;
  3733. display: flex;
  3734. align-items: center;
  3735. justify-content: center;
  3736. position: relative;
  3737. `;
  3738.  
  3739. const tooltip = document.createElement('div');
  3740. tooltip.textContent = 'Join Random Server / Server Hop';
  3741. tooltip.style.cssText = `
  3742. position: absolute;
  3743. background-color: rgba(51, 95, 255, 0.8);
  3744. color: white;
  3745. padding: 5px 10px;
  3746. border-radius: 5px;
  3747. font-size: 12px;
  3748. visibility: hidden;
  3749. opacity: 0;
  3750. transition: opacity 0.2s ease-in-out;
  3751. bottom: 100%;
  3752. left: 50%;
  3753. transform: translateX(-50%);
  3754. white-space: nowrap;
  3755. `;
  3756. serverHopButton.appendChild(tooltip);
  3757.  
  3758. serverHopButton.addEventListener('mouseover', () => {
  3759. tooltip.style.visibility = 'visible';
  3760. tooltip.style.opacity = '1';
  3761. });
  3762.  
  3763. serverHopButton.addEventListener('mouseout', () => {
  3764. tooltip.style.visibility = 'hidden';
  3765. tooltip.style.opacity = '0';
  3766. });
  3767.  
  3768. const logo = document.createElement('img');
  3769. logo.src = window.Base64Images.icon_serverhop;
  3770. logo.style.cssText = `
  3771. width: 45px;
  3772. height: 45px;
  3773. `;
  3774. serverHopButton.appendChild(logo);
  3775.  
  3776. playButton.parentNode.insertBefore(buttonContainer, playButton);
  3777. buttonContainer.appendChild(playButton);
  3778. buttonContainer.appendChild(serverHopButton);
  3779.  
  3780. serverHopButton.addEventListener('click', () => {
  3781. ServerHop();
  3782. });
  3783. }
  3784.  
  3785. const filterEnabled = localStorage.getItem("ROLOCATE_togglefilterserversbutton") === "true";
  3786. const hopEnabled = localStorage.getItem("ROLOCATE_toggleserverhopbutton") === "true";
  3787. const recentEnabled = localStorage.getItem("ROLOCATE_togglerecentserverbutton") === "true";
  3788.  
  3789. const filterPresent = !filterEnabled || document.querySelector('.RL-filter-button');
  3790. const hopPresent = !hopEnabled || document.querySelector('.custom-play-button');
  3791. const recentPresent = !recentEnabled || document.querySelector('.recent-servers-section');
  3792.  
  3793. if (filterPresent && hopPresent && recentPresent) {
  3794. obs.disconnect();
  3795. ConsoleLogEnabled("Disconnected Observer");
  3796. }
  3797. });
  3798.  
  3799. observer.observe(document.body, {
  3800. childList: true,
  3801. subtree: true
  3802. });
  3803. }
  3804.  
  3805.  
  3806.  
  3807.  
  3808. /*********************************************************************************************************************************************************************************************************************************************
  3809. 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
  3810.  
  3811. *********************************************************************************************************************************************************************************************************************************************/
  3812.  
  3813.  
  3814. /*********************************************************************************************************************************************************************************************************************************************
  3815. Functions for the 1st button
  3816.  
  3817. *********************************************************************************************************************************************************************************************************************************************/
  3818.  
  3819.  
  3820. /*******************************************************
  3821. name of function: smallest_servers
  3822. description: Fetches the smallest servers, disables the "Load More" button, shows a loading bar, and recreates the server cards.
  3823. *******************************************************/
  3824. async function smallest_servers() {
  3825. // Disable the "Load More" button and show the loading bar
  3826. Loadingbar(true);
  3827. disableFilterButton(true);
  3828. disableLoadMoreButton();
  3829. notifications("Finding small servers...", "success", "🧐");
  3830.  
  3831. // Get the game ID from the URL
  3832. const gameId = window.location.pathname.split('/')[2];
  3833.  
  3834. // Retry mechanism
  3835. let retries = 3;
  3836. let success = false;
  3837.  
  3838. while (retries > 0 && !success) {
  3839. try {
  3840. // Use GM_xmlhttpRequest to fetch server data from the Roblox API
  3841. const response = await new Promise((resolve, reject) => {
  3842. GM_xmlhttpRequest({
  3843. method: "GET",
  3844. url: `https://games.roblox.com/v1/games/${gameId}/servers/0?sortOrder=1&excludeFullGames=true&limit=100`,
  3845. onload: function(response) {
  3846. if (response.status === 429) {
  3847. reject(new Error('429: Too Many Requests'));
  3848. } else if (response.status >= 200 && response.status < 300) {
  3849. resolve(response);
  3850. } else {
  3851. reject(new Error(`HTTP error! status: ${response.status}`));
  3852. }
  3853. },
  3854. onerror: function(error) {
  3855. reject(error);
  3856. }
  3857. });
  3858. });
  3859.  
  3860. const data = JSON.parse(response.responseText);
  3861.  
  3862. // Process each server
  3863. for (const server of data.data) {
  3864. const {
  3865. id: serverId,
  3866. playerTokens,
  3867. maxPlayers,
  3868. playing
  3869. } = server;
  3870.  
  3871. // Pass the server data to the card creation function
  3872. await rbx_card(serverId, playerTokens, maxPlayers, playing, gameId);
  3873. }
  3874.  
  3875. success = true; // Mark as successful if no errors occurred
  3876. } catch (error) {
  3877. retries--; // Decrement the retry count
  3878.  
  3879. if (error.message === '429: Too Many Requests' && retries > 0) {
  3880. ConsoleLogEnabled('Encountered a 429 error. Retrying in 5 seconds...');
  3881. await new Promise(resolve => setTimeout(resolve, 5000)); // Wait for 5 seconds
  3882. } else {
  3883. ConsoleLogEnabled('Error fetching server data:', error);
  3884. notifications('Error: Failed to fetch server data. Please try again later.', 'error', '⚠️', '5000');
  3885. Loadingbar(false);
  3886. break; // Exit the loop if it's not a 429 error or no retries left
  3887. }
  3888. } finally {
  3889. if (success || retries === 0) {
  3890. // Hide the loading bar and enable the filter button
  3891. Loadingbar(false);
  3892. disableFilterButton(false);
  3893. }
  3894. }
  3895. }
  3896. }
  3897.  
  3898.  
  3899.  
  3900. /*********************************************************************************************************************************************************************************************************************************************
  3901. Functions for the 2nd button
  3902.  
  3903. *********************************************************************************************************************************************************************************************************************************************/
  3904.  
  3905.  
  3906. /*******************************************************
  3907. name of function: available_space_servers
  3908. description: Fetches servers with available space, disables the "Load More" button, shows a loading bar, and recreates the server cards.
  3909. *******************************************************/
  3910. async function available_space_servers() {
  3911. // Disable the "Load More" button and show the loading bar
  3912. Loadingbar(true);
  3913. disableLoadMoreButton();
  3914. disableFilterButton(true);
  3915. notifications("Finding servers with space...", "success", "🧐");
  3916.  
  3917. // Get the game ID from the URL
  3918. const gameId = window.location.pathname.split('/')[2];
  3919.  
  3920. // Retry mechanism
  3921. let retries = 3;
  3922. let success = false;
  3923.  
  3924. while (retries > 0 && !success) {
  3925. try {
  3926. // Use GM_xmlhttpRequest to fetch server data from the Roblox API
  3927. const response = await new Promise((resolve, reject) => {
  3928. GM_xmlhttpRequest({
  3929. method: "GET",
  3930. url: `https://games.roblox.com/v1/games/${gameId}/servers/0?sortOrder=2&excludeFullGames=true&limit=100`,
  3931. onload: function(response) {
  3932. if (response.status === 429) {
  3933. reject(new Error('429: Too Many Requests'));
  3934. } else if (response.status >= 200 && response.status < 300) {
  3935. resolve(response);
  3936. } else {
  3937. reject(new Error(`HTTP error! status: ${response.status}`));
  3938. }
  3939. },
  3940. onerror: function(error) {
  3941. reject(error);
  3942. }
  3943. });
  3944. });
  3945.  
  3946. const data = JSON.parse(response.responseText);
  3947.  
  3948. // Process each server
  3949. for (const server of data.data) {
  3950. const {
  3951. id: serverId,
  3952. playerTokens,
  3953. maxPlayers,
  3954. playing
  3955. } = server;
  3956.  
  3957. // Pass the server data to the card creation function
  3958. await rbx_card(serverId, playerTokens, maxPlayers, playing, gameId);
  3959. }
  3960.  
  3961. success = true; // Mark as successful if no errors occurred
  3962. } catch (error) {
  3963. retries--; // Decrement the retry count
  3964.  
  3965. if (error.message === '429: Too Many Requests' && retries > 0) {
  3966. ConsoleLogEnabled('Encountered a 429 error. Retrying in 10 seconds...');
  3967. await new Promise(resolve => setTimeout(resolve, 10000)); // Wait for 10 seconds
  3968. } else {
  3969. ConsoleLogEnabled('Error fetching server data:', error);
  3970. break; // Exit the loop if it's not a 429 error or no retries left
  3971. }
  3972. } finally {
  3973. if (success || retries === 0) {
  3974. // Hide the loading bar and enable the filter button
  3975. Loadingbar(false);
  3976. disableFilterButton(false);
  3977. }
  3978. }
  3979. }
  3980. }
  3981.  
  3982. /*********************************************************************************************************************************************************************************************************************************************
  3983. Functions for the 3rd button
  3984.  
  3985. *********************************************************************************************************************************************************************************************************************************************/
  3986.  
  3987.  
  3988. /*******************************************************
  3989. name of function: player_count_tab
  3990. description: Opens a popup for the user to select the max player count using a slider and filters servers accordingly.
  3991. *******************************************************/
  3992. function player_count_tab() {
  3993. // Check if the max player count has already been determined
  3994. if (!player_count_tab.maxPlayers) {
  3995. // Try to find the element containing the player count information
  3996. const playerCountElement = document.querySelector('.text-info.rbx-game-status.rbx-game-server-status.text-overflow');
  3997. if (playerCountElement) {
  3998. const playerCountText = playerCountElement.textContent.trim();
  3999. const match = playerCountText.match(/(\d+) of (\d+) people max/);
  4000. if (match) {
  4001. const maxPlayers = parseInt(match[2], 10);
  4002. if (!isNaN(maxPlayers) && maxPlayers > 1) {
  4003. player_count_tab.maxPlayers = maxPlayers;
  4004. ConsoleLogEnabled("Found text element with max playercount");
  4005. }
  4006. }
  4007. } else {
  4008. // If the element is not found, extract the gameId from the URL
  4009. const gameIdMatch = window.location.href.match(/games\/(\d+)/);
  4010. if (gameIdMatch && gameIdMatch[1]) {
  4011. const gameId = gameIdMatch[1];
  4012. // Send a request to the Roblox API to get server information
  4013. GM_xmlhttpRequest({
  4014. method: 'GET',
  4015. url: `https://games.roblox.com/v1/games/${gameId}/servers/public?sortOrder=1&excludeFullGames=true&limit=100`,
  4016. onload: function(response) {
  4017. try {
  4018. if (response.status === 429) {
  4019. // Rate limit error, default to 100
  4020. ConsoleLogEnabled("Rate limited defaulting to 100.");
  4021. player_count_tab.maxPlayers = 100;
  4022. } else {
  4023. ConsoleLogEnabled("Valid api response");
  4024. const data = JSON.parse(response.responseText);
  4025. if (data.data && data.data.length > 0) {
  4026. const maxPlayers = data.data[0].maxPlayers;
  4027. if (!isNaN(maxPlayers) && maxPlayers > 1) {
  4028. player_count_tab.maxPlayers = maxPlayers;
  4029. }
  4030. }
  4031. }
  4032. // Update the slider range if the popup is already created
  4033. const slider = document.querySelector('.player-count-popup input[type="range"]');
  4034. if (slider) {
  4035. slider.max = player_count_tab.maxPlayers ? (player_count_tab.maxPlayers - 1).toString() : '100';
  4036. slider.style.background = `
  4037. linear-gradient(
  4038. to right,
  4039. #00A2FF 0%,
  4040. #00A2FF ${slider.value}%,
  4041. #444 ${slider.value}%,
  4042. #444 100%
  4043. );
  4044. `;
  4045. }
  4046. } catch (error) {
  4047. ConsoleLogEnabled('Failed to parse API response:', error);
  4048. // Default to 100 if parsing fails
  4049. player_count_tab.maxPlayers = 100;
  4050. const slider = document.querySelector('.player-count-popup input[type="range"]');
  4051. if (slider) {
  4052. slider.max = '100';
  4053. slider.style.background = `
  4054. linear-gradient(
  4055. to right,
  4056. #00A2FF 0%,
  4057. #00A2FF ${slider.value}%,
  4058. #444 ${slider.value}%,
  4059. #444 100%
  4060. );
  4061. `;
  4062. }
  4063. }
  4064. },
  4065. onerror: function(error) {
  4066. ConsoleLogEnabled('Failed to fetch server information:', error);
  4067. ConsoleLogEnabled('Fallback to 100 players.');
  4068. // Default to 100 if the request fails
  4069. player_count_tab.maxPlayers = 100;
  4070. const slider = document.querySelector('.player-count-popup input[type="range"]');
  4071. if (slider) {
  4072. slider.max = '100';
  4073. slider.style.background = `
  4074. linear-gradient(
  4075. to right,
  4076. #00A2FF 0%,
  4077. #00A2FF ${slider.value}%,
  4078. #444 ${slider.value}%,
  4079. #444 100%
  4080. );
  4081. `;
  4082. }
  4083. }
  4084. });
  4085. }
  4086. }
  4087. }
  4088. // Create the overlay (backdrop)
  4089. const overlay = document.createElement('div');
  4090. overlay.style.cssText = `
  4091. position: fixed;
  4092. top: 0;
  4093. left: 0;
  4094. width: 100%;
  4095. height: 100%;
  4096. background-color: rgba(0, 0, 0, 0.5);
  4097. z-index: 9999;
  4098. opacity: 0;
  4099. transition: opacity 0.3s ease;
  4100. `;
  4101. document.body.appendChild(overlay);
  4102.  
  4103. // Create the popup container
  4104. const popup = document.createElement('div');
  4105. popup.className = 'player-count-popup';
  4106. popup.style.cssText = `
  4107. position: fixed;
  4108. top: 50%;
  4109. left: 50%;
  4110. transform: translate(-50%, -50%);
  4111. background-color: rgb(30, 32, 34);
  4112. padding: 20px;
  4113. border-radius: 10px;
  4114. z-index: 10000;
  4115. box-shadow: 0 0 15px rgba(0, 0, 0, 0.7);
  4116. display: flex;
  4117. flex-direction: column;
  4118. align-items: center;
  4119. gap: 15px;
  4120. width: 300px;
  4121. opacity: 0;
  4122. transition: opacity 0.3s ease, transform 0.3s ease;
  4123. `;
  4124.  
  4125. // Add a close button in the top-right corner (bigger size)
  4126. const closeButton = document.createElement('button');
  4127. closeButton.innerHTML = '&times;'; // Using '×' for the close icon
  4128. closeButton.style.cssText = `
  4129. position: absolute;
  4130. top: 10px;
  4131. right: 10px;
  4132. background: transparent;
  4133. border: none;
  4134. color: #ffffff;
  4135. font-size: 24px; /* Increased font size */
  4136. cursor: pointer;
  4137. width: 36px; /* Increased size */
  4138. height: 36px; /* Increased size */
  4139. border-radius: 50%;
  4140. display: flex;
  4141. align-items: center;
  4142. justify-content: center;
  4143. transition: background-color 0.3s ease, color 0.3s ease;
  4144. `;
  4145. closeButton.addEventListener('mouseenter', () => {
  4146. closeButton.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
  4147. closeButton.style.color = '#ff4444';
  4148. });
  4149. closeButton.addEventListener('mouseleave', () => {
  4150. closeButton.style.backgroundColor = 'transparent';
  4151. closeButton.style.color = '#ffffff';
  4152. });
  4153.  
  4154. // Add a title
  4155. const title = document.createElement('h3');
  4156. title.textContent = 'Select Max Player Count';
  4157. title.style.cssText = `
  4158. color: white;
  4159. margin: 0;
  4160. font-size: 18px;
  4161. font-weight: 500;
  4162. `;
  4163. popup.appendChild(title);
  4164.  
  4165. // Add a slider with improved functionality and styling
  4166. const slider = document.createElement('input');
  4167. slider.type = 'range';
  4168. slider.min = '1';
  4169. slider.max = player_count_tab.maxPlayers ? (player_count_tab.maxPlayers - 1).toString() : '100';
  4170. slider.value = '1'; // Default value
  4171. slider.step = '1'; // Step for better accuracy
  4172. slider.style.cssText = `
  4173. width: 80%;
  4174. cursor: pointer;
  4175. margin: 10px 0;
  4176. -webkit-appearance: none; /* Remove default styling */
  4177. background: transparent;
  4178. `;
  4179. // Custom slider track
  4180. slider.style.background = `
  4181. linear-gradient(
  4182. to right,
  4183. #00A2FF 0%,
  4184. #00A2FF ${slider.value}%,
  4185. #444 ${slider.value}%,
  4186. #444 100%
  4187. );
  4188. border-radius: 5px;
  4189. height: 6px;
  4190. `;
  4191. // Custom slider thumb
  4192. slider.style.setProperty('--thumb-size', '20px'); /* Larger thumb */
  4193. slider.style.setProperty('--thumb-color', '#00A2FF');
  4194. slider.style.setProperty('--thumb-hover-color', '#0088cc');
  4195. slider.style.setProperty('--thumb-border', '2px solid #fff');
  4196. slider.style.setProperty('--thumb-shadow', '0 0 5px rgba(0, 0, 0, 0.5)');
  4197. slider.addEventListener('input', () => {
  4198. slider.style.background = `
  4199. linear-gradient(
  4200. to right,
  4201. #00A2FF 0%,
  4202. #00A2FF ${slider.value}%,
  4203. #444 ${slider.value}%,
  4204. #444 100%
  4205. );
  4206. `;
  4207. sliderValue.textContent = slider.value; // Update the displayed value
  4208. });
  4209. // Keyboard support for better accuracy (fixed to increment/decrement by 1)
  4210. slider.addEventListener('keydown', (e) => {
  4211. e.preventDefault(); // Prevent default behavior (which might cause jumps)
  4212. let newValue = parseInt(slider.value, 10);
  4213. if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {
  4214. newValue = Math.max(1, newValue - 1); // Decrease by 1
  4215. } else if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {
  4216. newValue = Math.min(100, newValue + 1); // Increase by 1
  4217. }
  4218. slider.value = newValue;
  4219. slider.dispatchEvent(new Event('input')); // Trigger input event to update UI
  4220. });
  4221. popup.appendChild(slider);
  4222.  
  4223. // Add a display for the slider value
  4224. const sliderValue = document.createElement('span');
  4225. sliderValue.textContent = slider.value;
  4226. sliderValue.style.cssText = `
  4227. color: white;
  4228. font-size: 16px;
  4229. font-weight: bold;
  4230. `;
  4231. popup.appendChild(sliderValue);
  4232.  
  4233. // Add a submit button with dark, blackish style
  4234. const submitButton = document.createElement('button');
  4235. submitButton.textContent = 'Search';
  4236. submitButton.style.cssText = `
  4237. padding: 8px 20px;
  4238. font-size: 16px;
  4239. background-color: #1a1a1a; /* Dark blackish color */
  4240. color: white;
  4241. border: none;
  4242. border-radius: 5px;
  4243. cursor: pointer;
  4244. transition: background-color 0.3s ease, transform 0.2s ease;
  4245. `;
  4246. submitButton.addEventListener('mouseenter', () => {
  4247. submitButton.style.backgroundColor = '#333'; /* Slightly lighter on hover */
  4248. submitButton.style.transform = 'scale(1.05)';
  4249. });
  4250. submitButton.addEventListener('mouseleave', () => {
  4251. submitButton.style.backgroundColor = '#1a1a1a';
  4252. submitButton.style.transform = 'scale(1)';
  4253. });
  4254.  
  4255. // Add a yellow box with a tip under the submit button
  4256. const tipBox = document.createElement('div');
  4257. tipBox.style.cssText = `
  4258. width: 100%;
  4259. padding: 10px;
  4260. background-color: rgba(255, 204, 0, 0.15);
  4261. border-radius: 5px;
  4262. text-align: center;
  4263. font-size: 14px;
  4264. color: #ffcc00;
  4265. transition: background-color 0.3s ease;
  4266. `;
  4267. tipBox.textContent = 'Tip: Click the slider and use the arrow keys for more accuracy.';
  4268. tipBox.addEventListener('mouseenter', () => {
  4269. tipBox.style.backgroundColor = 'rgba(255, 204, 0, 0.25)';
  4270. });
  4271. tipBox.addEventListener('mouseleave', () => {
  4272. tipBox.style.backgroundColor = 'rgba(255, 204, 0, 0.15)';
  4273. });
  4274. popup.appendChild(tipBox);
  4275.  
  4276. // Append the popup to the body
  4277. document.body.appendChild(popup);
  4278.  
  4279. // Fade in the overlay and popup
  4280. setTimeout(() => {
  4281. overlay.style.opacity = '1';
  4282. popup.style.opacity = '1';
  4283. popup.style.transform = 'translate(-50%, -50%) scale(1)';
  4284. }, 10);
  4285.  
  4286. /*******************************************************
  4287. name of function: fadeOutAndRemove
  4288. description: Fades out and removes the popup and overlay.
  4289. *******************************************************/
  4290. function fadeOutAndRemove(popup, overlay) {
  4291. popup.style.opacity = '0';
  4292. popup.style.transform = 'translate(-50%, -50%) scale(0.9)';
  4293. overlay.style.opacity = '0';
  4294. setTimeout(() => {
  4295. popup.remove();
  4296. overlay.remove();
  4297. }, 300); // Match the duration of the transition
  4298. }
  4299.  
  4300. // Close the popup when clicking outside
  4301. overlay.addEventListener('click', () => {
  4302. fadeOutAndRemove(popup, overlay);
  4303. });
  4304.  
  4305. // Close the popup when the close button is clicked
  4306. closeButton.addEventListener('click', () => {
  4307. fadeOutAndRemove(popup, overlay);
  4308. });
  4309.  
  4310. // Handle submit button click
  4311. submitButton.addEventListener('click', () => {
  4312. const maxPlayers = parseInt(slider.value, 10);
  4313. if (!isNaN(maxPlayers) && maxPlayers > 0) {
  4314. filterServersByPlayerCount(maxPlayers);
  4315. fadeOutAndRemove(popup, overlay);
  4316. } else {
  4317. notifications('Error: Please enter a number greater than 0', 'error', '⚠️', '5000');
  4318. }
  4319. });
  4320.  
  4321. popup.appendChild(submitButton);
  4322. popup.appendChild(closeButton);
  4323. }
  4324. /*******************************************************
  4325. name of function: fetchServersWithRetry
  4326. description: Fetches server data with retry logic and a delay between requests to avoid rate-limiting.
  4327. Uses GM_xmlhttpRequest instead of fetch.
  4328. *******************************************************/
  4329. async function fetchServersWithRetry(url, retries = 15, currentDelay = 750) {
  4330. return new Promise((resolve, reject) => {
  4331. GM_xmlhttpRequest({
  4332. method: 'GET',
  4333. url: url,
  4334. onload: function(response) {
  4335. // Check for 429 Rate Limit error
  4336. if (response.status === 429) {
  4337. if (retries > 0) {
  4338. const newDelay = currentDelay * 1; // Exponential backoff
  4339. ConsoleLogEnabled(`[DEBUG] Rate limited. Waiting ${newDelay / 1000} seconds before retrying...`);
  4340. setTimeout(() => {
  4341. resolve(fetchServersWithRetry(url, retries - 1, newDelay)); // Retry with increased delay
  4342. }, newDelay);
  4343. } else {
  4344. ConsoleLogEnabled('[DEBUG] Rate limit retries exhausted.');
  4345. notifications('Error: Rate limited please try again later.', 'error', '⚠️', '5000')
  4346. reject(new Error('RateLimit'));
  4347. }
  4348. return;
  4349. }
  4350.  
  4351. // Handle other HTTP errors
  4352. if (response.status < 200 || response.status >= 300) {
  4353. ConsoleLogEnabled('[DEBUG] HTTP error:', response.status, response.statusText);
  4354. reject(new Error(`HTTP error: ${response.status}`));
  4355. return;
  4356. }
  4357.  
  4358. // Parse and return the JSON data
  4359. try {
  4360. const data = JSON.parse(response.responseText);
  4361. ConsoleLogEnabled('[DEBUG] Fetched data successfully:', data);
  4362. resolve(data);
  4363. } catch (error) {
  4364. ConsoleLogEnabled('[DEBUG] Error parsing JSON:', error);
  4365. reject(error);
  4366. }
  4367. },
  4368. onerror: function(error) {
  4369. ConsoleLogEnabled('[DEBUG] Error in GM_xmlhttpRequest:', error);
  4370. reject(error);
  4371. }
  4372. });
  4373. });
  4374. }
  4375.  
  4376. /*******************************************************
  4377. name of function: filterServersByPlayerCount
  4378. description: Filters servers to show only those with a player count equal to or below the specified max.
  4379. If no exact matches are found, prioritizes servers with player counts lower than the input.
  4380. Keeps fetching until at least 8 servers are found, with a dynamic delay between requests.
  4381. *******************************************************/
  4382. async function filterServersByPlayerCount(maxPlayers) {
  4383. // Validate maxPlayers before proceeding
  4384. if (isNaN(maxPlayers) || maxPlayers < 1 || !Number.isInteger(maxPlayers)) {
  4385. ConsoleLogEnabled('[DEBUG] Invalid input for maxPlayers.');
  4386. notifications('Error: Please input a valid whole number greater than or equal to 1.', 'error', '⚠️', '5000');
  4387. return;
  4388. }
  4389.  
  4390. // Disable UI elements and clear the server list
  4391. Loadingbar(true);
  4392. disableLoadMoreButton();
  4393. disableFilterButton(true);
  4394. const serverList = document.querySelector('#rbx-public-game-server-item-container');
  4395. serverList.innerHTML = '';
  4396.  
  4397. const gameId = window.location.pathname.split('/')[2];
  4398. let cursor = null;
  4399. let serversFound = 0;
  4400. let serverMaxPlayers = null;
  4401. let isCloserToOne = null;
  4402. let topDownServers = []; // Servers collected during top-down search
  4403. let bottomUpServers = []; // Servers collected during bottom-up search
  4404. let currentDelay = 500; // Initial delay of 0.5 seconds
  4405. const timeLimit = 3 * 60 * 1000; // 3 minutes in milliseconds
  4406. const startTime = Date.now(); // Record the start time
  4407. notifications('Will search for a maximum of 3 minutes to find a server.', 'success', '🔎', '5000');
  4408.  
  4409.  
  4410. try {
  4411. while (serversFound < 16) {
  4412. // Check if the time limit has been exceeded
  4413. if (Date.now() - startTime > timeLimit) {
  4414. ConsoleLogEnabled('[DEBUG] Time limit reached. Proceeding to fallback servers.');
  4415. notifications('Warning: Time limit reached. Proceeding to fallback servers.', 'warning', '❗', '5000');
  4416. break;
  4417. }
  4418.  
  4419. // Fetch initial data to determine serverMaxPlayers and isCloserToOne
  4420. if (!serverMaxPlayers) {
  4421. const initialUrl = cursor ?
  4422. `https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=100&cursor=${cursor}` :
  4423. `https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=100`;
  4424.  
  4425. const initialData = await fetchServersWithRetry(initialUrl);
  4426. if (initialData.data.length > 0) {
  4427. serverMaxPlayers = initialData.data[0].maxPlayers;
  4428. isCloserToOne = maxPlayers <= (serverMaxPlayers / 2);
  4429. } else {
  4430. notifications("No servers found in initial fetch.", "error", "⚠️", "5000")
  4431. ConsoleLogEnabled('[DEBUG] No servers found in initial fetch.', 'warning', '❗');
  4432. break;
  4433. }
  4434. }
  4435.  
  4436. // Validate maxPlayers against serverMaxPlayers
  4437. if (maxPlayers >= serverMaxPlayers) {
  4438. ConsoleLogEnabled('[DEBUG] Invalid input: maxPlayers is greater than or equal to serverMaxPlayers.');
  4439. notifications(`Error: Please input a number between 1 through ${serverMaxPlayers - 1}`, 'error', '⚠️', '5000');
  4440. return;
  4441. }
  4442.  
  4443. // Adjust the URL based on isCloserToOne
  4444. const baseUrl = isCloserToOne ?
  4445. `https://games.roblox.com/v1/games/${gameId}/servers/public?sortOrder=1&excludeFullGames=true&limit=100` :
  4446. `https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=100`; // why does this work lmao
  4447.  
  4448. const url = cursor ? `${baseUrl}&cursor=${cursor}` : baseUrl;
  4449. const data = await fetchServersWithRetry(url);
  4450.  
  4451. // Safety check: Ensure the server list is valid and iterable
  4452. if (!Array.isArray(data.data)) {
  4453. ConsoleLogEnabled('[DEBUG] Invalid server list received. Waiting 1 second before retrying...');
  4454. await delay(1000); // Wait 1 second before retrying
  4455. continue; // Skip the rest of the loop and retry
  4456. }
  4457.  
  4458. // Filter and process servers
  4459. for (const server of data.data) {
  4460. if (server.playing === maxPlayers) {
  4461. await rbx_card(server.id, server.playerTokens, server.maxPlayers, server.playing, gameId);
  4462. serversFound++;
  4463.  
  4464. if (serversFound >= 16) {
  4465. break;
  4466. }
  4467. } else if (!isCloserToOne && server.playing > maxPlayers) {
  4468. topDownServers.push(server); // Add to top-down fallback list
  4469. } else if (isCloserToOne && server.playing < maxPlayers) {
  4470. bottomUpServers.push(server); // Add to bottom-up fallback list
  4471. }
  4472. }
  4473.  
  4474. // Exit if no more servers are available
  4475. if (!data.nextPageCursor) {
  4476. break;
  4477. }
  4478.  
  4479. cursor = data.nextPageCursor;
  4480.  
  4481. // Adjust delay dynamically
  4482. if (currentDelay > 150) {
  4483. currentDelay = Math.max(150, currentDelay / 2); // Gradually reduce delay
  4484. }
  4485. ConsoleLogEnabled(`[DEBUG] Waiting ${currentDelay / 1000} seconds before next request...`);
  4486. await delay(currentDelay);
  4487. }
  4488.  
  4489. // If no exact matches were found or time limit reached, use fallback servers
  4490. if (serversFound === 0 && (topDownServers.length > 0 || bottomUpServers.length > 0)) {
  4491. notifications(`There are no servers with ${maxPlayers} players. Showing servers closest to ${maxPlayers} players.`, 'warning', '😔', '8000');
  4492. // Sort top-down servers by player count (ascending)
  4493. topDownServers.sort((a, b) => a.playing - b.playing);
  4494.  
  4495. // Sort bottom-up servers by player count (descending)
  4496. bottomUpServers.sort((a, b) => b.playing - a.playing);
  4497.  
  4498. // Combine both fallback lists (prioritize top-down servers first)
  4499. const combinedFallback = [...topDownServers, ...bottomUpServers];
  4500.  
  4501. for (const server of combinedFallback) {
  4502. await rbx_card(server.id, server.playerTokens, server.maxPlayers, server.playing, gameId);
  4503. serversFound++;
  4504.  
  4505. if (serversFound >= 16) {
  4506. break;
  4507. }
  4508. }
  4509. }
  4510.  
  4511. if (serversFound <= 0) {
  4512. notifications('No Servers Found Within The Provided Criteria', 'info', '🔎', '5000');
  4513. }
  4514. } catch (error) {
  4515. ConsoleLogEnabled('[DEBUG] Error in filterServersByPlayerCount:', error);
  4516. } finally {
  4517. Loadingbar(false);
  4518. disableFilterButton(false);
  4519. }
  4520. }
  4521.  
  4522. /*********************************************************************************************************************************************************************************************************************************************
  4523. Functions for the 4th button
  4524.  
  4525. *********************************************************************************************************************************************************************************************************************************************/
  4526.  
  4527. /*******************************************************
  4528. name of function: random_servers
  4529. 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.
  4530. *******************************************************/
  4531. async function random_servers() {
  4532. notifications('Finding Random Servers. Please wait 2-5 seconds', 'success', '🔎', '5000');
  4533. // Disable the "Load More" button and show the loading bar
  4534. Loadingbar(true);
  4535. disableFilterButton(true);
  4536. disableLoadMoreButton();
  4537.  
  4538. // Get the game ID from the URL
  4539. const gameId = window.location.pathname.split('/')[2];
  4540.  
  4541. try {
  4542. // Fetch servers from the first URL with retry logic
  4543. const firstUrl = `https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=10`;
  4544. const firstData = await fetchWithRetry(firstUrl, 10); // Retry up to 3 times
  4545.  
  4546. // Wait for 5 seconds
  4547. await delay(1500);
  4548.  
  4549. // Fetch servers from the second URL with retry logic
  4550. const secondUrl = `https://games.roblox.com/v1/games/${gameId}/servers/public?sortOrder=1&excludeFullGames=true&limit=10`;
  4551. const secondData = await fetchWithRetry(secondUrl, 10); // Retry up to 3 times
  4552.  
  4553. // Combine the servers from both URLs
  4554. const combinedServers = [...firstData.data, ...secondData.data];
  4555.  
  4556. // Remove duplicates by server ID
  4557. const uniqueServers = [];
  4558. const seenServerIds = new Set();
  4559.  
  4560. for (const server of combinedServers) {
  4561. if (!seenServerIds.has(server.id)) {
  4562. seenServerIds.add(server.id);
  4563. uniqueServers.push(server);
  4564. }
  4565. }
  4566.  
  4567. // Shuffle the unique servers array
  4568. const shuffledServers = shuffleArray(uniqueServers);
  4569.  
  4570. // Get the first 16 shuffled servers
  4571. const selectedServers = shuffledServers.slice(0, 16);
  4572.  
  4573. // Process each server in random order
  4574. for (const server of selectedServers) {
  4575. const {
  4576. id: serverId,
  4577. playerTokens,
  4578. maxPlayers,
  4579. playing
  4580. } = server;
  4581.  
  4582. // Pass the server data to the card creation function
  4583. await rbx_card(serverId, playerTokens, maxPlayers, playing, gameId);
  4584. }
  4585. } catch (error) {
  4586. ConsoleLogEnabled('Error fetching server data:', error);
  4587. notifications('Error: Failed to fetch server data. Please try again later.', 'error', '⚠️', '5000');
  4588. } finally {
  4589. // Hide the loading bar and enable the filter button
  4590. Loadingbar(false);
  4591. disableFilterButton(false);
  4592. }
  4593. }
  4594.  
  4595. /*******************************************************
  4596. name of function: fetchWithRetry
  4597. description: Fetches data from a URL with retry logic for 429 errors using GM_xmlhttpRequest.
  4598. *******************************************************/
  4599. function fetchWithRetry(url, retries) {
  4600. return new Promise((resolve, reject) => {
  4601. const attemptFetch = (attempt = 0) => {
  4602. GM_xmlhttpRequest({
  4603. method: "GET",
  4604. url: url,
  4605. onload: function(response) {
  4606. if (response.status === 429) {
  4607. if (attempt < retries) {
  4608. ConsoleLogEnabled(`Rate limited. Retrying in 2.5 seconds... (Attempt ${attempt + 1}/${retries})`);
  4609. setTimeout(() => attemptFetch(attempt + 1), 1500); // Wait 1.5 seconds and retry
  4610. } else {
  4611. reject(new Error('Rate limit exceeded after retries'));
  4612. }
  4613. } else if (response.status >= 200 && response.status < 300) {
  4614. try {
  4615. const data = JSON.parse(response.responseText);
  4616. resolve(data);
  4617. } catch (error) {
  4618. reject(new Error('Failed to parse JSON response'));
  4619. }
  4620. } else {
  4621. reject(new Error(`HTTP error: ${response.status}`));
  4622. }
  4623. },
  4624. onerror: function(error) {
  4625. if (attempt < retries) {
  4626. ConsoleLogEnabled(`Error occurred. Retrying in 10 seconds... (Attempt ${attempt + 1}/${retries})`);
  4627. setTimeout(() => attemptFetch(attempt + 1), 10000); // Wait 10 seconds and retry
  4628. } else {
  4629. reject(error);
  4630. }
  4631. }
  4632. });
  4633. };
  4634.  
  4635. attemptFetch();
  4636. });
  4637. }
  4638.  
  4639. /*******************************************************
  4640. name of function: shuffleArray
  4641. description: Shuffles an array using the Fisher-Yates algorithm.
  4642. *******************************************************/
  4643. function shuffleArray(array) {
  4644. for (let i = array.length - 1; i > 0; i--) {
  4645. const j = Math.floor(Math.random() * (i + 1)); // Random index from 0 to i
  4646. [array[i], array[j]] = [array[j], array[i]]; // Swap elements
  4647. }
  4648. return array;
  4649. }
  4650.  
  4651.  
  4652. /*********************************************************************************************************************************************************************************************************************************************
  4653. Functions for the 5th button. taken from my other project
  4654.  
  4655. *********************************************************************************************************************************************************************************************************************************************/
  4656.  
  4657. if (Isongamespage) {
  4658. // Create a <style> element
  4659. const style = document.createElement('style');
  4660. style.textContent = `
  4661. /* Overlay for the modal background */
  4662. .overlay {
  4663. position: fixed;
  4664. top: 0;
  4665. left: 0;
  4666. width: 100%;
  4667. height: 100%;
  4668. background-color: rgba(0, 0, 0, 0.85); /* Solid black overlay */
  4669. z-index: 1000; /* Ensure overlay is below the popup */
  4670. opacity: 0; /* Start invisible */
  4671. animation: fadeIn 0.3s ease forwards; /* Fade-in animation */
  4672. }
  4673.  
  4674. @keyframes fadeIn {
  4675. from {
  4676. opacity: 0;
  4677. }
  4678. to {
  4679. opacity: 1;
  4680. }
  4681. }
  4682.  
  4683. /* Popup Container for the server region */
  4684. .filter-popup {
  4685. background-color: #1e1e1e; /* Darker background */
  4686. color: #ffffff; /* White text */
  4687. padding: 25px;
  4688. border-radius: 12px;
  4689. box-shadow: 0 8px 20px rgba(0, 0, 0, 0.5);
  4690. width: 320px;
  4691. max-width: 90%;
  4692. position: fixed; /* Fixed positioning */
  4693. top: 50%; /* Center vertically */
  4694. left: 50%; /* Center horizontally */
  4695. transform: translate(-50%, -50%); /* Offset to truly center */
  4696. text-align: center;
  4697. z-index: 1001; /* Ensure popup is above the overlay */
  4698. border: 1px solid #444; /* Subtle border */
  4699. opacity: 0; /* Start invisible */
  4700. animation: fadeInPopup 0.3s ease 0.1s forwards; /* Fade-in animation with delay */
  4701. }
  4702.  
  4703. @keyframes fadeInPopup {
  4704. from {
  4705. opacity: 0;
  4706. transform: translate(-50%, -55%); /* Slight upward offset */
  4707. }
  4708. to {
  4709. opacity: 1;
  4710. transform: translate(-50%, -50%); /* Center position */
  4711. }
  4712. }
  4713.  
  4714. /* Fade-out animation for overlay and popup */
  4715. .overlay.fade-out {
  4716. animation: fadeOut 0.3s ease forwards;
  4717. }
  4718.  
  4719. .filter-popup.fade-out {
  4720. animation: fadeOutPopup 0.3s ease forwards;
  4721. }
  4722.  
  4723. @keyframes fadeOut {
  4724. from {
  4725. opacity: 1;
  4726. }
  4727. to {
  4728. opacity: 0;
  4729. }
  4730. }
  4731.  
  4732. @keyframes fadeOutPopup {
  4733. from {
  4734. opacity: 1;
  4735. transform: translate(-50%, -50%); /* Center position */
  4736. }
  4737. to {
  4738. opacity: 0;
  4739. transform: translate(-50%, -55%); /* Slight upward offset */
  4740. }
  4741. }
  4742.  
  4743. /* Close Button for the server selector */
  4744. #closePopup {
  4745. position: absolute;
  4746. top: 5px; /* Reduced from 12px to 5px */
  4747. right: 1px; /* Reduced from 12px to 5px */
  4748. background: transparent; /* Transparent background */
  4749. border: none;
  4750. color: #ffffff; /* White color */
  4751. font-size: 20px;
  4752. cursor: pointer;
  4753. width: 28px;
  4754. height: 28px;
  4755. border-radius: 50%;
  4756. display: flex;
  4757. align-items: center;
  4758. justify-content: center;
  4759. transition: background-color 0.3s ease, color 0.3s ease;
  4760. }
  4761.  
  4762. #closePopup:hover {
  4763. background-color: rgba(255, 255, 255, 0.1); /* Light hover effect */
  4764. color: #ff4444; /* Red color on hover */
  4765. }
  4766.  
  4767. /* Label */
  4768. .filter-popup label {
  4769. display: block;
  4770. margin-bottom: 12px;
  4771. font-size: 16px;
  4772. color: #ffffff;
  4773. font-weight: 500; /* Slightly bolder text */
  4774. }
  4775.  
  4776. /* Dropdown */
  4777. .filter-popup select {
  4778. background-color: #333; /* Darker gray background */
  4779. color: #ffffff; /* White text */
  4780. padding: 10px;
  4781. border-radius: 6px;
  4782. border: 1px solid #555; /* Darker border */
  4783. width: 100%;
  4784. margin-bottom: 12px;
  4785. font-size: 14px;
  4786. transition: border-color 0.3s ease;
  4787. }
  4788.  
  4789. .filter-popup select:focus {
  4790. border-color: #888; /* Lighter border on focus */
  4791. outline: none;
  4792. }
  4793.  
  4794. /* Custom Input */
  4795. .filter-popup input[type="number"] {
  4796. background-color: #333; /* Darker gray background */
  4797. color: #ffffff; /* White text */
  4798. padding: 10px;
  4799. border-radius: 6px;
  4800. border: 1px solid #555; /* Darker border */
  4801. width: 100%;
  4802. margin-bottom: 12px;
  4803. font-size: 14px;
  4804. transition: border-color 0.3s ease;
  4805. }
  4806.  
  4807. .filter-popup input[type="number"]:focus {
  4808. border-color: #888; /* Lighter border on focus */
  4809. outline: none;
  4810. }
  4811.  
  4812. /* Confirm Button */
  4813. #confirmServerCount {
  4814. background-color: #444; /* Dark gray background */
  4815. color: #ffffff; /* White text */
  4816. padding: 10px 20px;
  4817. border: 1px solid #666; /* Gray border */
  4818. border-radius: 6px;
  4819. cursor: pointer;
  4820. font-size: 14px;
  4821. width: 100%;
  4822. transition: background-color 0.3s ease, transform 0.2s ease;
  4823. }
  4824.  
  4825. #confirmServerCount:hover {
  4826. background-color: #555; /* Lighter gray on hover */
  4827. transform: translateY(-1px); /* Slight lift effect */
  4828. }
  4829.  
  4830. #confirmServerCount:active {
  4831. transform: translateY(0); /* Reset lift effect on click */
  4832. }
  4833.  
  4834. /* Highlighted server item */
  4835. .rbx-game-server-item.highlighted {
  4836. border: 2px solid #4caf50; /* Green border */
  4837. border-radius: 8px;
  4838. background-color: rgba(76, 175, 80, 0.1); /* Subtle green background */
  4839. }
  4840.  
  4841. /* Disabled fetch button */
  4842. .fetch-button:disabled {
  4843. opacity: 0.5;
  4844. cursor: not-allowed;
  4845. }
  4846.  
  4847. /* Popup Header */
  4848. .popup-header {
  4849. margin-bottom: 24px;
  4850. text-align: left;
  4851. padding: 16px;
  4852. background-color: rgba(255, 255, 255, 0.05); /* Subtle background for contrast */
  4853. border-radius: 8px;
  4854. border: 1px solid rgba(255, 255, 255, 0.1); /* Subtle border */
  4855. transition: background-color 0.3s ease, border-color 0.3s ease;
  4856. }
  4857.  
  4858. .popup-header:hover {
  4859. background-color: rgba(255, 255, 255, 0.08); /* Slightly brighter on hover */
  4860. border-color: rgba(255, 255, 255, 0.2);
  4861. }
  4862.  
  4863. .popup-header h3 {
  4864. margin: 0 0 12px 0;
  4865. font-size: 22px;
  4866. color: #ffffff;
  4867. font-weight: 700; /* Bolder for emphasis */
  4868. letter-spacing: -0.5px; /* Tighter letter spacing for modern look */
  4869. }
  4870.  
  4871. .popup-header p {
  4872. margin: 0;
  4873. font-size: 14px;
  4874. color: #cccccc;
  4875. line-height: 1.6; /* Improved line height for readability */
  4876. opacity: 0.9; /* Slightly transparent for a softer look */
  4877. }
  4878.  
  4879. /* Popup Footer */
  4880. .popup-footer {
  4881. margin-top: 20px;
  4882. text-align: left;
  4883. font-size: 14px;
  4884. color: #ffcc00; /* Yellow color for warnings */
  4885. background-color: rgba(255, 204, 0, 0.15); /* Lighter yellow background */
  4886. padding: 12px;
  4887. border-radius: 8px;
  4888. border: 1px solid rgba(255, 204, 0, 0.15); /* Subtle border */
  4889. transition: background-color 0.3s ease, border-color 0.3s ease;
  4890. }
  4891.  
  4892. .popup-footer:hover {
  4893. background-color: rgba(255, 204, 0, 0.25); /* Slightly brighter on hover */
  4894. border-color: rgba(255, 204, 0, 0.25);
  4895. }
  4896.  
  4897. .popup-footer p {
  4898. margin: 0;
  4899. line-height: 1.5;
  4900. font-weight: 500; /* Slightly bolder for emphasis */
  4901. }
  4902.  
  4903. /* Label */
  4904. .filter-popup label {
  4905. display: block;
  4906. margin-bottom: 12px;
  4907. font-size: 15px;
  4908. color: #ffffff;
  4909. font-weight: 500;
  4910. text-align: left;
  4911. opacity: 0.9; /* Slightly transparent for a softer look */
  4912. transition: opacity 0.3s ease;
  4913. }
  4914.  
  4915. .filter-popup label:hover {
  4916. opacity: 1; /* Fully opaque on hover */
  4917. }
  4918.  
  4919. select:hover, select:focus {
  4920. border-color: #ffffff;
  4921. outline: none;
  4922. }
  4923.  
  4924.  
  4925. `;
  4926. // Append the <style> element to the document head
  4927. document.head.appendChild(style);
  4928. }
  4929.  
  4930.  
  4931. function showMessage(message) {
  4932. const loadMoreButtonContainer = document.querySelector('.rbx-public-running-games-footer');
  4933.  
  4934. if (!loadMoreButtonContainer) {
  4935. ConsoleLogEnabled("Error: 'Load More' button container not found! Ensure the element exists in the DOM.");
  4936. return;
  4937. }
  4938.  
  4939. const existingMessage = loadMoreButtonContainer.querySelector('.premium-message-container');
  4940.  
  4941. // If message is "END", remove any existing message and exit
  4942. if (message === "END") {
  4943. if (existingMessage) {
  4944. existingMessage.remove();
  4945. ConsoleLogEnabled("Message container removed.");
  4946. } else {
  4947. ConsoleLogEnabled("No message container found to remove.");
  4948. }
  4949. return;
  4950. }
  4951.  
  4952. // Remove existing message if present before showing a new one
  4953. if (existingMessage) {
  4954. existingMessage.remove();
  4955. ConsoleLogEnabled("Warning: An existing message was found and replaced.");
  4956. }
  4957.  
  4958. // Inject CSS only once
  4959. if (!document.getElementById('premium-message-styles')) {
  4960. const style = document.createElement('style');
  4961. style.id = 'premium-message-styles';
  4962. style.textContent = `
  4963. .premium-message-container {
  4964. margin-top: 20px;
  4965. padding: 18px 26px;
  4966. background: linear-gradient(145deg, #2b0000, #1a0000);
  4967. border-radius: 14px;
  4968. box-shadow: 0 6px 20px rgba(255, 0, 0, 0.2);
  4969. font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  4970. font-size: 16px;
  4971. color: #ffdddd;
  4972. transition: all 0.3s ease-in-out, transform 0.3s ease, box-shadow 0.3s ease;
  4973. opacity: 0;
  4974. animation: fadeIn 0.6s ease forwards;
  4975. border: 1px solid #440000;
  4976. backdrop-filter: blur(6px);
  4977. display: flex;
  4978. align-items: center;
  4979. gap: 16px;
  4980. cursor: default;
  4981. user-select: none;
  4982. }
  4983.  
  4984. .premium-message-container:hover {
  4985. transform: scale(1.015);
  4986. box-shadow: 0 8px 24px rgba(255, 0, 0, 0.25);
  4987. background: linear-gradient(145deg, #330000, #220000);
  4988. color: #ffe5e5;
  4989. }
  4990.  
  4991. .premium-message-logo {
  4992. width: 28px;
  4993. height: 28px;
  4994. border-radius: 6px;
  4995. object-fit: contain;
  4996. box-shadow: 0 0 8px rgba(255, 0, 0, 0.2);
  4997. background-color: #000;
  4998. }
  4999.  
  5000. .premium-message-text {
  5001. flex: 1;
  5002. text-align: left;
  5003. font-weight: 500;
  5004. letter-spacing: 0.3px;
  5005. }
  5006.  
  5007. @keyframes fadeIn {
  5008. to { opacity: 1; }
  5009. }
  5010. `;
  5011. document.head.appendChild(style);
  5012. }
  5013.  
  5014. // Create the message container
  5015. const container = document.createElement('div');
  5016. container.className = 'premium-message-container';
  5017.  
  5018. // Create and insert the logo
  5019. const logo = document.createElement('img');
  5020. logo.className = 'premium-message-logo';
  5021. logo.src = window.Base64Images.logo;
  5022.  
  5023. // Create and insert the message text
  5024. const messageText = document.createElement('div');
  5025. messageText.className = 'premium-message-text';
  5026. messageText.textContent = message;
  5027.  
  5028. // Build the full component
  5029. container.appendChild(logo);
  5030. container.appendChild(messageText);
  5031. loadMoreButtonContainer.appendChild(container);
  5032.  
  5033. ConsoleLogEnabled("Message displayed successfully:", message);
  5034. return container;
  5035. }
  5036.  
  5037.  
  5038.  
  5039.  
  5040. // Function to show the popup for random stuff
  5041. function showPopup() {
  5042. const overlay = document.createElement('div');
  5043. overlay.className = 'overlay';
  5044.  
  5045. const popup = document.createElement('div');
  5046. popup.className = 'filter-popup';
  5047. popup.textContent = 'Uh somethings wrong if you see this message. Please report to the greasyfork issues page!';
  5048.  
  5049. document.body.appendChild(overlay);
  5050. document.body.appendChild(popup);
  5051.  
  5052. return popup;
  5053. }
  5054.  
  5055. // Function to hide the popup for the stuff
  5056. function hidePopup() {
  5057. const popup = document.querySelector('.filter-popup');
  5058. const overlay = document.querySelector('.overlay');
  5059.  
  5060. if (popup) popup.remove();
  5061. if (overlay) overlay.remove();
  5062. }
  5063.  
  5064. // Function to fetch server details so game id and job id. yea!
  5065. async function fetchServerDetails(gameId, jobId) {
  5066. return new Promise((resolve, reject) => {
  5067. GM_xmlhttpRequest({
  5068. method: "POST",
  5069. url: "https://gamejoin.roblox.com/v1/join-game-instance", // url for game id
  5070. headers: { // doesent need cookie cuase of magic
  5071. "Content-Type": "application/json",
  5072. "User-Agent": "Roblox/WinInet",
  5073. },
  5074. data: JSON.stringify({
  5075. placeId: gameId,
  5076. gameId: jobId
  5077. }),
  5078. onload: function(response) {
  5079. const json = JSON.parse(response.responseText);
  5080.  
  5081. ConsoleLogEnabled("API Response:", json); // This prints the full response
  5082.  
  5083. // Check if the response indicates that the user needs to purchase the game
  5084. if (json.status === 12 && json.message === 'You need to purchase access to this game before you can play.') { // yea error message!
  5085. reject('purchase_required'); // Special error code for this case yea!
  5086. return;
  5087. }
  5088.  
  5089.  
  5090. // Check if the response indicates that the user needs to purchase the game
  5091. if (json.status === 12 && json.message === 'Cannot join this non-root place due to join restrictions') { // yea error message!
  5092. reject('subplace_join_restriction'); // Special error code for this case yea!
  5093. return;
  5094. }
  5095.  
  5096. const address = json?.joinScript?.UdmuxEndpoints?.[0]?.Address ?? json?.joinScript?.MachineAddress;
  5097.  
  5098. if (!address) {
  5099. ConsoleLogEnabled("API Response (Unknown Location) Which means Full Server!:", json); // Log the API response for debug
  5100. reject(`Unable to fetch server location: Status ${json.status}`); // debug
  5101. return;
  5102. }
  5103.  
  5104. const location = serverRegionsByIp[address.replace(/^(128\.116\.\d+)\.\d+$/, "$1.0")]; // lmao all servers atart with this so yea dont argue with me
  5105.  
  5106. if (!location) {
  5107. ConsoleLogEnabled("API Response (Unknown Location):", json); // Log the API response into the chat. might remove it from production but idc rn
  5108. reject(`Unknown server address ${address}`);
  5109. return;
  5110. }
  5111.  
  5112. resolve(location);
  5113. },
  5114. onerror: function(error) {
  5115. ConsoleLogEnabled("API Request Failed:", error); // damn if this happpens idk what to tell u
  5116. reject(`Failed to fetch server details: ${error}`);
  5117. },
  5118. });
  5119. });
  5120. }
  5121.  
  5122. // cusomt delay also known as sleep fucntion in js cause this language sucks and doesent have a default function
  5123. function delay(ms) {
  5124. return new Promise(resolve => setTimeout(resolve, ms));
  5125. }
  5126.  
  5127. // Function to create a popup for selecting the number of servers
  5128. // basically yea thats what it doesent
  5129. function createServerCountPopup(callback) {
  5130. const overlay = document.createElement('div');
  5131. overlay.className = 'overlay';
  5132.  
  5133. const popup = document.createElement('div');
  5134. popup.className = 'filter-popup';
  5135.  
  5136. // Inject styles for dropdown icon
  5137. const style = document.createElement('style');
  5138. style.textContent = `
  5139. .dropdown-wrapper {
  5140. position: relative;
  5141. display: inline-block;
  5142. width: 100%;
  5143. }
  5144.  
  5145. .dropdown-wrapper select {
  5146. width: 100%;
  5147. padding-right: 30px;
  5148. appearance: none;
  5149. -webkit-appearance: none;
  5150. -moz-appearance: none;
  5151. }
  5152.  
  5153. .dropdown-wrapper .dropdown-icon {
  5154. position: absolute;
  5155. right: 10px;
  5156. top: 40%;
  5157. transform: translateY(-50%);
  5158. pointer-events: none;
  5159. font-size: 12px;
  5160. color: #fff;
  5161.  
  5162. }
  5163. `;
  5164. document.head.appendChild(style);
  5165.  
  5166. popup.innerHTML = `
  5167. <button id="closePopup">X</button>
  5168. <div class="popup-header">
  5169. <h3>Select Number of Servers</h3>
  5170. <p>Choose how many servers you want to search. Higher values will provide more location variety but may take longer to process.</p>
  5171. <div class="popup-footer">
  5172. <p><strong>Note:</strong> Searching over 100 servers may take longer and could result in rate limiting. It is rare though.</p>
  5173. </div>
  5174. </div>
  5175. <label for="serverCount">Select Number of Servers:</label>
  5176. <div class="dropdown-wrapper">
  5177. <select id="serverCount">
  5178. <option value="10">10 Servers</option>
  5179. <option value="25" selected>25 Servers</option>
  5180. <option value="50">50 Servers</option>
  5181. <option value="100">100 Servers</option>
  5182. <option value="200">200 Servers</option>
  5183. <option value="500">500 Servers</option>
  5184. <option value="1000">1000 Servers</option>
  5185. <option value="custom">Custom</option>
  5186. </select>
  5187. <span class="dropdown-icon">▼</span>
  5188. </div>
  5189. <input id="customServerCount" type="number" min="1" max="1000" placeholder="Enter a number (1-1000)" style="display: none;">
  5190. <button id="confirmServerCount">Confirm</button>
  5191. `;
  5192.  
  5193. document.body.appendChild(overlay);
  5194. document.body.appendChild(popup);
  5195.  
  5196. const serverCountDropdown = popup.querySelector('#serverCount');
  5197. const customServerCountInput = popup.querySelector('#customServerCount');
  5198. const confirmButton = popup.querySelector('#confirmServerCount');
  5199. const closeButton = popup.querySelector('#closePopup');
  5200.  
  5201. serverCountDropdown.addEventListener('change', () => {
  5202. if (serverCountDropdown.value === 'custom') {
  5203. customServerCountInput.style.display = 'block';
  5204. } else {
  5205. customServerCountInput.style.display = 'none';
  5206. }
  5207. });
  5208.  
  5209. confirmButton.addEventListener('click', () => {
  5210. let serverCount;
  5211.  
  5212. if (serverCountDropdown.value === 'custom') {
  5213. serverCount = parseInt(customServerCountInput.value);
  5214. if (isNaN(serverCount) || serverCount < 1 || serverCount > 1000) {
  5215. notifications('Error: Please enter a valid number between 1 and 1000.', 'error', '⚠️', '5000');
  5216. return;
  5217. }
  5218. } else {
  5219. serverCount = parseInt(serverCountDropdown.value);
  5220. }
  5221.  
  5222. if (serverCount > 100) {
  5223. notifications('Searching over 100 servers may take some time and you might get rate limited!', 'info', '⌛', '8000');
  5224. }
  5225.  
  5226. callback(serverCount);
  5227. disableFilterButton(true);
  5228. disableLoadMoreButton(true);
  5229. hidePopup();
  5230. Loadingbar(true);
  5231. });
  5232.  
  5233. closeButton.addEventListener('click', () => {
  5234. hidePopup();
  5235. });
  5236.  
  5237. function hidePopup() {
  5238. const overlay = document.querySelector('.overlay');
  5239. const popup = document.querySelector('.filter-popup');
  5240.  
  5241. overlay.classList.add('fade-out');
  5242. popup.classList.add('fade-out');
  5243.  
  5244. setTimeout(() => {
  5245. overlay.remove();
  5246. popup.remove();
  5247. }, 300);
  5248. }
  5249. }
  5250.  
  5251.  
  5252. // Function to fetch public servers
  5253. // totallimit is amount of sevrers to fetch
  5254. async function fetchPublicServers(gameId, totalLimit) {
  5255. let servers = [];
  5256. let cursor = null;
  5257.  
  5258. while (servers.length < totalLimit) { // too lazy to comment any of this. hopefully i remember what this does in the future
  5259. const url = `https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=100${cursor ? `&cursor=${cursor}` : ''}`;
  5260.  
  5261. const response = await new Promise((resolve, reject) => {
  5262. GM_xmlhttpRequest({
  5263. method: "GET",
  5264. url: url,
  5265. onload: function(response) {
  5266. resolve(JSON.parse(response.responseText));
  5267. },
  5268. onerror: function(error) {
  5269. reject(`Failed to fetch public servers: ${error}`);
  5270. },
  5271. });
  5272. });
  5273.  
  5274. servers = servers.concat(response.data);
  5275.  
  5276. if (!response.nextPageCursor || servers.length >= totalLimit) {
  5277. break;
  5278. }
  5279.  
  5280. cursor = response.nextPageCursor;
  5281. await delay(3000); // wait 3 seconds before each page request. if u think this is slow i tried 1 second or 2 seconds and got rate limited :|
  5282. }
  5283.  
  5284. return servers.slice(0, totalLimit);
  5285. }
  5286.  
  5287. function createFilterDropdowns(servers) {
  5288. // Create the main filter container
  5289. const filterContainer = document.createElement('div');
  5290. Object.assign(filterContainer.style, {
  5291. display: 'flex',
  5292. gap: '28px',
  5293. alignItems: 'center',
  5294. padding: '32px 36px',
  5295. background: 'linear-gradient(145deg, rgba(18,18,18,0.98) 0%, rgba(10,10,10,0.98) 100%)',
  5296. borderRadius: '24px',
  5297. boxShadow: '0 20px 40px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.05)',
  5298. backdropFilter: 'blur(30px)',
  5299. opacity: '0',
  5300. transform: 'translateY(-40px) scale(0.95)',
  5301. transition: 'all 0.9s cubic-bezier(0.19, 1, 0.22, 1)',
  5302. position: 'relative',
  5303. border: '1px solid rgba(255,255,255,0.08)',
  5304. margin: '32px',
  5305. fontFamily: "'Inter', system-ui, -apple-system, sans-serif",
  5306. fontSize: '16px'
  5307. });
  5308.  
  5309. // Enhanced animated border glow with more effect
  5310. const borderGlow = document.createElement('div');
  5311. Object.assign(borderGlow.style, {
  5312. position: 'absolute',
  5313. inset: '-1px',
  5314. borderRadius: '24px',
  5315. pointerEvents: 'none',
  5316. background: 'linear-gradient(45deg, rgba(255,30,30,0.2), rgba(255,80,80,0.05), rgba(255,30,30,0.15), rgba(255,80,80,0.05))',
  5317. backgroundSize: '400% 400%',
  5318. zIndex: '-1',
  5319. animation: 'gradientFlow 15s ease infinite',
  5320. opacity: '0.8',
  5321. filter: 'blur(1px)'
  5322. });
  5323. filterContainer.appendChild(borderGlow);
  5324.  
  5325. // add CSS for animations and scrollbar styling
  5326. const style = document.createElement('style');
  5327. style.textContent = `
  5328. @keyframes gradientFlow {
  5329. 0% { background-position: 0% 50%; }
  5330. 50% { background-position: 100% 50%; }
  5331. 100% { background-position: 0% 50%; }
  5332. }
  5333.  
  5334. @keyframes pulse {
  5335. 0% { box-shadow: 0 0 0 0 rgba(255, 40, 40, 0.4); }
  5336. 70% { box-shadow: 0 0 0 12px rgba(255, 40, 40, 0); }
  5337. 100% { box-shadow: 0 0 0 0 rgba(255, 40, 40, 0); }
  5338. }
  5339.  
  5340. select::-webkit-scrollbar {
  5341. width: 8px;
  5342. }
  5343. select::-webkit-scrollbar-track {
  5344. background: rgba(20,20,20,0.6);
  5345. border-radius: 8px;
  5346. }
  5347. select::-webkit-scrollbar-thumb {
  5348. background: rgba(255,40,40,0.5);
  5349. border-radius: 8px;
  5350. border: 2px solid rgba(20,20,20,0.6);
  5351. }
  5352. select::-webkit-scrollbar-thumb:hover {
  5353. background: rgba(255,40,40,0.7);
  5354. }
  5355.  
  5356. .logo-pulse {
  5357. animation: pulse 2s infinite;
  5358. }
  5359. `;
  5360. document.head.appendChild(style);
  5361.  
  5362. // Enhanced logo with more sophisticated hover effects
  5363. const logoWrapper = document.createElement('div');
  5364. Object.assign(logoWrapper.style, {
  5365. position: 'relative',
  5366. marginRight: '24px',
  5367. });
  5368.  
  5369. const logo = document.createElement('img');
  5370. logo.src = window.Base64Images.logo;
  5371. Object.assign(logo.style, {
  5372. width: '60px',
  5373. height: '60px',
  5374. borderRadius: '16px',
  5375. transition: 'all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1)',
  5376. filter: 'drop-shadow(0 8px 16px rgba(255,40,40,0.3))',
  5377. border: '2px solid rgba(255,40,40,0.3)',
  5378. });
  5379.  
  5380. const logoGlow = document.createElement('div');
  5381. Object.assign(logoGlow.style, {
  5382. position: 'absolute',
  5383. inset: '-4px',
  5384. borderRadius: '20px',
  5385. background: 'radial-gradient(circle at center, rgba(255,40,40,0.4) 0%, rgba(255,40,40,0) 70%)',
  5386. opacity: '0',
  5387. transition: 'opacity 0.5s ease',
  5388. pointerEvents: 'none',
  5389. zIndex: '-1',
  5390. });
  5391.  
  5392. logo.addEventListener('mouseover', () => {
  5393. logo.style.transform = 'rotate(-8deg) scale(1.15)';
  5394. logo.style.filter = 'drop-shadow(0 12px 24px rgba(255,40,40,0.5))';
  5395. logo.style.border = '2px solid rgba(255,40,40,0.6)';
  5396. logoGlow.style.opacity = '1';
  5397. logo.classList.add('logo-pulse');
  5398. });
  5399.  
  5400. logo.addEventListener('mouseout', () => {
  5401. logo.style.transform = 'rotate(0) scale(1)';
  5402. logo.style.filter = 'drop-shadow(0 8px 16px rgba(255,40,40,0.3))';
  5403. logo.style.border = '2px solid rgba(255,40,40,0.3)';
  5404. logoGlow.style.opacity = '0';
  5405. logo.classList.remove('logo-pulse');
  5406. });
  5407.  
  5408. logoWrapper.appendChild(logoGlow);
  5409. logoWrapper.appendChild(logo);
  5410. filterContainer.appendChild(logoWrapper);
  5411.  
  5412. // Function to create a premium dropdown with enhanced styling
  5413. const createDropdown = (id, placeholder) => {
  5414. const wrapper = document.createElement('div');
  5415. Object.assign(wrapper.style, {
  5416. position: 'relative',
  5417. minWidth: '240px',
  5418. flex: '1'
  5419. });
  5420.  
  5421. // Label for the dropdown with subtle animation
  5422. const label = document.createElement('div');
  5423. label.textContent = placeholder.replace('All ', '');
  5424. Object.assign(label.style, {
  5425. color: 'rgba(255,255,255,0.7)',
  5426. marginBottom: '10px',
  5427. fontSize: '14px',
  5428. fontWeight: '500',
  5429. letterSpacing: '0.5px',
  5430. transform: 'translateX(2px)',
  5431. transition: 'color 0.3s ease',
  5432. textTransform: 'uppercase'
  5433. });
  5434. wrapper.appendChild(label);
  5435.  
  5436. const dropdown = document.createElement('select');
  5437. dropdown.id = id;
  5438. dropdown.innerHTML = `<option value="">${placeholder}</option>`;
  5439. Object.assign(dropdown.style, {
  5440. width: '100%',
  5441. padding: '18px 56px 18px 24px',
  5442. fontSize: '16px',
  5443. fontWeight: '500',
  5444. background: 'linear-gradient(145deg, rgba(35,35,35,0.9), rgba(20,20,20,0.9))',
  5445. color: '#FF2828',
  5446. border: '1px solid rgba(255,255,255,0.1)',
  5447. borderRadius: '14px',
  5448. boxShadow: '0 8px 16px rgba(0,0,0,0.25), inset 0 1px 0 rgba(255,255,255,0.05)',
  5449. appearance: 'none',
  5450. cursor: 'pointer',
  5451. transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
  5452. opacity: '0',
  5453. transform: 'translateY(-20px)',
  5454. letterSpacing: '0.3px'
  5455. });
  5456.  
  5457. // Enhanced chevron icon with SVG
  5458. const chevron = document.createElement('div');
  5459. chevron.innerHTML = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>`;
  5460. Object.assign(chevron.style, {
  5461. position: 'absolute',
  5462. right: '22px',
  5463. bottom: '18px',
  5464. transform: 'translateY(0)',
  5465. pointerEvents: 'none',
  5466. transition: 'transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1)',
  5467. color: 'rgba(255,40,40,0.9)',
  5468. display: 'flex',
  5469. alignItems: 'center',
  5470. justifyContent: 'center'
  5471. });
  5472.  
  5473. // Refined dropdown interactions
  5474. dropdown.addEventListener('mouseover', () => {
  5475. dropdown.style.background = 'linear-gradient(145deg, rgba(50,50,50,0.9), rgba(35,35,35,0.9))';
  5476. dropdown.style.boxShadow = '0 12px 24px rgba(0,0,0,0.35), inset 0 1px 0 rgba(255,255,255,0.08)';
  5477. dropdown.style.borderColor = 'rgba(255,40,40,0.3)';
  5478. label.style.color = 'rgba(255,40,40,0.9)';
  5479. chevron.style.transform = 'translateY(0) rotate(180deg)';
  5480. });
  5481.  
  5482. dropdown.addEventListener('mouseout', () => {
  5483. if (document.activeElement !== dropdown) {
  5484. dropdown.style.background = 'linear-gradient(145deg, rgba(35,35,35,0.9), rgba(20,20,20,0.9))';
  5485. dropdown.style.boxShadow = '0 8px 16px rgba(0,0,0,0.25), inset 0 1px 0 rgba(255,255,255,0.05)';
  5486. dropdown.style.borderColor = 'rgba(255,255,255,0.1)';
  5487. label.style.color = 'rgba(255,255,255,0.7)';
  5488. chevron.style.transform = 'translateY(0) rotate(0deg)';
  5489. }
  5490. });
  5491.  
  5492. dropdown.addEventListener('focus', () => {
  5493. dropdown.style.outline = 'none';
  5494. dropdown.style.borderColor = 'rgba(255,40,40,0.5)';
  5495. dropdown.style.boxShadow = '0 12px 24px rgba(0,0,0,0.35), 0 0 0 3px rgba(255,40,40,0.2), inset 0 1px 0 rgba(255,255,255,0.08)';
  5496. label.style.color = 'rgba(255,40,40,0.9)';
  5497. chevron.style.transform = 'translateY(0) rotate(180deg)';
  5498. });
  5499.  
  5500. dropdown.addEventListener('blur', () => {
  5501. dropdown.style.borderColor = 'rgba(255,255,255,0.1)';
  5502. dropdown.style.boxShadow = '0 8px 16px rgba(0,0,0,0.25), inset 0 1px 0 rgba(255,255,255,0.05)';
  5503. label.style.color = 'rgba(255,255,255,0.7)';
  5504. chevron.style.transform = 'translateY(0) rotate(0deg)';
  5505. });
  5506.  
  5507. dropdown.addEventListener('change', () => {
  5508. dropdown.style.transform = 'scale(0.97)';
  5509. setTimeout(() => dropdown.style.transform = 'scale(1)', 150);
  5510.  
  5511. // Add a subtle flash effect on change
  5512. const flash = document.createElement('div');
  5513. Object.assign(flash.style, {
  5514. position: 'absolute',
  5515. inset: '0',
  5516. borderRadius: '14px',
  5517. backgroundColor: 'rgba(255,40,40,0.15)',
  5518. pointerEvents: 'none',
  5519. opacity: '0',
  5520. transition: 'opacity 0.3s ease'
  5521. });
  5522. wrapper.appendChild(flash);
  5523. flash.style.opacity = '1';
  5524. setTimeout(() => {
  5525. flash.style.opacity = '0';
  5526. setTimeout(() => wrapper.removeChild(flash), 300);
  5527. }, 50);
  5528. });
  5529.  
  5530. // Enhanced fade-in animation with staggered timing
  5531. setTimeout(() => {
  5532. dropdown.style.opacity = '1';
  5533. dropdown.style.transform = 'translateY(0)';
  5534. }, 500);
  5535.  
  5536. wrapper.appendChild(dropdown);
  5537. wrapper.appendChild(chevron);
  5538. return wrapper;
  5539. };
  5540.  
  5541. // Create enhanced dropdowns
  5542. const countryDropdown = createDropdown('countryFilter', 'All Countries');
  5543. const cityDropdown = createDropdown('cityFilter', 'All Cities');
  5544.  
  5545. // Populate dropdowns with server data
  5546. const countryCounts = {};
  5547. servers.forEach(server => {
  5548. const country = server.location.country.name;
  5549. countryCounts[country] = (countryCounts[country] || 0) + 1;
  5550. });
  5551.  
  5552. // Sort countries alphabetically
  5553. const sortedCountries = Object.keys(countryCounts).sort();
  5554.  
  5555. sortedCountries.forEach(country => {
  5556. const option = document.createElement('option');
  5557. option.value = country;
  5558. option.textContent = `${country} (${countryCounts[country]})`;
  5559. countryDropdown.querySelector('select').appendChild(option);
  5560. });
  5561.  
  5562. // Add a subtle visual indicator
  5563. const separator = document.createElement('div');
  5564. Object.assign(separator.style, {
  5565. height: '65px',
  5566. width: '1px',
  5567. background: 'linear-gradient(to bottom, rgba(255,255,255,0), rgba(255,40,40,0.3), rgba(255,255,255,0))',
  5568. margin: '0 4px'
  5569. });
  5570.  
  5571. countryDropdown.querySelector('select').addEventListener('change', () => {
  5572. const selectedCountry = countryDropdown.querySelector('select').value;
  5573. cityDropdown.querySelector('select').innerHTML = '<option value="">All Cities</option>';
  5574.  
  5575. if (selectedCountry) {
  5576. const cityCounts = {};
  5577. servers
  5578. .filter(server => server.location.country.name === selectedCountry)
  5579. .forEach(server => {
  5580. const city = server.location.city;
  5581. const region = server.location.region?.name;
  5582. const cityKey = region ? `${city}, ${region}` : city;
  5583. cityCounts[cityKey] = (cityCounts[cityKey] || 0) + 1;
  5584. });
  5585.  
  5586. // Sort cities alphabetically
  5587. const sortedCities = Object.keys(cityCounts).sort();
  5588.  
  5589. sortedCities.forEach(city => {
  5590. const option = document.createElement('option');
  5591. option.value = city;
  5592. option.textContent = `${city} (${cityCounts[city]})`;
  5593. cityDropdown.querySelector('select').appendChild(option);
  5594. });
  5595.  
  5596. // Enhanced animation for city dropdown update
  5597. cityDropdown.querySelector('select').style.opacity = '0.3';
  5598. cityDropdown.querySelector('select').style.transform = 'translateY(-10px)';
  5599. setTimeout(() => {
  5600. cityDropdown.querySelector('select').style.opacity = '1';
  5601. cityDropdown.querySelector('select').style.transform = 'translateY(0)';
  5602. }, 150);
  5603.  
  5604. // Visual indicator that cities were updated
  5605. cityDropdown.style.position = 'relative';
  5606. const updateFlash = document.createElement('div');
  5607. Object.assign(updateFlash.style, {
  5608. position: 'absolute',
  5609. inset: '30px 0 0 0',
  5610. borderRadius: '14px',
  5611. background: 'radial-gradient(circle at center, rgba(255,40,40,0.2) 0%, rgba(255,40,40,0) 70%)',
  5612. pointerEvents: 'none',
  5613. opacity: '1',
  5614. transition: 'opacity 0.8s ease'
  5615. });
  5616. cityDropdown.appendChild(updateFlash);
  5617. setTimeout(() => {
  5618. updateFlash.style.opacity = '0';
  5619. setTimeout(() => cityDropdown.removeChild(updateFlash), 800);
  5620. }, 100);
  5621. }
  5622. });
  5623.  
  5624. // Append dropdowns to container with separator
  5625. filterContainer.appendChild(countryDropdown);
  5626. filterContainer.appendChild(separator);
  5627. filterContainer.appendChild(cityDropdown);
  5628.  
  5629. // Enhanced fade-in container animation
  5630. setTimeout(() => {
  5631. filterContainer.style.opacity = '1';
  5632. filterContainer.style.transform = 'translateY(0) scale(1)';
  5633. }, 300);
  5634.  
  5635. return filterContainer;
  5636. }
  5637.  
  5638. // Function to filter servers based on selected country and city cause im lazy
  5639. function filterServers(servers, country, city) {
  5640. return servers.filter(server => {
  5641. const matchesCountry = !country || server.location.country.name === country;
  5642. const matchesCity = !city || `${server.location.city}${server.location.region?.name ? `, ${server.location.region.name}` : ''}` === city;
  5643. return matchesCountry && matchesCity;
  5644. });
  5645. }
  5646.  
  5647. // Function to sort servers by ping. maybe inaccurate but thats roblox's problem not mine
  5648. function sortServersByPing(servers) {
  5649. return servers.sort((a, b) => a.server.ping - b.server.ping);
  5650. }
  5651.  
  5652. async function fetchPlayerThumbnails_servers(playerTokens) {
  5653. const body = playerTokens.map(token => ({
  5654. requestId: `0:${token}:AvatarHeadshot:150x150:png:regular`,
  5655. type: "AvatarHeadShot",
  5656. targetId: 0,
  5657. token,
  5658. format: "png",
  5659. size: "150x150",
  5660. }));
  5661.  
  5662. const response = await fetch("https://thumbnails.roblox.com/v1/batch", {
  5663. method: "POST",
  5664. headers: {
  5665. "Content-Type": "application/json",
  5666. Accept: "application/json",
  5667. },
  5668. body: JSON.stringify(body),
  5669. });
  5670.  
  5671. const data = await response.json();
  5672. return data.data || [];
  5673. }
  5674.  
  5675. async function rebuildServerList(gameId, totalLimit, best_connection) {
  5676. const serverListContainer = document.getElementById("rbx-public-game-server-item-container");
  5677.  
  5678. // If "Best Connection" is enabled
  5679. // FUNCTION FOR THE 6TH BUTTON!
  5680. if (best_connection === true) {
  5681. disableLoadMoreButton(true);
  5682. disableFilterButton(true);
  5683. notifications("Retrieving Location...", "success", "🌎", '5000')
  5684. // Ask for the user's location
  5685. const userLocation = await getUserLocation();
  5686. if (!userLocation) {
  5687. notifications('Error: Unable to fetch your location. Please enable location access.', 'error', '⚠️', '5000');
  5688. disableFilterButton(false);
  5689. return;
  5690. }
  5691.  
  5692. // Fetch 50 servers
  5693. const servers = await fetchPublicServers(gameId, 50);
  5694. if (servers.length === 0) {
  5695. notifications('Error: No servers found. Please try again later.', 'error', '⚠️', '5000');
  5696. disableFilterButton(false);
  5697. return;
  5698. }
  5699.  
  5700. // Calculate distances and find the closest server
  5701. let closestServer = null;
  5702. let minDistance = Infinity;
  5703. let closestServerLocation = null;
  5704.  
  5705. for (const server of servers) {
  5706. const {
  5707. id: serverId,
  5708. maxPlayers,
  5709. playing
  5710. } = server;
  5711.  
  5712. // Skip full servers
  5713. if (playing >= maxPlayers) {
  5714. continue;
  5715. }
  5716.  
  5717. try {
  5718. // Fetch server location
  5719. const location = await fetchServerDetails(gameId, serverId);
  5720.  
  5721. // Calculate distance
  5722. const distance = calculateDistance(
  5723. userLocation.latitude,
  5724. userLocation.longitude,
  5725. location.latitude,
  5726. location.longitude
  5727. );
  5728.  
  5729. // Update closest server
  5730. if (distance < minDistance) {
  5731. minDistance = distance;
  5732. closestServer = server;
  5733. closestServerLocation = location;
  5734. }
  5735. } catch (error) {
  5736. ConsoleLogEnabled(`Error fetching details for server ${serverId}:`, error);
  5737. // Skip this server and continue with the next one
  5738. continue;
  5739. }
  5740. }
  5741.  
  5742. if (closestServer) {
  5743. // Automatically join the closest server
  5744. showLoadingOverlay();
  5745. Roblox.GameLauncher.joinGameInstance(gameId, closestServer.id); // why does the js beautifer make this code here look trash
  5746. notifications(`Joining nearest server!
  5747. Server ID: ${closestServer.id}
  5748. Distance: ${(minDistance / 1.609).toFixed(2)} miles | ${minDistance.toFixed(2)} km
  5749. Location (Country): ${closestServerLocation.country.name}.`, 'success', '🚀', '5000');
  5750.  
  5751. disableFilterButton(false);
  5752. Loadingbar(false);
  5753. } else {
  5754. notifications('No valid servers found. Please try again later after refreshing the webpage. Filter button disabled.', 'error', '⚠️', '8000');
  5755. Loadingbar(false);
  5756. }
  5757.  
  5758. return; // Exit the function after joining the best server
  5759. }
  5760.  
  5761. // Rest of the original function (for non-"Best Connection" mode)
  5762. if (!serverListContainer) {
  5763. ConsoleLogEnabled("Server list container not found!");
  5764. notifications('Error: No Servers found. There is nobody playing this game. Please refresh the page.', 'error', '⚠️', '8000');
  5765. Loadingbar(false);
  5766. return;
  5767. }
  5768.  
  5769. const messageElement = showMessage("Just a moment — to detect your location accurately, please stay on this page...");
  5770. const premium_message = messageElement.querySelector('.premium-message-text');
  5771.  
  5772. try {
  5773. // Retrieve user's location for distance calculations
  5774. const userLocation = await getUserLocation(); // this should be useless because of the new manual feature.
  5775. if (!userLocation) {
  5776. notifications('Error: Unable to fetch your location. Please enable location access.', 'error', '⚠️', '5000');
  5777. disableFilterButton(false);
  5778. return;
  5779. }
  5780.  
  5781. const servers = await fetchPublicServers(gameId, totalLimit);
  5782. const totalServers = servers.length;
  5783. let skippedServers = 0;
  5784.  
  5785. if (premium_message) {
  5786. premium_message.textContent = `Filtering servers... Please stay on this page to ensure a faster and more accurate search. ${totalServers} servers found, 0 loaded so far.`;
  5787. }
  5788. notifications(`Please do not leave this page as it slows down the search. \nFound a total of ${totalServers} servers.`, 'success', '👍', '8000');
  5789.  
  5790. const serverDetails = [];
  5791. for (let i = 0; i < servers.length; i++) {
  5792. const server = servers[i];
  5793. const {
  5794. id: serverId,
  5795. maxPlayers,
  5796. playing,
  5797. ping,
  5798. fps,
  5799. playerTokens
  5800. } = server;
  5801.  
  5802. let location;
  5803. try {
  5804. location = await fetchServerDetails(gameId, serverId);
  5805. } catch (error) {
  5806. if (error === 'purchase_required') {
  5807. if (premium_message) {
  5808. premium_message.textContent = "Error: Cannot access server regions because you have not purchased the game.";
  5809. }
  5810. notifications('Cannot access server regions because you have not purchased the game.', 'error', '⚠️', '15000');
  5811. Loadingbar(false); // disable loading bar
  5812. return;
  5813. return;
  5814. } else if (error === 'subplace_join_restriction') {
  5815. if (premium_message) {
  5816. premium_message.textContent = "Error: This game requires users to teleport to a subplace. As a result, server regions cannot be retrieved.";
  5817. }
  5818. notifications('Error: This game requires users to teleport to a subplace. As a result, server regions cannot be retrieved.', 'error', '⚠️', '15000');
  5819. Loadingbar(false); // disable loading bar
  5820. return;
  5821. } else {
  5822. ConsoleLogEnabled(error);
  5823. location = {
  5824. city: "Unknown",
  5825. country: {
  5826. name: "Unknown",
  5827. code: "??"
  5828. }
  5829. };
  5830. }
  5831. }
  5832.  
  5833.  
  5834. if (location.city === "Unknown" || playing >= maxPlayers) {
  5835. ConsoleLogEnabled(`Skipping server ${serverId} because it is full or location is unknown.`);
  5836. skippedServers++;
  5837. continue;
  5838. }
  5839.  
  5840. // Fetch player thumbnails
  5841. const playerThumbnails = playerTokens && playerTokens.length > 0 ? await fetchPlayerThumbnails_servers(playerTokens) : [];
  5842.  
  5843. serverDetails.push({
  5844. server,
  5845. location,
  5846. playerThumbnails
  5847. });
  5848. if (premium_message) {
  5849. premium_message.textContent = `Filtering servers, please do not leave this page...\n${totalServers} servers found, ${i + 1} server locations found`;
  5850. }
  5851. }
  5852.  
  5853. if (serverDetails.length === 0) {
  5854. showMessage("END");
  5855. notifications('Error: No servers found. Please try again with an increase in the number of servers to search for.', 'error', '⚠️', '15000');
  5856. Loadingbar(false); // disable loading bar
  5857. return;
  5858. }
  5859.  
  5860. const loadedServers = totalServers - skippedServers;
  5861. notifications(`Filtering complete!\n${totalServers} servers found, ${loadedServers} servers loaded, ${skippedServers} servers skipped (full).`, 'success', '👍', '5000');
  5862. showMessage("END");
  5863. Loadingbar(false); // disable loading bar
  5864.  
  5865. // Add filter dropdowns
  5866. const filterContainer = createFilterDropdowns(serverDetails);
  5867. serverListContainer.parentNode.insertBefore(filterContainer, serverListContainer);
  5868.  
  5869. // Style the server list container to use a grid layout
  5870. serverListContainer.style.display = "grid";
  5871. serverListContainer.style.gridTemplateColumns = "repeat(4, 1fr)"; // 4 columns
  5872. serverListContainer.style.gap = "0px"; // Set gap to 0px to remove the gap between grid items
  5873.  
  5874. const displayFilteredServers = (country, city) => {
  5875. serverListContainer.innerHTML = "";
  5876.  
  5877. const filteredServers = filterServers(serverDetails, country, city);
  5878. // Sort servers by distance from the user (fastest first)
  5879. const sortedServers = filteredServers.sort((a, b) => {
  5880. const distanceA = calculateDistance(userLocation.latitude, userLocation.longitude, a.location.latitude, a.location.longitude);
  5881. const distanceB = calculateDistance(userLocation.latitude, userLocation.longitude, b.location.latitude, b.location.longitude);
  5882. return distanceA - distanceB;
  5883. });
  5884.  
  5885. sortedServers.forEach(({
  5886. server,
  5887. location,
  5888. playerThumbnails
  5889. }) => {
  5890. const serverCard = document.createElement("li");
  5891. serverCard.className = "rbx-game-server-item col-md-3 col-sm-4 col-xs-6";
  5892. serverCard.style.width = "100%";
  5893. serverCard.style.minHeight = "400px";
  5894. serverCard.style.display = "flex";
  5895. serverCard.style.flexDirection = "column";
  5896. serverCard.style.justifyContent = "space-between";
  5897. serverCard.style.boxSizing = "border-box";
  5898. serverCard.style.outline = 'none';
  5899. serverCard.style.padding = '6px';
  5900. serverCard.style.borderRadius = '8px';
  5901.  
  5902. // Create label (now based on distance)
  5903. const pingLabel = document.createElement("div");
  5904. pingLabel.style.marginBottom = "5px"; // Adds spacing above label
  5905. pingLabel.style.padding = "5px 10px";
  5906. pingLabel.style.borderRadius = "8px";
  5907. pingLabel.style.fontWeight = "bold";
  5908. pingLabel.style.textAlign = "center"; // Centers the label
  5909.  
  5910. // Calculate distance from user to server
  5911. const distance = calculateDistance(
  5912. userLocation.latitude,
  5913. userLocation.longitude,
  5914. location.latitude,
  5915. location.longitude
  5916. );
  5917.  
  5918. // Calculate ping using the advanced formula
  5919. const calculatedPing = 2 * (distance / 180000) * 1000 * 1.8 + (20 + 0.04 * distance);
  5920.  
  5921. if (distance < 1250) {
  5922. pingLabel.textContent = "⚡ Fast";
  5923. pingLabel.style.backgroundColor = "#014737";
  5924. pingLabel.style.color = "#73e1bc";
  5925. } else if (distance < 5000) {
  5926. pingLabel.textContent = "⏳ OK";
  5927. pingLabel.style.backgroundColor = "#c75a00";
  5928. pingLabel.style.color = "#ffe8c2";
  5929. } else {
  5930. pingLabel.textContent = "🐌 Slow";
  5931. pingLabel.style.backgroundColor = "#771d1d";
  5932. pingLabel.style.color = "#fcc468";
  5933. }
  5934.  
  5935. // Create a container for player thumbnails
  5936. const thumbnailsContainer = document.createElement("div");
  5937. thumbnailsContainer.className = "player-thumbnails-container";
  5938. thumbnailsContainer.style.display = "grid";
  5939. thumbnailsContainer.style.gridTemplateColumns = "repeat(3, 60px)";
  5940. thumbnailsContainer.style.gridTemplateRows = "repeat(2, 60px)";
  5941. thumbnailsContainer.style.gap = "5px";
  5942. thumbnailsContainer.style.marginBottom = "10px";
  5943.  
  5944. // Add player thumbnails to the container (max 5)
  5945. const maxThumbnails = 5;
  5946. const displayedThumbnails = playerThumbnails.slice(0, maxThumbnails);
  5947. displayedThumbnails.forEach(thumb => {
  5948. if (thumb && thumb.imageUrl) {
  5949. const img = document.createElement("img");
  5950. img.src = thumb.imageUrl;
  5951. img.className = "avatar-card-image";
  5952. img.style.width = "60px";
  5953. img.style.height = "60px";
  5954. img.style.borderRadius = "50%";
  5955. thumbnailsContainer.appendChild(img);
  5956. }
  5957. });
  5958.  
  5959. // Add a placeholder for hidden players
  5960. const hiddenPlayers = server.playing - displayedThumbnails.length;
  5961. if (hiddenPlayers > 0) {
  5962. const placeholder = document.createElement("div");
  5963. placeholder.className = "avatar-card-image";
  5964. placeholder.style.width = "60px";
  5965. placeholder.style.height = "60px";
  5966. placeholder.style.borderRadius = "50%";
  5967. placeholder.style.backgroundColor = "#6a6f81";
  5968. placeholder.style.display = "flex";
  5969. placeholder.style.alignItems = "center";
  5970. placeholder.style.justifyContent = "center";
  5971. placeholder.style.color = "#fff";
  5972. placeholder.style.fontSize = "14px";
  5973. placeholder.textContent = `+${hiddenPlayers}`;
  5974. thumbnailsContainer.appendChild(placeholder);
  5975. }
  5976.  
  5977. // Server card content with both distance and calculated ping, with line spacers between each info
  5978. const cardItem = document.createElement("div");
  5979. cardItem.className = "card-item";
  5980. cardItem.style.display = "flex";
  5981. cardItem.style.flexDirection = "column";
  5982. cardItem.style.justifyContent = "space-between";
  5983. cardItem.style.height = "100%";
  5984.  
  5985. cardItem.innerHTML = `
  5986. ${thumbnailsContainer.outerHTML}
  5987. <div class="rbx-game-server-details game-server-details">
  5988. <div class="text-info rbx-game-status rbx-game-server-status text-overflow">
  5989. ${server.playing} of ${server.maxPlayers} people max
  5990. </div>
  5991. <div class="server-player-count-gauge border">
  5992. <div class="gauge-inner-bar border" style="width: ${(server.playing / server.maxPlayers) * 100}%;"></div>
  5993. </div>
  5994. <span data-placeid="${gameId}">
  5995. <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>
  5996. </span>
  5997. </div>
  5998. <div style="margin-top: 10px; text-align: center;">
  5999. ${pingLabel.outerHTML}
  6000. <div class="info-lines" style="margin-top: 8px;">
  6001. <div class="ping-info">Ping: ${calculatedPing.toFixed(2)} ms</div>
  6002. <hr style="margin: 6px 0;">
  6003. <div class="ping-info">Distance: ${distance.toFixed(2)} km</div>
  6004. <hr style="margin: 6px 0;">
  6005. <div class="location-info">${location.city}, ${location.country.name}</div>
  6006. <hr style="margin: 6px 0;">
  6007. <div class="fps-info">FPS: ${Math.round(server.fps)}</div>
  6008. </div>
  6009. </div>
  6010. `;
  6011.  
  6012. const joinButton = cardItem.querySelector(".rbx-game-server-join");
  6013. joinButton.addEventListener("click", () => {
  6014. ConsoleLogEnabled(`Roblox.GameLauncher.joinGameInstance(${gameId}, "${server.id}")`);
  6015. showLoadingOverlay();
  6016. Roblox.GameLauncher.joinGameInstance(gameId, server.id);
  6017. });
  6018.  
  6019. const container = adjustJoinButtonContainer(joinButton);
  6020. const inviteButton = createInviteButton(gameId, server.id);
  6021. container.appendChild(inviteButton);
  6022.  
  6023. serverCard.appendChild(cardItem);
  6024. serverListContainer.appendChild(serverCard);
  6025. });
  6026. };
  6027.  
  6028. // Add event listeners to dropdowns
  6029. const countryFilter = document.getElementById('countryFilter');
  6030. const cityFilter = document.getElementById('cityFilter');
  6031.  
  6032. countryFilter.addEventListener('change', () => {
  6033. displayFilteredServers(countryFilter.value, cityFilter.value);
  6034. });
  6035.  
  6036. cityFilter.addEventListener('change', () => {
  6037. displayFilteredServers(countryFilter.value, cityFilter.value);
  6038. });
  6039.  
  6040. // Display all servers initially
  6041. displayFilteredServers("", "");
  6042. } catch (error) {
  6043. ConsoleLogEnabled("Error rebuilding server list:", error);
  6044. notifications('Filtering Error: Failed to obtain permission to send API requests to the Roblox API. Please allow the script to enable request sending.', 'error', '⚠️ ', '8000');
  6045. if (premium_message) {
  6046. premium_message.textContent = "Filtering Error: Failed to obtain permission to send API requests to the Roblox API. Please allow the script to enable request sending.";
  6047. }
  6048. Loadingbar(false); // enable loading bar
  6049. } finally {
  6050. 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
  6051. disableFilterButton(false); // beta test filter button
  6052. //notifications('Note: The filter button is disabled while using server regions. Refresh to enable it.', 'info', '🔄', '15000') // old stuff not needed anymore
  6053. }
  6054. }
  6055.  
  6056.  
  6057.  
  6058.  
  6059. // Function to extract the game ID from the URL
  6060. function extractGameId() {
  6061. const url = window.location.href;
  6062. const match = url.match(/roblox\.com\/games\/(\d+)/);
  6063.  
  6064. if (match && match[1]) {
  6065. return match[1]; // Return the game ID
  6066. }
  6067. return null; // Return null if no game ID is found
  6068. }
  6069.  
  6070. // Log the game ID to the console
  6071. const gameId = extractGameId();
  6072.  
  6073. // Function to create and append the Invite button
  6074. function createInviteButton(placeId, serverId) { // too lazy to comment this function tbh just ready the name
  6075. const inviteButton = document.createElement('button');
  6076. inviteButton.textContent = 'Invite';
  6077. inviteButton.className = 'btn-control-xs btn-primary-md btn-min-width btn-full-width';
  6078. inviteButton.style.width = '25%';
  6079. inviteButton.style.marginLeft = '5px';
  6080.  
  6081. inviteButton.style.padding = '4px 8px';
  6082. inviteButton.style.fontSize = '12px';
  6083. inviteButton.style.borderRadius = '8px';
  6084. inviteButton.style.backgroundColor = '#3b3e49';
  6085. inviteButton.style.borderColor = '#3b3e49';
  6086. inviteButton.style.color = '#ffffff';
  6087. inviteButton.style.cursor = 'pointer';
  6088. inviteButton.style.fontWeight = '500';
  6089. inviteButton.style.textAlign = 'center';
  6090. inviteButton.style.whiteSpace = 'nowrap';
  6091. inviteButton.style.verticalAlign = 'middle';
  6092. inviteButton.style.lineHeight = '100%';
  6093. inviteButton.style.fontFamily = 'Builder Sans, Helvetica Neue, Helvetica, Arial, Lucida Grande, sans-serif';
  6094. inviteButton.style.textRendering = 'auto';
  6095. inviteButton.style.webkitFontSmoothing = 'antialiased';
  6096. inviteButton.style.mozOsxFontSmoothing = 'grayscale';
  6097.  
  6098. inviteButton.addEventListener('click', () => {
  6099. const inviteLink = `https://oqarshi.github.io/Invite/?placeid=${placeId}&serverid=${serverId}`;
  6100. navigator.clipboard.writeText(inviteLink).then(() => {
  6101. ConsoleLogEnabled(`Invite link copied to clipboard: ${inviteLink}`);
  6102. notifications('Success! Invite link copied to clipboard!', 'success', '🎉', ' 2000');
  6103. }).catch(() => {
  6104. ConsoleLogEnabled('Failed to copy invite link.');
  6105. notifications('Error: Failed to copy invite link', 'error', '😔', '2000');
  6106. });
  6107. });
  6108.  
  6109. return inviteButton;
  6110. }
  6111.  
  6112. // Function to adjust the Join button and its container
  6113. function adjustJoinButtonContainer(joinButton) {
  6114. const container = document.createElement('div');
  6115. container.style.display = 'flex';
  6116. container.style.width = '100%';
  6117.  
  6118. joinButton.style.width = '75%';
  6119.  
  6120. joinButton.parentNode.insertBefore(container, joinButton);
  6121. container.appendChild(joinButton);
  6122.  
  6123. return container;
  6124. }
  6125.  
  6126.  
  6127.  
  6128.  
  6129. /*********************************************************************************************************************************************************************************************************************************************
  6130. Functions for the 6th button.
  6131.  
  6132. *********************************************************************************************************************************************************************************************************************************************/
  6133.  
  6134.  
  6135.  
  6136.  
  6137. function calculateDistance(lat1, lon1, lat2, lon2) {
  6138. const R = 6371; // Radius of the Earth in kilometers
  6139. const dLat = (lat2 - lat1) * (Math.PI / 180);
  6140. const dLon = (lon2 - lon1) * (Math.PI / 180);
  6141. const a =
  6142. Math.sin(dLat / 2) * Math.sin(dLat / 2) +
  6143. Math.cos(lat1 * (Math.PI / 180)) * Math.cos(lat2 * (Math.PI / 180)) *
  6144. Math.sin(dLon / 2) * Math.sin(dLon / 2);
  6145. const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  6146. return R * c; // Distance in kilometers
  6147. }
  6148.  
  6149.  
  6150.  
  6151. // Fallback location resolver with timezone-based estimation
  6152. function resolveOfflineFallbackLocation(resolve) {
  6153. ConsoleLogEnabled("Attempting offline location estimation...");
  6154.  
  6155. let guessedLocation = null;
  6156. let closestLocation = null;
  6157. let closestDistance = Infinity;
  6158.  
  6159. const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || "";
  6160. const timezoneMap = {
  6161. "America/Los_Angeles": {
  6162. lat: 34.0522,
  6163. lon: -118.2437
  6164. },
  6165. "America/Denver": {
  6166. lat: 39.7392,
  6167. lon: -104.9903
  6168. },
  6169. "America/Chicago": {
  6170. lat: 41.8781,
  6171. lon: -87.6298
  6172. },
  6173. "America/New_York": {
  6174. lat: 40.7128,
  6175. lon: -74.006
  6176. },
  6177. "Europe/London": {
  6178. lat: 51.5074,
  6179. lon: -0.1278
  6180. },
  6181. "Europe/Berlin": {
  6182. lat: 52.52,
  6183. lon: 13.405
  6184. },
  6185. "Europe/Paris": {
  6186. lat: 48.8566,
  6187. lon: 2.3522
  6188. },
  6189. "Asia/Tokyo": {
  6190. lat: 35.6895,
  6191. lon: 139.6917
  6192. },
  6193. "Asia/Kolkata": {
  6194. lat: 28.6139,
  6195. lon: 77.209
  6196. },
  6197. "Australia/Sydney": {
  6198. lat: -33.8688,
  6199. lon: 151.2093
  6200. },
  6201. "America/Argentina/Buenos_Aires": {
  6202. lat: -34.6037,
  6203. lon: -58.3816
  6204. },
  6205. "Africa/Nairobi": {
  6206. lat: -1.286389,
  6207. lon: 36.817223
  6208. },
  6209. "Asia/Singapore": {
  6210. lat: 1.3521,
  6211. lon: 103.8198
  6212. },
  6213. "America/Toronto": {
  6214. lat: 43.65107,
  6215. lon: -79.347015
  6216. },
  6217. "Europe/Moscow": {
  6218. lat: 55.7558,
  6219. lon: 37.6173
  6220. },
  6221. "Europe/Madrid": {
  6222. lat: 40.4168,
  6223. lon: -3.7038
  6224. },
  6225. "Asia/Shanghai": {
  6226. lat: 31.2304,
  6227. lon: 121.4737
  6228. },
  6229. "Africa/Cairo": {
  6230. lat: 30.0444,
  6231. lon: 31.2357
  6232. },
  6233. "Africa/Johannesburg": {
  6234. lat: -26.2041,
  6235. lon: 28.0473
  6236. },
  6237. "Europe/Amsterdam": {
  6238. lat: 52.3676,
  6239. lon: 4.9041
  6240. },
  6241. "Asia/Manila": {
  6242. lat: 14.5995,
  6243. lon: 120.9842
  6244. },
  6245. "Asia/Seoul": {
  6246. lat: 37.5665,
  6247. lon: 126.978
  6248. }
  6249. };
  6250.  
  6251.  
  6252. // If user's timezone is available in the map
  6253. if (timezoneMap[timezone]) {
  6254. guessedLocation = timezoneMap[timezone];
  6255. ConsoleLogEnabled("User's timezone found:", timezone);
  6256. }
  6257.  
  6258. // If the timezone is not found, find the closest match
  6259. if (!guessedLocation) {
  6260. ConsoleLogEnabled("User's timezone not found. Finding closest match...");
  6261. Object.keys(timezoneMap).forEach((tz) => {
  6262. const location = timezoneMap[tz];
  6263. const distance = calculateDistance(location.lat, location.lon, 0, 0); // Distance from the equator (0,0)
  6264. if (distance < closestDistance) {
  6265. closestDistance = distance;
  6266. closestLocation = location;
  6267. }
  6268. });
  6269. guessedLocation = closestLocation;
  6270. }
  6271.  
  6272. // If we found a location, return it, otherwise default to New York
  6273. if (guessedLocation) {
  6274. notifications("Estimated location based on timezone. Please allow location access to see what servers are closest to you or change to manual in settings.", "info", "🕒", "6000");
  6275. resolve({
  6276. latitude: guessedLocation.lat,
  6277. longitude: guessedLocation.lon
  6278. });
  6279. } else {
  6280. notifications("Could not estimate location. Fatal error, please report on Greasyfork. Using default (New York).", "error", "⚠️", "6000");
  6281. resolve({
  6282. latitude: 40.7128,
  6283. longitude: -74.0060
  6284. }); // Default to NYC
  6285. }
  6286. }
  6287.  
  6288.  
  6289.  
  6290. function getUserLocation() {
  6291. return new Promise((resolve, reject) => {
  6292. // Check priority location setting
  6293. const priorityLocation = localStorage.getItem("ROLOCATE_prioritylocation") || "automatic";
  6294.  
  6295. // If in manual mode, use stored coordinates
  6296. if (priorityLocation === "manual") {
  6297. try {
  6298. const coords = JSON.parse(GM_getValue("ROLOCATE_coordinates", '{"lat":"","lng":""}'));
  6299. if (coords.lat && coords.lng) {
  6300. ConsoleLogEnabled("Using manual location from storage");
  6301. notifications("We successfully detected your location.", "success", "🌎", "2000");
  6302. return resolve({
  6303. latitude: parseFloat(coords.lat), // Changed to match automatic mode
  6304. longitude: parseFloat(coords.lng), // Changed to match automatic mode
  6305. source: "manual",
  6306. accuracy: 0 // Manual coordinates have no accuracy metric
  6307. });
  6308. } else {
  6309. ConsoleLogEnabled("Manual mode selected but no coordinates set - falling back to automatic behavior");
  6310. notifications("Manual mode selected but no coordinates set. Fatal error: Report on greasyfork. Using Automatic Mode.", "error", "", "2000");
  6311. // Fall through to automatic behavior
  6312. }
  6313. } catch (e) {
  6314. ConsoleLogEnabled("Error reading manual coordinates:", e);
  6315. notifications("Error reading manual coordinates. Fatal error: Report on greasyfork. Using Automatic Mode.", "error", "", "2000");
  6316. // Fall through to automatic behavior
  6317. }
  6318. }
  6319.  
  6320. // Automatic mode behavior
  6321. if (!navigator.geolocation) {
  6322. ConsoleLogEnabled("Geolocation not supported.");
  6323. notifications("Geolocation is not supported by your browser.", "error", "⚠️", "15000");
  6324. return resolveOfflineFallbackLocation(resolve);
  6325. }
  6326.  
  6327. navigator.geolocation.getCurrentPosition(
  6328. (position) => resolveSuccess(position, resolve),
  6329. async (error) => {
  6330. ConsoleLogEnabled("Geolocation error:", error);
  6331.  
  6332. // Attempt to inspect geolocation permission state
  6333. try {
  6334. if (navigator.permissions && navigator.permissions.query) {
  6335. const permissionStatus = await navigator.permissions.query({
  6336. name: "geolocation"
  6337. });
  6338. ConsoleLogEnabled("Geolocation permission status:", permissionStatus.state);
  6339.  
  6340. if (permissionStatus.state === "denied") {
  6341. return resolveOfflineFallbackLocation(resolve);
  6342. }
  6343. }
  6344. } catch (permError) {
  6345. ConsoleLogEnabled("Permission check failed:", permError);
  6346. }
  6347.  
  6348. // Retry geolocation once with a slightly relaxed setting
  6349. navigator.geolocation.getCurrentPosition(
  6350. (position) => resolveSuccess(position, resolve),
  6351. (retryError) => {
  6352. ConsoleLogEnabled("Second geolocation attempt failed:", retryError);
  6353. notifications("Could not get your location. Using fallback.", "error", "⚠️", "15000");
  6354. resolveOfflineFallbackLocation(resolve);
  6355. }, {
  6356. maximumAge: 5000,
  6357. timeout: 10000,
  6358. }
  6359. );
  6360. }, {
  6361. timeout: 10000,
  6362. maximumAge: 0,
  6363. }
  6364. );
  6365. });
  6366. }
  6367.  
  6368.  
  6369. // Success Handler (unchanged)
  6370. function resolveSuccess(position, resolve) {
  6371. notifications("We successfully detected your location.", "success", "🌎", "2000");
  6372. disableLoadMoreButton(true);
  6373. disableFilterButton(true);
  6374. Loadingbar(true);
  6375. resolve({
  6376. latitude: position.coords.latitude,
  6377. longitude: position.coords.longitude,
  6378. source: "geolocation",
  6379. accuracy: position.coords.accuracy
  6380. });
  6381. }
  6382.  
  6383.  
  6384.  
  6385. /*********************************************************************************************************************************************************************************************************************************************
  6386. Functions for the 7th button.
  6387.  
  6388. *********************************************************************************************************************************************************************************************************************************************/
  6389. async function auto_join_small_server() {
  6390. // Disable the "Load More" button and show the loading bar
  6391. Loadingbar(true);
  6392. disableFilterButton(true);
  6393. disableLoadMoreButton();
  6394.  
  6395. // Get the game ID from the URL
  6396. const gameId = window.location.pathname.split('/')[2];
  6397.  
  6398. // Retry mechanism for 429 errors
  6399. let retries = 3; // Number of retries
  6400. let success = false;
  6401.  
  6402. while (retries > 0 && !success) {
  6403. try {
  6404. // Fetch server data using GM_xmlhttpRequest
  6405. const data = await new Promise((resolve, reject) => {
  6406. GM_xmlhttpRequest({
  6407. method: "GET",
  6408. url: `https://games.roblox.com/v1/games/${gameId}/servers/public?sortOrder=1&excludeFullGames=true&limit=100`,
  6409. onload: function(response) {
  6410. if (response.status === 429) {
  6411. reject('429: Too Many Requests');
  6412. } else if (response.status >= 200 && response.status < 300) {
  6413. resolve(JSON.parse(response.responseText));
  6414. } else {
  6415. reject(`HTTP error: ${response.status}`);
  6416. }
  6417. },
  6418. onerror: function(error) {
  6419. reject(error);
  6420. },
  6421. });
  6422. });
  6423.  
  6424. // Find the server with the lowest player count
  6425. let minPlayers = Infinity;
  6426. let targetServer = null;
  6427.  
  6428. for (const server of data.data) {
  6429. if (server.playing < minPlayers) {
  6430. minPlayers = server.playing;
  6431. targetServer = server;
  6432. }
  6433. }
  6434.  
  6435. if (targetServer) {
  6436. // Join the server with the lowest player count
  6437. showLoadingOverlay();
  6438. Roblox.GameLauncher.joinGameInstance(gameId, targetServer.id);
  6439. notifications(`Joining a server with ${targetServer.playing} player(s).`, 'success', '🚀');
  6440. success = true; // Mark as successful
  6441. } else {
  6442. notifications('No available servers found.', 'error', '⚠️');
  6443. break; // Exit the loop if no servers are found
  6444. }
  6445. } catch (error) {
  6446. if (error === '429: Too Many Requests' && retries > 0) {
  6447. ConsoleLogEnabled('Rate limited. Retrying in 10 seconds...');
  6448. notifications('Rate limited. Retrying in 10 seconds...', 'warning', '⏳', '10000');
  6449. await delay(10000); // Wait 10 seconds before retrying
  6450. retries--;
  6451. } else {
  6452. ConsoleLogEnabled('Error fetching server data:', error);
  6453. notifications('Error: Failed to fetch server data. Please try again later.', 'error', '⚠️', '5000');
  6454. Loadingbar(false);
  6455. break; // Exit the loop if it's not a 429 error or no retries left
  6456. }
  6457. }
  6458. }
  6459.  
  6460. // Hide the loading bar and enable the filter button
  6461. Loadingbar(false);
  6462. disableFilterButton(false);
  6463. }
  6464. /*********************************************************************************************************************************************************************************************************************************************
  6465. Functions for the 8th button.
  6466.  
  6467. *********************************************************************************************************************************************************************************************************************************************/
  6468.  
  6469. // Enhanced popup styling for the username input dialog
  6470. function find_user_server_tab() {
  6471. // Create the overlay (backdrop) with improved animation
  6472. const overlay = document.createElement('div');
  6473. overlay.style.cssText = `
  6474. position: fixed;
  6475. top: 0;
  6476. left: 0;
  6477. width: 100%;
  6478. height: 100%;
  6479. background-color: rgba(0, 0, 0, 0.6);
  6480. z-index: 9999;
  6481. opacity: 0;
  6482. transition: opacity 0.4s ease;
  6483. backdrop-filter: blur(3px);
  6484. `;
  6485. document.body.appendChild(overlay);
  6486.  
  6487. // Create the popup container with improved styling
  6488. const popup = document.createElement('div');
  6489. popup.className = 'player-count-popup';
  6490. popup.style.cssText = `
  6491. position: fixed;
  6492. top: 50%;
  6493. left: 50%;
  6494. transform: translate(-50%, -50%) scale(0.95);
  6495. background-color: rgb(30, 32, 34);
  6496. padding: 30px 25px;
  6497. border-radius: 12px;
  6498. z-index: 10000;
  6499. box-shadow: 0 10px 25px rgba(0, 0, 0, 0.8);
  6500. display: flex;
  6501. flex-direction: column;
  6502. align-items: center;
  6503. gap: 20px;
  6504. width: 340px;
  6505. opacity: 0;
  6506. transition: opacity 0.4s ease, transform 0.4s ease;
  6507. border: 1px solid rgba(255, 255, 255, 0.1);
  6508. `;
  6509.  
  6510. // Improved close button with better hover effects
  6511. const closeButton = document.createElement('button');
  6512. closeButton.innerHTML = '&times;';
  6513. closeButton.style.cssText = `
  6514. position: absolute;
  6515. top: 12px;
  6516. right: 12px;
  6517. background: transparent;
  6518. border: none;
  6519. color: #999999;
  6520. font-size: 26px;
  6521. cursor: pointer;
  6522. width: 36px;
  6523. height: 36px;
  6524. border-radius: 50%;
  6525. display: flex;
  6526. align-items: center;
  6527. justify-content: center;
  6528. transition: all 0.2s ease;
  6529. `;
  6530. closeButton.addEventListener('mouseenter', () => {
  6531. closeButton.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
  6532. closeButton.style.color = '#ff4444';
  6533. closeButton.style.transform = 'rotate(90deg)';
  6534. });
  6535. closeButton.addEventListener('mouseleave', () => {
  6536. closeButton.style.backgroundColor = 'transparent';
  6537. closeButton.style.color = '#999999';
  6538. closeButton.style.transform = 'rotate(0deg)';
  6539. });
  6540.  
  6541. // Enhanced title with icon
  6542. const titleContainer = document.createElement('div');
  6543. titleContainer.style.cssText = `
  6544. display: flex;
  6545. align-items: center;
  6546. gap: 10px;
  6547. margin-bottom: 5px;
  6548. `;
  6549.  
  6550. const titleIcon = document.createElement('span');
  6551. titleIcon.innerHTML = '🔍';
  6552. titleIcon.style.cssText = `font-size: 22px;`;
  6553.  
  6554. const title = document.createElement('h3');
  6555. title.textContent = 'Locate User';
  6556. title.style.cssText = `
  6557. color: white;
  6558. margin: 0;
  6559. font-size: 20px;
  6560. font-weight: 600;
  6561. `;
  6562.  
  6563. titleContainer.appendChild(titleIcon);
  6564. titleContainer.appendChild(title);
  6565. popup.appendChild(titleContainer);
  6566.  
  6567. // Add subtitle with instructions
  6568. const subtitle = document.createElement('p');
  6569. subtitle.textContent = 'Enter the exact Roblox username to find their server';
  6570. subtitle.style.cssText = `
  6571. color: #aaaaaa;
  6572. margin: -10px 0 0 0;
  6573. font-size: 14px;
  6574. text-align: center;
  6575. `;
  6576. popup.appendChild(subtitle);
  6577.  
  6578. // Improved input box with focus styling
  6579. const usernameInput = document.createElement('input');
  6580. usernameInput.type = 'text';
  6581. usernameInput.placeholder = 'Username';
  6582. usernameInput.style.cssText = `
  6583. width: 100%;
  6584. padding: 12px 15px;
  6585. font-size: 16px;
  6586. border: 2px solid #444;
  6587. border-radius: 8px;
  6588. background-color: #252729;
  6589. color: white;
  6590. outline: none;
  6591. transition: all 0.3s ease;
  6592. box-sizing: border-box;
  6593. `;
  6594. usernameInput.addEventListener('focus', () => {
  6595. usernameInput.style.borderColor = '#b71c1c';
  6596. usernameInput.style.boxShadow = '0 0 5px rgba(211, 47, 47, 0.5)';
  6597. });
  6598. usernameInput.addEventListener('blur', () => {
  6599. usernameInput.style.borderColor = '#444';
  6600. usernameInput.style.boxShadow = 'none';
  6601. });
  6602. popup.appendChild(usernameInput);
  6603.  
  6604. // Enhanced confirm button with better styling and effects
  6605. const confirmButton = document.createElement('button');
  6606. confirmButton.textContent = 'Search';
  6607. confirmButton.style.cssText = `
  6608. padding: 12px 0;
  6609. width: 100%;
  6610. font-size: 16px;
  6611. font-weight: 600;
  6612. background-color: #b71c1c;
  6613. color: white;
  6614. border: none;
  6615. border-radius: 8px;
  6616. cursor: pointer;
  6617. transition: all 0.3s ease;
  6618. display: flex;
  6619. align-items: center;
  6620. justify-content: center;
  6621. gap: 8px;
  6622. `;
  6623.  
  6624. // Add icon to button
  6625. const searchIcon = document.createElement('span');
  6626. searchIcon.textContent = '🔍';
  6627. searchIcon.style.fontSize = '18px';
  6628. confirmButton.prepend(searchIcon);
  6629.  
  6630. confirmButton.addEventListener('mouseenter', () => {
  6631. confirmButton.style.backgroundColor = '#d32f2f';
  6632. confirmButton.style.transform = 'translateY(-2px)';
  6633. confirmButton.style.boxShadow = '0 5px 15px rgba(211, 47, 47, 0.5)';
  6634. });
  6635. confirmButton.addEventListener('mouseleave', () => {
  6636. confirmButton.style.backgroundColor = '#b71c1c';
  6637. confirmButton.style.transform = 'translateY(0)';
  6638. confirmButton.style.boxShadow = 'none';
  6639. });
  6640. confirmButton.addEventListener('mousedown', () => {
  6641. confirmButton.style.transform = 'translateY(1px)';
  6642. });
  6643. confirmButton.addEventListener('mouseup', () => {
  6644. confirmButton.style.transform = 'translateY(-2px)';
  6645. });
  6646.  
  6647. // Handle keyboard enter for better UX
  6648. usernameInput.addEventListener('keydown', (e) => {
  6649. if (e.key === 'Enter') {
  6650. confirmButton.click();
  6651. }
  6652. });
  6653.  
  6654. // Handle confirm button click with enhanced validation feedback
  6655. confirmButton.addEventListener('click', () => {
  6656. const username = usernameInput.value.trim();
  6657. if (username) {
  6658. if (username.length >= 3 && username.length <= 20) {
  6659. FindPlayerGameServer(username);
  6660. notifications("Searching for the user's server...", "info", "🔍", "5000");
  6661. fadeOutAndRemove_7th(popup, overlay);
  6662. } else {
  6663. usernameInput.style.borderColor = '#ff4444';
  6664. usernameInput.style.boxShadow = '0 0 5px rgba(255, 68, 68, 0.5)';
  6665. notifications('Username must be between 3 and 20 characters', 'error', '⚠️', '3000');
  6666.  
  6667. // Reset input styling after delay
  6668. setTimeout(() => {
  6669. if (document.body.contains(usernameInput)) {
  6670. usernameInput.style.borderColor = '#444';
  6671. usernameInput.style.boxShadow = 'none';
  6672. }
  6673. }, 2000);
  6674. }
  6675. } else {
  6676. usernameInput.style.borderColor = '#ff4444';
  6677. usernameInput.style.boxShadow = '0 0 5px rgba(255, 68, 68, 0.5)';
  6678. notifications('Please enter a username', 'error', '⚠️', '2500');
  6679. }
  6680. });
  6681.  
  6682. // Append elements to popup
  6683. popup.appendChild(confirmButton);
  6684. popup.appendChild(closeButton);
  6685.  
  6686. // Append popup to body
  6687. document.body.appendChild(popup);
  6688.  
  6689. // Focus the input field automatically
  6690. setTimeout(() => {
  6691. usernameInput.focus();
  6692. }, 300);
  6693.  
  6694. // Fade in the overlay and popup with improved animation
  6695. setTimeout(() => {
  6696. overlay.style.opacity = '1';
  6697. popup.style.opacity = '1';
  6698. popup.style.transform = 'translate(-50%, -50%) scale(1)';
  6699. }, 10);
  6700.  
  6701. // Keep the same fadeOutAndRemove function name for compatibility
  6702. function fadeOutAndRemove_7th(popup, overlay) {
  6703. popup.style.opacity = '0';
  6704. popup.style.transform = 'translate(-50%, -50%) scale(0.9)';
  6705. overlay.style.opacity = '0';
  6706. setTimeout(() => {
  6707. popup.remove();
  6708. overlay.remove();
  6709. }, 300);
  6710. }
  6711.  
  6712. // Close the popup when clicking outside
  6713. overlay.addEventListener('click', () => {
  6714. fadeOutAndRemove_7th(popup, overlay);
  6715. });
  6716.  
  6717. // Close the popup when the close button is clicked
  6718. closeButton.addEventListener('click', () => {
  6719. fadeOutAndRemove_7th(popup, overlay);
  6720. });
  6721. }
  6722.  
  6723.  
  6724. // Add this helper function somewhere inside or above your main function
  6725. const checkIfUserOnline = async (userId) => {
  6726. try {
  6727. const response = await fetch("https://presence.roblox.com/v1/presence/users", {
  6728. method: "POST",
  6729. headers: {
  6730. "Content-Type": "application/json"
  6731. },
  6732. body: JSON.stringify({
  6733. userIds: [parseInt(userId)]
  6734. })
  6735. });
  6736. const data = await response.json();
  6737. const presenceType = data.userPresences?.[0]?.userPresenceType ?? 0;
  6738. return presenceType !== 0; // true if online or in-game/studio
  6739. } catch (error) {
  6740. ConsoleLogEnabled("Presence check failed:", error);
  6741. return false; // fail-safe: treat as offline
  6742. }
  6743. };
  6744.  
  6745.  
  6746. async function FindPlayerGameServer(playerName) {
  6747. disableLoadMoreButton();
  6748. Loadingbar(true);
  6749. disableFilterButton(true);
  6750. const wait = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds));
  6751.  
  6752. const fetchData = async (url, options = {}) => {
  6753. if (!options.headers) options.headers = {};
  6754. return fetch(url, options)
  6755. .then(response => response.json())
  6756. .catch(error => ConsoleLogEnabled("Fetch error:", error));
  6757. };
  6758.  
  6759. const fetchDataGM = (url, options = {}) => {
  6760. return new Promise((resolve, reject) => {
  6761. GM_xmlhttpRequest({
  6762. method: options.method || 'GET',
  6763. url: url,
  6764. headers: options.headers || {},
  6765. anonymous: true, // Prevents sending cookies
  6766. nocache: true, // Prevents caching
  6767. onload: function(response) {
  6768. try {
  6769. const parsedData = JSON.parse(response.responseText);
  6770. resolve(parsedData);
  6771. } catch (error) {
  6772. ConsoleLogEnabled("JSON parsing error:", error);
  6773. reject(error);
  6774. }
  6775. },
  6776. onerror: function(error) {
  6777. ConsoleLogEnabled("Request error:", error);
  6778. reject(error);
  6779. }
  6780. });
  6781. });
  6782. };
  6783.  
  6784. ConsoleLogEnabled(`Initiating search for player: ${playerName}`);
  6785.  
  6786. const gameId = window.location.href.split("/")[4];
  6787. ConsoleLogEnabled(`Game ID identified: ${gameId}`);
  6788.  
  6789. let userId;
  6790.  
  6791. try {
  6792. ConsoleLogEnabled(`Retrieving user ID for player: ${playerName}`);
  6793. const userProfile = await fetch(`https://www.roblox.com/users/profile?username=${playerName}`);
  6794. if (!userProfile.ok) {
  6795. notifications("Error: User does not exist on Roblox!", "error", "⚠️", "2500");
  6796. Loadingbar(false);
  6797. disableFilterButton(false);
  6798. throw `Player "${playerName}" not found`;
  6799. }
  6800. userId = userProfile.url.match(/\d+/)[0];
  6801. const isOnline = await checkIfUserOnline(userId);
  6802. if (!isOnline) {
  6803. notifications("User is currently offline.", "error", "📴", "4000");
  6804. Loadingbar(false);
  6805. disableFilterButton(false);
  6806. fadeOutAndRemove_7th_progress(progressPopup.popup, progressPopup.overlay);
  6807. return `User "${playerName}" is offline.`;
  6808. }
  6809. ConsoleLogEnabled(`User is online proceeding with server scan.`);
  6810.  
  6811. ConsoleLogEnabled(`User ID retrieved: ${userId}`);
  6812. } catch (error) {
  6813. ConsoleLogEnabled("Error:", error);
  6814. return `Error: ${error}`;
  6815. }
  6816.  
  6817. ConsoleLogEnabled(`Fetching avatar thumbnail for user ID: ${userId}`);
  6818. const avatarThumbnail = (await fetchData(`https://thumbnails.roblox.com/v1/users/avatar-headshot?userIds=${userId}&format=Png&size=150x150`)).data[0].imageUrl;
  6819. ConsoleLogEnabled(`Avatar thumbnail URL: ${avatarThumbnail}`);
  6820.  
  6821. let pageCursor = null;
  6822. let playerFound = false;
  6823. let totalServersChecked = 0;
  6824. let startTime = Date.now();
  6825.  
  6826. // Show the search progress popup with the player's thumbnail
  6827. const progressPopup = showSearchProgressPopup(avatarThumbnail);
  6828.  
  6829. while (true) {
  6830. let apiUrl = `https://games.roblox.com/v1/games/${gameId}/servers/0?limit=100`;
  6831. if (pageCursor) apiUrl += "&cursor=" + pageCursor;
  6832.  
  6833. ConsoleLogEnabled(`Accessing servers with URL: ${apiUrl}`);
  6834. const serverList = await fetchDataGM(apiUrl, {
  6835. credentials: "omit"
  6836. }).catch(() => null);
  6837.  
  6838. if (serverList && serverList.data) {
  6839. ConsoleLogEnabled(`Discovered ${serverList.data.length} servers in this set.`);
  6840. for (let index = 0; index < serverList.data.length; index++) {
  6841. const server = serverList.data[index];
  6842. if (!playerFound) {
  6843. totalServersChecked++;
  6844.  
  6845. // Calculate time elapsed
  6846. const timeElapsed = Math.floor((Date.now() - startTime) / 1000);
  6847.  
  6848. // Update the progress popup
  6849. updateSearchProgressPopup(
  6850. progressPopup,
  6851. totalServersChecked,
  6852. timeElapsed
  6853. );
  6854.  
  6855. if (server.playerTokens.length === 0) {
  6856. ConsoleLogEnabled(`Server ${index + 1} is empty. Proceeding to next server.`);
  6857. continue;
  6858. }
  6859.  
  6860. ConsoleLogEnabled(`Inspecting server ${index + 1} hosting ${server.playing} players...`);
  6861.  
  6862. let thumbnailData;
  6863. while (true) {
  6864. let requestBody = [];
  6865.  
  6866. const generateRequestEntry = (playerToken) => ({
  6867. requestId: `0:${playerToken}:AvatarHeadshot:150x150:png:regular`,
  6868. type: "AvatarHeadShot",
  6869. targetId: 0,
  6870. token: playerToken,
  6871. format: "png",
  6872. size: "150x150"
  6873. });
  6874.  
  6875. server.playerTokens.forEach(token => {
  6876. requestBody.push(generateRequestEntry(token));
  6877. });
  6878.  
  6879. try {
  6880. ConsoleLogEnabled(`Fetching thumbnails for ${server.playerTokens.length} player(s)...`);
  6881. thumbnailData = await fetchData("https://thumbnails.roblox.com/v1/batch", {
  6882. method: "POST",
  6883. headers: {
  6884. "Content-Type": "application/json",
  6885. Accept: "application/json"
  6886. },
  6887. body: JSON.stringify(requestBody)
  6888. });
  6889. ConsoleLogEnabled("Thumbnail Data Response:", thumbnailData);
  6890. break;
  6891. } catch (error) {
  6892. ConsoleLogEnabled("Thumbnail retrieval error:", error);
  6893. await wait(1000);
  6894. }
  6895. }
  6896.  
  6897. if (!thumbnailData.data) {
  6898. ConsoleLogEnabled("No thumbnail data available. Moving to next server.");
  6899. continue;
  6900. }
  6901.  
  6902. for (let thumbIndex = 0; thumbIndex < thumbnailData.data.length; thumbIndex++) {
  6903. const thumbnail = thumbnailData.data[thumbIndex];
  6904. if (thumbnail && thumbnail.imageUrl === avatarThumbnail) {
  6905. playerFound = true;
  6906. ConsoleLogEnabled(`Player located in server ${index + 1}!`);
  6907. notifications("Found User's Server! Joining Server...", "success", "🚀", "5000");
  6908. showLoadingOverlay();
  6909. Roblox.GameLauncher.joinGameInstance(gameId, server.id);
  6910. fadeOutAndRemove_7th_progress(progressPopup.popup, progressPopup.overlay);
  6911. Loadingbar(false);
  6912. disableFilterButton(false);
  6913. return {
  6914. gameId,
  6915. serverId: server.id,
  6916. currentPlayers: server.playing,
  6917. maximumPlayers: server.maxPlayers,
  6918. };
  6919. }
  6920. }
  6921. } else {
  6922. break;
  6923. }
  6924. }
  6925.  
  6926. pageCursor = serverList.nextPageCursor;
  6927. if (!pageCursor || playerFound) break;
  6928. else {
  6929. ConsoleLogEnabled("Pausing for 2.5 seconds before next server batch...");
  6930. await wait(2500);
  6931. }
  6932. } else {
  6933. ConsoleLogEnabled("Server fetch failed. Retrying in 10 seconds...");
  6934. notifications("Got rate limited. Waiting 10 seconds...", "info", "❗", "10000")
  6935. await wait(10000);
  6936. }
  6937. }
  6938.  
  6939. if (!playerFound) {
  6940. // Wait for 2 seconds before calling another function
  6941. setTimeout(() => {
  6942. fadeOutAndRemove_7th_progress(progressPopup.popup, progressPopup.overlay);
  6943. notifications("User not found playing this game!", "error", "⚠️", "5000");
  6944. notifications("If the user is playing, try again in a minute. The Roblox API may not be updated yet.", "info", "🔄", "20000");
  6945. }, 2000); // 2000 milliseconds = 2 seconds
  6946.  
  6947. // Existing logic (unchanged)
  6948. Loadingbar(false);
  6949. disableFilterButton(false);
  6950. ConsoleLogEnabled(`Player not found in the searched servers (${totalServersChecked} servers checked)`);
  6951. // Update the progress popup with the final count
  6952. updateSearchProgressPopup(
  6953. progressPopup,
  6954. totalServersChecked,
  6955. Math.floor((Date.now() - startTime) / 1000),
  6956. true
  6957. );
  6958. return `Player not found in the searched servers (${totalServersChecked} servers checked)`;
  6959. }
  6960. }
  6961.  
  6962. // Enhanced progress popup for search visualization
  6963. function showSearchProgressPopup(avatarThumbnail) {
  6964. // Create the overlay with blur effect
  6965. const overlay = document.createElement('div');
  6966. overlay.style.cssText = `
  6967. position: fixed;
  6968. top: 0;
  6969. left: 0;
  6970. width: 100%;
  6971. height: 100%;
  6972. background-color: rgba(0, 0, 0, 0.6);
  6973. z-index: 9999;
  6974. opacity: 0;
  6975. transition: opacity 0.4s ease;
  6976. backdrop-filter: blur(3px);
  6977. `;
  6978. document.body.appendChild(overlay);
  6979.  
  6980. // Create the popup container with improved styling
  6981. const popup = document.createElement('div');
  6982. popup.className = 'search-progress-popup';
  6983. popup.style.cssText = `
  6984. position: fixed;
  6985. top: 50%;
  6986. left: 50%;
  6987. transform: translate(-50%, -50%) scale(0.95);
  6988. background-color: rgb(30, 32, 34);
  6989. padding: 30px;
  6990. border-radius: 12px;
  6991. z-index: 10000;
  6992. box-shadow: 0 10px 25px rgba(0, 0, 0, 0.8);
  6993. display: flex;
  6994. flex-direction: column;
  6995. align-items: center;
  6996. gap: 20px;
  6997. width: 340px;
  6998. opacity: 0;
  6999. transition: opacity 0.4s ease, transform 0.4s ease;
  7000. border: 1px solid rgba(255, 255, 255, 0.1);
  7001. `;
  7002.  
  7003. // Improved title with visual hierarchy
  7004. const title = document.createElement('h3');
  7005. title.textContent = 'Searching for Player...';
  7006. title.style.cssText = `
  7007. color: white;
  7008. margin: 0 0 10px 0;
  7009. font-size: 20px;
  7010. font-weight: 600;
  7011. text-align: center;
  7012. `;
  7013. popup.appendChild(title);
  7014.  
  7015. // Add animated loader element
  7016. const loaderContainer = document.createElement('div');
  7017. loaderContainer.style.cssText = `
  7018. position: relative;
  7019. width: 120px;
  7020. height: 120px;
  7021. display: flex;
  7022. justify-content: center;
  7023. align-items: center;
  7024. `;
  7025.  
  7026. // Create ripple effect around avatar
  7027. const ripple = document.createElement('div');
  7028. ripple.style.cssText = `
  7029. position: absolute;
  7030. width: 110px;
  7031. height: 110px;
  7032. border-radius: 50%;
  7033. border: 3px solid #b71c1c;
  7034. animation: ripple 1.5s ease-out infinite;
  7035. opacity: 0;
  7036. `;
  7037.  
  7038. // Add the keyframe animation
  7039. const style = document.createElement('style');
  7040. style.textContent = `
  7041. @keyframes ripple {
  7042. 0% {
  7043. transform: scale(0.8);
  7044. opacity: 0.8;
  7045. }
  7046. 100% {
  7047. transform: scale(1.2);
  7048. opacity: 0;
  7049. }
  7050. }
  7051. @keyframes pulse {
  7052. 0% { box-shadow: 0 0 0 0 rgba(255, 68, 68, 0.5); }
  7053. 70% { box-shadow: 0 0 0 10px rgba(58, 122, 255, 0); }
  7054. 100% { box-shadow: 0 0 0 0 rgba(58, 122, 255, 0); }
  7055. }
  7056. `;
  7057. document.head.appendChild(style);
  7058.  
  7059. // Enhanced player thumbnail display
  7060. const thumbnail = document.createElement('img');
  7061. thumbnail.src = avatarThumbnail;
  7062. thumbnail.style.cssText = `
  7063. width: 100px;
  7064. height: 100px;
  7065. border-radius: 50%;
  7066. object-fit: cover;
  7067. border: 4px solid #b71c1c;
  7068. box-shadow: 0 0 20px rgba(255, 68, 68, 0.5);
  7069. animation: pulse 2s infinite;
  7070. z-index: 2;
  7071. `;
  7072.  
  7073. loaderContainer.appendChild(ripple);
  7074. loaderContainer.appendChild(thumbnail);
  7075. popup.appendChild(loaderContainer);
  7076.  
  7077. // Status container
  7078. const statusContainer = document.createElement('div');
  7079. statusContainer.style.cssText = `
  7080. background-color: rgba(0, 0, 0, 0.2);
  7081. border-radius: 8px;
  7082. padding: 15px;
  7083. width: 100%;
  7084. box-sizing: border-box;
  7085. `;
  7086.  
  7087. // Improved progress indicators with icons
  7088. const serversSearchedContainer = document.createElement('div');
  7089. serversSearchedContainer.style.cssText = `
  7090. display: flex;
  7091. align-items: center;
  7092. gap: 10px;
  7093. margin-bottom: 10px;
  7094. `;
  7095.  
  7096. const serverIcon = document.createElement('span');
  7097. serverIcon.textContent = '🔍';
  7098. serverIcon.style.fontSize = '18px';
  7099.  
  7100. const serversSearchedText = document.createElement('p');
  7101. serversSearchedText.textContent = 'Servers searched: 0';
  7102. serversSearchedText.style.cssText = `
  7103. color: white;
  7104. margin: 0;
  7105. font-size: 16px;
  7106. flex-grow: 1;
  7107. `;
  7108.  
  7109. serversSearchedContainer.appendChild(serverIcon);
  7110. serversSearchedContainer.appendChild(serversSearchedText);
  7111.  
  7112. const timeElapsedContainer = document.createElement('div');
  7113. timeElapsedContainer.style.cssText = `
  7114. display: flex;
  7115. align-items: center;
  7116. gap: 10px;
  7117. `;
  7118.  
  7119. const timeIcon = document.createElement('span');
  7120. timeIcon.textContent = '⏱️';
  7121. timeIcon.style.fontSize = '18px';
  7122.  
  7123. const timeElapsedText = document.createElement('p');
  7124. timeElapsedText.textContent = 'Time elapsed: 0s';
  7125. timeElapsedText.style.cssText = `
  7126. color: white;
  7127. margin: 0;
  7128. font-size: 16px;
  7129. flex-grow: 1;
  7130. `;
  7131.  
  7132. timeElapsedContainer.appendChild(timeIcon);
  7133. timeElapsedContainer.appendChild(timeElapsedText);
  7134.  
  7135. statusContainer.appendChild(serversSearchedContainer);
  7136. statusContainer.appendChild(timeElapsedContainer);
  7137. popup.appendChild(statusContainer);
  7138.  
  7139. // Add a status message that can be updated
  7140. const statusMessage = document.createElement('p');
  7141. statusMessage.textContent = 'Scanning servers...';
  7142. statusMessage.style.cssText = `
  7143. color: #aaaaaa;
  7144. margin: -10px 0 0 0;
  7145. font-size: 14px;
  7146. text-align: center;
  7147. font-style: italic;
  7148. `;
  7149. popup.appendChild(statusMessage);
  7150.  
  7151. // Append the popup to the body
  7152. document.body.appendChild(popup);
  7153.  
  7154. // Fade in the overlay and popup
  7155. setTimeout(() => {
  7156. overlay.style.opacity = '1';
  7157. popup.style.opacity = '1';
  7158. popup.style.transform = 'translate(-50%, -50%) scale(1)';
  7159. }, 10);
  7160.  
  7161. return {
  7162. popup,
  7163. overlay,
  7164. serversSearchedText,
  7165. timeElapsedText,
  7166. statusMessage,
  7167. ripple
  7168. };
  7169. }
  7170.  
  7171. // Enhanced update function for search progress popup
  7172. function updateSearchProgressPopup(
  7173. progressPopup,
  7174. totalServersChecked,
  7175. timeElapsed,
  7176. isFinal = false
  7177. ) {
  7178. // Update numbers with animation effect
  7179. const currentServers = parseInt(progressPopup.serversSearchedText.textContent.match(/\d+/)[0]);
  7180. const currentTime = parseInt(progressPopup.timeElapsedText.textContent.match(/\d+/)[0]);
  7181.  
  7182. // Animate the number changes
  7183. progressPopup.serversSearchedText.textContent = `Servers searched: ${totalServersChecked}`;
  7184. progressPopup.timeElapsedText.textContent = `Time elapsed: ${timeElapsed}s`;
  7185.  
  7186. // Update status message based on progress
  7187. if (totalServersChecked % 5 === 0) {
  7188. const messages = [
  7189. "Scanning servers...",
  7190. "Checking player lists...",
  7191. "Searching for matches...",
  7192. "Processing server data...",
  7193. "Analyzing player tokens..."
  7194. ];
  7195. progressPopup.statusMessage.textContent = messages[Math.floor(Math.random() * messages.length)];
  7196. }
  7197.  
  7198. if (isFinal) {
  7199. progressPopup.statusMessage.textContent = "Search completed";
  7200. progressPopup.statusMessage.style.color = "#ffaa00";
  7201. progressPopup.statusMessage.style.fontWeight = "bold";
  7202. progressPopup.ripple.style.animationPlayState = "paused";
  7203. progressPopup.serversSearchedText.textContent = `Servers searched: ${totalServersChecked} (complete)`;
  7204. }
  7205. }
  7206.  
  7207. // Enhanced removal function for progress popup
  7208. function fadeOutAndRemove_7th_progress(popup, overlay) {
  7209. popup.style.opacity = '0';
  7210. popup.style.transform = 'translate(-50%, -50%) scale(0.9)';
  7211. overlay.style.opacity = '0';
  7212. setTimeout(() => {
  7213. popup.remove();
  7214. overlay.remove();
  7215. }, 400);
  7216. }
  7217.  
  7218.  
  7219. /*********************************************************************************************************************************************************************************************************************************************
  7220. End of: This is all the functions for the 8 buttons
  7221.  
  7222. *********************************************************************************************************************************************************************************************************************************************/
  7223.  
  7224.  
  7225.  
  7226. /*********************************************************************************************************************************************************************************************************************************************
  7227. The Universal Functions
  7228.  
  7229. *********************************************************************************************************************************************************************************************************************************************/
  7230.  
  7231.  
  7232.  
  7233.  
  7234. /*******************************************************
  7235. name of function: disableLoadMoreButton
  7236. description: Disables the "Load More" button
  7237. *******************************************************/
  7238. function disableLoadMoreButton() {
  7239. const loadMoreButton = document.querySelector('.rbx-running-games-load-more');
  7240. if (loadMoreButton) {
  7241. loadMoreButton.disabled = true;
  7242. loadMoreButton.style.opacity = '0.5'; // Optional: Make the button look disabled
  7243. loadMoreButton.style.cursor = 'not-allowed'; // Optional: Change cursor to indicate disabled state
  7244. loadMoreButton.title = 'Disabled by Rolocate'; // Set tooltip text
  7245. } else {
  7246. ConsoleLogEnabled('Load More button not found!');
  7247. ConsoleLogEnabled('Maybe moved by another extension!');
  7248. }
  7249. }
  7250.  
  7251.  
  7252. /*******************************************************
  7253. name of function: Loadingbar
  7254. description: Shows or hides a loading bar (now using pulsing boxes)
  7255. *******************************************************/
  7256. function Loadingbar(disable) {
  7257. const serverListSection = document.querySelector('#rbx-public-running-games');
  7258. const serverCardsContainer = document.querySelector('#rbx-public-game-server-item-container');
  7259. const emptyGameInstancesContainer = document.querySelector('.section-content-off.empty-game-instances-container');
  7260. const noServersMessage = emptyGameInstancesContainer?.querySelector('.no-servers-message');
  7261.  
  7262. // Check if the "Unable to load servers." message is visible
  7263. if (!serverCardsContainer && noServersMessage?.textContent.includes('Unable to load servers.')) {
  7264. notifications('Unable to load servers. Please refresh the page.', 'error', '⚠️', '8000');
  7265. return;
  7266. }
  7267.  
  7268. // Reset everything if disable is true
  7269. if (disable) {
  7270. if (serverCardsContainer) {
  7271. serverCardsContainer.innerHTML = ''; // Clear contents
  7272. serverCardsContainer.removeAttribute('style'); // Remove inline styles if present
  7273. }
  7274.  
  7275. // Prevent duplicate loading bars
  7276. const existingLoadingBar = document.querySelector('#loading-bar');
  7277. if (existingLoadingBar) {
  7278. existingLoadingBar.remove(); // Remove the existing loading bar if it exists
  7279. }
  7280.  
  7281. // Create and display the loading boxes
  7282. const loadingContainer = document.createElement('div');
  7283. loadingContainer.id = 'loading-bar';
  7284. loadingContainer.style.cssText = `
  7285. display: flex;
  7286. justify-content: center;
  7287. align-items: center;
  7288. gap: 5px;
  7289. margin-top: 10px;
  7290. `;
  7291.  
  7292. const fragment = document.createDocumentFragment();
  7293. for (let i = 0; i < 3; i++) {
  7294. const box = document.createElement('div');
  7295. box.style.cssText = `
  7296. width: 10px;
  7297. height: 10px;
  7298. background-color: white;
  7299. margin: 0 5px;
  7300. border-radius: 2px;
  7301. animation: pulse 1.2s ${i * 0.2}s infinite;
  7302. `;
  7303. fragment.appendChild(box);
  7304. }
  7305. loadingContainer.appendChild(fragment);
  7306.  
  7307. if (serverListSection) {
  7308. serverListSection.appendChild(loadingContainer);
  7309. }
  7310.  
  7311. // Inject CSS only once
  7312. const existingStyle = document.querySelector('#loading-style');
  7313. if (!existingStyle) {
  7314. const styleSheet = document.createElement('style');
  7315. styleSheet.id = 'loading-style';
  7316. styleSheet.textContent = `
  7317. @keyframes pulse {
  7318. 0%, 100% { transform: scale(1); }
  7319. 50% { transform: scale(1.5); }
  7320. }
  7321. `;
  7322. document.head.appendChild(styleSheet);
  7323. }
  7324.  
  7325. // Remove gradient div if it exists
  7326. const gradientDiv = document.querySelector('div[style*="background: linear-gradient(145deg"]');
  7327. if (gradientDiv) {
  7328. gradientDiv.remove();
  7329. }
  7330.  
  7331. // ik this approach sucks but its the best i can do. it remove ths premium messages with this specific
  7332. // text so it doesnet remove the other stuff, you prob cant even understand what im sayin right now
  7333. const premiumMessageDiv = document.querySelector('.premium-message-text');
  7334. if (premiumMessageDiv) {
  7335. const messageText = premiumMessageDiv.textContent.trim();
  7336. const errorMessages = [
  7337. "Error: Cannot access server regions because you have not purchased the game.",
  7338. "Error: This game requires users to teleport to a subplace. As a result, server regions cannot be retrieved."
  7339. ];
  7340.  
  7341. if (errorMessages.includes(messageText)) {
  7342. showMessage("END");
  7343. }
  7344. }
  7345.  
  7346.  
  7347.  
  7348.  
  7349. } else {
  7350. // If disable is false, remove the loading bar
  7351. const loadingBar = document.querySelector('#loading-bar');
  7352. if (loadingBar) {
  7353. loadingBar.remove();
  7354. }
  7355.  
  7356. // Reset any applied styles
  7357. const styleSheet = document.querySelector('#loading-style');
  7358. if (styleSheet) {
  7359. styleSheet.remove();
  7360. }
  7361. }
  7362. }
  7363.  
  7364.  
  7365.  
  7366.  
  7367. /*******************************************************
  7368. name of function: fetchPlayerThumbnails
  7369. description: Fetches player thumbnails for up to 5 players. Skips the batch if an error occurs.
  7370. *******************************************************/
  7371. async function fetchPlayerThumbnails(playerTokens) {
  7372. // Limit to the first 5 player tokens
  7373. const limitedTokens = playerTokens.slice(0, 5);
  7374.  
  7375. const body = limitedTokens.map(token => ({
  7376. requestId: `0:${token}:AvatarHeadshot:150x150:png:regular`,
  7377. type: "AvatarHeadShot",
  7378. targetId: 0,
  7379. token,
  7380. format: "png",
  7381. size: "150x150",
  7382. }));
  7383.  
  7384. try {
  7385. const response = await fetch("https://thumbnails.roblox.com/v1/batch", {
  7386. method: "POST",
  7387. headers: {
  7388. "Content-Type": "application/json",
  7389. Accept: "application/json",
  7390. },
  7391. body: JSON.stringify(body),
  7392. });
  7393.  
  7394. // Check if the response is successful
  7395. if (!response.ok) {
  7396. throw new Error(`HTTP error! Status: ${response.status}`);
  7397. }
  7398.  
  7399. const data = await response.json();
  7400. return data.data || []; // Return the data or an empty array if no data is present
  7401. } catch (error) {
  7402. ConsoleLogEnabled('Error fetching player thumbnails:', error);
  7403. return []; // Return an empty array if an error occurs
  7404. }
  7405. }
  7406.  
  7407.  
  7408. /*******************************************************
  7409. name of function: disableFilterButton
  7410. description: Disables or enables the filter button based on the input.
  7411. *******************************************************/
  7412. function disableFilterButton(disable) {
  7413. const filterButton = document.querySelector('.RL-filter-button');
  7414. const refreshButtons = document.querySelectorAll('.btn-more.rbx-refresh.refresh-link-icon.btn-control-xs.btn-min-width');
  7415. const filterOverlayId = 'filter-button-overlay';
  7416. const refreshOverlayClass = 'refresh-button-overlay';
  7417.  
  7418. if (filterButton) {
  7419. const parent = filterButton.parentElement;
  7420.  
  7421. if (disable) {
  7422. // Disable the filter button with an overlay
  7423. filterButton.disabled = true;
  7424. filterButton.style.opacity = '0.5';
  7425. filterButton.style.cursor = 'not-allowed';
  7426.  
  7427. // Create an overlay if it doesn't exist
  7428. let overlay = document.getElementById(filterOverlayId);
  7429. if (!overlay) {
  7430. overlay = document.createElement('div');
  7431. overlay.id = filterOverlayId;
  7432. overlay.style.position = 'absolute';
  7433. overlay.style.top = '-10px';
  7434. overlay.style.left = '-10px';
  7435. overlay.style.width = 'calc(100% + 20px)';
  7436. overlay.style.height = 'calc(100% + 20px)';
  7437. overlay.style.backgroundColor = 'transparent';
  7438. overlay.style.zIndex = '9999';
  7439. overlay.style.pointerEvents = 'all'; // Block clicks
  7440. parent.style.position = 'relative';
  7441. parent.appendChild(overlay);
  7442. }
  7443. } else {
  7444. // Enable the filter button
  7445. filterButton.disabled = false;
  7446. filterButton.style.opacity = '1';
  7447. filterButton.style.cursor = 'pointer';
  7448.  
  7449. // Remove the overlay if it exists
  7450. const overlay = document.getElementById(filterOverlayId);
  7451. if (overlay) {
  7452. overlay.remove();
  7453. }
  7454. }
  7455. } else {
  7456. ConsoleLogEnabled('Filter button not found! Something is wrong!');
  7457. notifications("Something's wrong. Please report and issue on Greasyfork.", "error", "⚠️", "15000");
  7458. }
  7459.  
  7460. if (refreshButtons.length > 0) {
  7461. refreshButtons.forEach((refreshButton) => {
  7462. const refreshParent = refreshButton.parentElement;
  7463.  
  7464. if (disable) {
  7465. // Create an overlay over the refresh button
  7466. let refreshOverlay = refreshParent.querySelector(`.${refreshOverlayClass}`);
  7467. if (!refreshOverlay) {
  7468. refreshOverlay = document.createElement('div');
  7469. refreshOverlay.className = refreshOverlayClass;
  7470. refreshOverlay.style.position = 'absolute';
  7471. refreshOverlay.style.top = '-10px';
  7472. refreshOverlay.style.left = '-10px';
  7473. refreshOverlay.style.width = 'calc(100% + 20px)';
  7474. refreshOverlay.style.height = 'calc(100% + 20px)';
  7475. refreshOverlay.style.backgroundColor = 'transparent';
  7476. refreshOverlay.style.zIndex = '9999';
  7477. refreshOverlay.style.pointerEvents = 'all'; // Block clicks
  7478. refreshParent.style.position = 'relative';
  7479. refreshParent.appendChild(refreshOverlay);
  7480. }
  7481. } else {
  7482. // Remove the overlay if it exists
  7483. const refreshOverlay = refreshParent.querySelector(`.${refreshOverlayClass}`);
  7484. if (refreshOverlay) {
  7485. refreshOverlay.remove();
  7486. }
  7487. }
  7488. });
  7489. } else {
  7490. ConsoleLogEnabled('Refresh button not found!');
  7491. notifications("Something's wrong. Please report and issue on Greasyfork.", "error", "⚠️", "15000");
  7492. }
  7493. }
  7494.  
  7495.  
  7496.  
  7497. async function rbx_card(serverId, playerTokens, maxPlayers, playing, gameId) {
  7498. // Fetch player thumbnails (up to 5)
  7499. const thumbnails = await fetchPlayerThumbnails(playerTokens);
  7500.  
  7501. // Create the server card container
  7502. const cardItem = document.createElement('li');
  7503. cardItem.className = 'rbx-game-server-item col-md-3 col-sm-4 col-xs-6';
  7504.  
  7505. // Create the player thumbnails container
  7506. const playerThumbnailsContainer = document.createElement('div');
  7507. playerThumbnailsContainer.className = 'player-thumbnails-container';
  7508.  
  7509. // Add player thumbnails to the container (up to 5)
  7510. thumbnails.forEach(thumbnail => {
  7511. const playerAvatar = document.createElement('span');
  7512. playerAvatar.className = 'avatar avatar-headshot-md player-avatar';
  7513.  
  7514. const thumbnailImage = document.createElement('span');
  7515. thumbnailImage.className = 'thumbnail-2d-container avatar-card-image';
  7516.  
  7517. const img = document.createElement('img');
  7518. img.src = thumbnail.imageUrl;
  7519. img.alt = '';
  7520. img.title = '';
  7521.  
  7522. thumbnailImage.appendChild(img);
  7523. playerAvatar.appendChild(thumbnailImage);
  7524. playerThumbnailsContainer.appendChild(playerAvatar);
  7525. });
  7526.  
  7527. // Add the 6th placeholder for remaining players
  7528. if (playing > 5) {
  7529. const remainingPlayers = playing - 5;
  7530. const placeholder = document.createElement('span');
  7531. placeholder.className = 'avatar avatar-headshot-md player-avatar hidden-players-placeholder';
  7532. placeholder.textContent = `+${remainingPlayers}`;
  7533. placeholder.style.cssText = `
  7534. background-color: #6a6f81; /* Grayish-blue background */
  7535. color: white;
  7536. display: flex;
  7537. align-items: center;
  7538. justify-content: center;
  7539. border-radius: 50%; /* Fully round */
  7540. font-size: 16px; /* Larger font size */
  7541. width: 60px; /* Larger width */
  7542. height: 60px; /* Larger height */
  7543. `;
  7544. playerThumbnailsContainer.appendChild(placeholder);
  7545. }
  7546.  
  7547. // Create the server details container
  7548. const serverDetails = document.createElement('div');
  7549. serverDetails.className = 'rbx-game-server-details game-server-details';
  7550.  
  7551. // Add server status (e.g., "15 of 15 people max")
  7552. const serverStatus = document.createElement('div');
  7553. serverStatus.className = 'text-info rbx-game-status rbx-game-server-status text-overflow';
  7554. serverStatus.textContent = `${playing} of ${maxPlayers} people max`;
  7555. serverDetails.appendChild(serverStatus);
  7556.  
  7557. // Add the player count gauge
  7558. const gaugeContainer = document.createElement('div');
  7559. gaugeContainer.className = 'server-player-count-gauge border';
  7560.  
  7561. const gaugeInner = document.createElement('div');
  7562. gaugeInner.className = 'gauge-inner-bar border';
  7563. gaugeInner.style.width = `${(playing / maxPlayers) * 100}%`;
  7564.  
  7565. gaugeContainer.appendChild(gaugeInner);
  7566. serverDetails.appendChild(gaugeContainer);
  7567.  
  7568. // Create a container for the buttons
  7569. const buttonContainer = document.createElement('div');
  7570. buttonContainer.className = 'button-container';
  7571. buttonContainer.style.cssText = `
  7572. display: flex;
  7573. gap: 8px; /* Space between buttons */
  7574. `;
  7575.  
  7576. // Add the "Join" button
  7577. const joinButton = document.createElement('button');
  7578. joinButton.type = 'button';
  7579. joinButton.className = 'btn-full-width btn-control-xs rbx-game-server-join game-server-join-btn btn-primary-md btn-min-width';
  7580. joinButton.textContent = 'Join';
  7581.  
  7582. // Add click event to join the server
  7583. joinButton.addEventListener('click', () => {
  7584. showLoadingOverlay();
  7585. Roblox.GameLauncher.joinGameInstance(gameId, serverId);
  7586. });
  7587.  
  7588. buttonContainer.appendChild(joinButton);
  7589.  
  7590. // Add the "Invite" button
  7591. const inviteButton = document.createElement('button');
  7592. inviteButton.type = 'button';
  7593. inviteButton.className = 'btn-full-width btn-control-xs rbx-game-server-invite game-server-invite-btn btn-secondary-md btn-min-width';
  7594. inviteButton.textContent = 'Invite';
  7595.  
  7596. // Add click event to log the invite link
  7597. inviteButton.addEventListener('click', () => {
  7598. const inviteLink = `https://oqarshi.github.io/Invite/?placeid=${gameId}&serverid=${serverId}`;
  7599. ConsoleLogEnabled('Copied invite link:', inviteLink);
  7600. navigator.clipboard.writeText(inviteLink).then(() => {
  7601. notifications('Success! Invite link copied to clipboard!', 'success', '🎉', '2000');
  7602. ConsoleLogEnabled('Invite link copied to clipboard');
  7603. }).catch(err => {
  7604. ConsoleLogEnabled('Failed to copy invite link:', err);
  7605. notifications('Failed! Invite link copied to clipboard!', 'error', '⚠️', '2000');
  7606. });
  7607. });
  7608.  
  7609. buttonContainer.appendChild(inviteButton);
  7610.  
  7611. // Add the button container to the server details
  7612. serverDetails.appendChild(buttonContainer);
  7613.  
  7614. // Assemble the card
  7615. const cardContainer = document.createElement('div');
  7616. cardContainer.className = 'card-item';
  7617. cardContainer.appendChild(playerThumbnailsContainer);
  7618. cardContainer.appendChild(serverDetails);
  7619.  
  7620. cardItem.appendChild(cardContainer);
  7621.  
  7622. // Add the card to the server list
  7623. const serverList = document.querySelector('#rbx-public-game-server-item-container');
  7624. serverList.appendChild(cardItem);
  7625. }
  7626.  
  7627. /*********************************************************************************************************************************************************************************************************************************************
  7628. End of function for the notification function
  7629.  
  7630. *********************************************************************************************************************************************************************************************************************************************/
  7631.  
  7632.  
  7633. /*********************************************************************************************************************************************************************************************************************************************
  7634. Launching Function
  7635.  
  7636. *********************************************************************************************************************************************************************************************************************************************/
  7637.  
  7638. function showLoadingOverlay() {
  7639. // Create the content div (no overlay background)
  7640. const content = document.createElement('div');
  7641. content.style.position = 'fixed';
  7642. content.style.top = 'calc(50% - 15px)'; // Move 15px higher
  7643. content.style.left = '50%';
  7644. content.style.transform = 'translate(-50%, -50%)'; // Center the box horizontally
  7645. content.style.width = '400px';
  7646. content.style.height = '250px';
  7647. content.style.backgroundColor = '#1e1e1e'; // Dark background
  7648. content.style.display = 'flex';
  7649. content.style.flexDirection = 'column';
  7650. content.style.justifyContent = 'center';
  7651. content.style.alignItems = 'center';
  7652. content.style.boxShadow = '0 4px 20px rgba(0, 0, 0, 0.5)'; // Subtle shadow
  7653. content.style.borderRadius = '12px'; // Slightly rounded corners
  7654. content.style.zIndex = '1000000'; // z-index set to 1 million
  7655. content.style.opacity = '0'; // Start with 0 opacity for fade-in
  7656. content.style.transition = 'opacity 0.5s ease'; // Fade-in transition
  7657. content.style.fontFamily = 'Arial, sans-serif'; // Clean font
  7658.  
  7659. // Create the loading text
  7660. const loadingText = document.createElement('p');
  7661. loadingText.textContent = 'Joining Roblox Game...';
  7662. loadingText.style.fontSize = '20px';
  7663. loadingText.style.color = '#fff'; // White text for contrast
  7664. loadingText.style.marginTop = '20px'; // Spacing between image and text
  7665. loadingText.style.fontWeight = 'bold'; // Bold text
  7666.  
  7667. // Create the base64 image
  7668. const base64Image = document.createElement('img');
  7669. base64Image.src = window.Base64Images.logo;
  7670. base64Image.style.width = '80px'; // Slightly larger image
  7671. base64Image.style.height = '80px'; // Slightly larger image
  7672. base64Image.style.borderRadius = '8px'; // Rounded corners for the image
  7673.  
  7674. // Create the loading boxes container
  7675. const loadingBoxes = document.createElement('div');
  7676. loadingBoxes.style.display = 'flex';
  7677. loadingBoxes.style.alignItems = 'center';
  7678. loadingBoxes.style.justifyContent = 'center';
  7679. loadingBoxes.style.marginTop = '15px'; // Spacing between text and boxes
  7680.  
  7681. // Create the three loading boxes
  7682. for (let i = 0; i < 3; i++) {
  7683. const box = document.createElement('div');
  7684. box.style.width = '10px';
  7685. box.style.height = '10px';
  7686. box.style.backgroundColor = '#fff'; // White boxes
  7687. box.style.margin = '0 5px'; // Spacing between boxes
  7688. box.style.borderRadius = '2px'; // Slightly rounded corners
  7689. box.style.animation = `pulse 1.2s ${i * 0.2}s infinite`; // Animation with delay
  7690. loadingBoxes.appendChild(box);
  7691. }
  7692.  
  7693. // Define the pulse animation using CSS
  7694. const style = document.createElement('style');
  7695. style.textContent = `
  7696. @keyframes pulse {
  7697. 0%, 100% { transform: scale(1); }
  7698. 50% { transform: scale(1.5); }
  7699. }
  7700. `;
  7701. document.head.appendChild(style); // Add the animation to the document
  7702.  
  7703. // Append the image, text, and loading boxes to the content div
  7704. content.appendChild(base64Image);
  7705. content.appendChild(loadingText);
  7706. content.appendChild(loadingBoxes);
  7707.  
  7708. // Append the content div to the body
  7709. document.body.appendChild(content);
  7710.  
  7711. // Trigger fade-in animation
  7712. setTimeout(() => {
  7713. content.style.opacity = '1';
  7714. }, 10); // Small delay to trigger the transition
  7715.  
  7716. // Remove the content after 8 seconds with fade-out animation
  7717. setTimeout(() => {
  7718. content.style.opacity = '0'; // Fade out
  7719. setTimeout(() => {
  7720. document.body.removeChild(content); // Remove after fade-out completes
  7721. }, 500); // Wait for the fade-out transition to finish
  7722. }, 8000); // 8000 milliseconds = 8 seconds
  7723. }
  7724.  
  7725.  
  7726. /*********************************************************************************************************************************************************************************************************************************************
  7727. End of function for the launching function
  7728.  
  7729. *********************************************************************************************************************************************************************************************************************************************/
  7730.  
  7731. const serverRegionsByIp = {
  7732. "128.116.0.0": { city: "Hong Kong", country: { name: "Hong Kong", code: "HK" }, latitude: 22.3193, longitude: 114.1694 },
  7733. "128.116.1.0": { city: "Los Angeles", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 34.0522, longitude: -118.2437 },
  7734. "128.116.2.0": { city: "Warsaw", country: { name: "Poland", code: "PL" }, region: { name: "Mazowieckie", code: "14" }, latitude: 52.2297, longitude: 21.0122 },
  7735. "128.116.3.0": { city: "Warsaw", country: { name: "Poland", code: "PL" }, region: { name: "Mazowieckie", code: "14" }, latitude: 52.2297, longitude: 21.0122 },
  7736. "128.116.4.0": { city: "Paris", country: { name: "France", code: "FR" }, region: { name: "Île-de-France", code: "IDF" }, latitude: 48.8566, longitude: 2.3522 },
  7737. "128.116.5.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  7738. "128.116.6.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
  7739. "128.116.7.0": { city: "Mumbai", country: { name: "India", code: "IN" }, region: { name: "Mahārāshtra", code: "MH" }, latitude: 19.0760, longitude: 72.8777 },
  7740. "128.116.8.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  7741. "128.116.9.0": { city: "Mumbai", country: { name: "India", code: "IN" }, region: { name: "Mahārāshtra", code: "MH" }, latitude: 19.0760, longitude: 72.8777 },
  7742. "128.116.10.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  7743. "128.116.11.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  7744. "128.116.12.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  7745. "128.116.13.0": { city: "Amsterdam", country: { name: "Netherlands", code: "NL" }, region: { name: "Noord-Holland", code: "NH" }, latitude: 52.3676, longitude: 4.9041 },
  7746. "128.116.14.0": { city: "Hong Kong", country: { name: "Hong Kong", code: "HK" }, latitude: 22.3193, longitude: 114.1694 },
  7747. "128.116.15.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
  7748. "128.116.16.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
  7749. "128.116.17.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
  7750. "128.116.18.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 },
  7751. "128.116.19.0": { city: "Paris", country: { name: "France", code: "FR" }, region: { name: "Île-de-France", code: "IDF" }, latitude: 48.8566, longitude: 2.3522 },
  7752. "128.116.20.0": { city: "Paris", country: { name: "France", code: "FR" }, region: { name: "Île-de-France", code: "IDF" }, latitude: 48.8566, longitude: 2.3522 },
  7753. "128.116.21.0": { city: "Amsterdam", country: { name: "Netherlands", code: "NL" }, region: { name: "Noord-Holland", code: "NH" }, latitude: 52.3676, longitude: 4.9041 },
  7754. "128.116.22.0": { city: "Atlanta", country: { name: "United States", code: "US" }, region: { name: "Georgia", code: "GA" }, latitude: 33.7490, longitude: -84.3880 },
  7755. "128.116.23.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
  7756. "128.116.24.0": { city: "Atlanta", country: { name: "United States", code: "US" }, region: { name: "Georgia", code: "GA" }, latitude: 33.7490, longitude: -84.3880 },
  7757. "128.116.25.0": { city: "Atlanta", country: { name: "United States", code: "US" }, region: { name: "Georgia", code: "GA" }, latitude: 33.7490, longitude: -84.3880 },
  7758. "128.116.26.0": { city: "Paris", country: { name: "France", code: "FR" }, region: { name: "Île-de-France", code: "IDF" }, latitude: 48.8566, longitude: 2.3522 },
  7759. "128.116.27.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  7760. "128.116.28.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  7761. "128.116.29.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  7762. "128.116.30.0": { city: "Hong Kong", country: { name: "Hong Kong", code: "HK" }, latitude: 22.3193, longitude: 114.1694 },
  7763. "128.116.31.0": { city: "Warsaw", country: { name: "Poland", code: "PL" }, region: { name: "Mazowieckie", code: "14" }, latitude: 52.2297, longitude: 21.0122 },
  7764. "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 },
  7765. "128.116.33.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 },
  7766. "128.116.34.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  7767. "128.116.35.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 },
  7768. "128.116.36.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 },
  7769. "128.116.37.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 },
  7770. "128.116.38.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 },
  7771. "128.116.39.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  7772. "128.116.40.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  7773. "128.116.41.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  7774. "128.116.42.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  7775. "128.116.43.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  7776. "128.116.44.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  7777. "128.116.45.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 },
  7778. "128.116.46.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  7779. "128.116.47.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  7780. "128.116.48.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  7781. "128.116.49.0": { city: "Los Angeles", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 34.0522, longitude: -118.2437 },
  7782. "128.116.50.0": { city: "Singapore", country: { name: "Singapore", code: "SG" }, latitude: 1.3521, longitude: 103.8198 },
  7783. "128.116.51.0": { city: "Sydney", country: { name: "Australia", code: "AU" }, region: { name: "New South Wales", code: "NSW" }, latitude: -33.8688, longitude: 151.2093 },
  7784. "128.116.52.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  7785. "128.116.53.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  7786. "128.116.54.0": { city: "Amsterdam", country: { name: "Netherlands", code: "NL" }, region: { name: "Noord-Holland", code: "NH" }, latitude: 52.3676, longitude: 4.9041 },
  7787. "128.116.55.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
  7788. "128.116.56.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  7789. "128.116.57.0": { city: "San Jose", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3382, longitude: -121.8863 },
  7790. "128.116.58.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
  7791. "128.116.59.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
  7792. "128.116.60.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
  7793. "128.116.61.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  7794. "128.116.62.0": { city: "Seattle", country: { name: "United States", code: "US" }, region: { name: "Washington", code: "WA" }, latitude: 47.6062, longitude: -122.3321 },
  7795. "128.116.63.0": { city: "Los Angeles", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 34.0522, longitude: -118.2437 },
  7796. "128.116.64.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  7797. "128.116.65.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
  7798. "128.116.66.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
  7799. "128.116.67.0": { city: "San Jose", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3382, longitude: -121.8863 },
  7800. "128.116.68.0": { city: "Santa Clara", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3541, longitude: -121.9552 },
  7801. "128.116.69.0": { city: "Santa Clara", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3541, longitude: -121.9552 },
  7802. "128.116.70.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  7803. "128.116.71.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  7804. "128.116.72.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 },
  7805. "128.116.73.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 },
  7806. "128.116.74.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  7807. "128.116.75.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  7808. "128.116.76.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  7809. "128.116.77.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  7810. "128.116.78.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  7811. "128.116.79.0": { city: "Singapore", country: { name: "Singapore", code: "SG" }, latitude: 1.3521, longitude: 103.8198 },
  7812. "128.116.80.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  7813. "128.116.81.0": { city: "Santa Clara", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3541, longitude: -121.9552 },
  7814. "128.116.82.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
  7815. "128.116.83.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
  7816. "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 },
  7817. "128.116.85.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 },
  7818. "128.116.86.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  7819. "128.116.87.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  7820. "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 },
  7821. "128.116.89.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 },
  7822. "128.116.90.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  7823. "128.116.91.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  7824. "128.116.92.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  7825. "128.116.93.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  7826. "128.116.94.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  7827. "128.116.95.0": { city: "Dallas", country: { name: "United States", code: "US" }, region: { name: "Texas", code: "TX" }, latitude: 32.7767, longitude: -96.7970 },
  7828. "128.116.96.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  7829. "128.116.97.0": { city: "Singapore", country: { name: "Singapore", code: "SG" }, latitude: 1.3521, longitude: 103.8198 },
  7830. "128.116.98.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  7831. "128.116.99.0": { city: "Atlanta", country: { name: "United States", code: "US" }, region: { name: "Georgia", code: "GA" }, latitude: 33.7490, longitude: -84.3880 },
  7832. "128.116.100.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  7833. "128.116.101.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  7834. "128.116.102.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  7835. "128.116.103.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  7836. "128.116.104.0": { city: "Mumbai", country: { name: "India", code: "IN" }, region: { name: "Mahārāshtra", code: "MH" }, latitude: 19.0760, longitude: 72.8777 },
  7837. "128.116.105.0": { city: "Santa Clara", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3541, longitude: -121.9552 },
  7838. "128.116.106.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  7839. "128.116.107.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  7840. "128.116.108.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  7841. "128.116.109.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  7842. "128.116.110.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  7843. "128.116.111.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  7844. "128.116.112.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  7845. "128.116.113.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  7846. "128.116.114.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  7847. "128.116.115.0": { city: "Seattle", country: { name: "United States", code: "US" }, region: { name: "Washington", code: "WA" }, latitude: 47.6062, longitude: -122.3321 },
  7848. "128.116.116.0": { city: "Los Angeles", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 34.0522, longitude: -118.2437 },
  7849. "128.116.117.0": { city: "San Jose", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3382, longitude: -121.8863 },
  7850. "128.116.118.0": { city: "Hong Kong", country: { name: "Hong Kong", code: "HK" }, latitude: 22.3193, longitude: 114.1694 },
  7851. "128.116.119.0": { city: "London", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5072, longitude: 0.1276 },
  7852. "128.116.120.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
  7853. "128.116.121.0": { city: "Amsterdam", country: { name: "Netherlands", code: "NL" }, region: { name: "Noord-Holland", code: "NH" }, latitude: 52.3676, longitude: 4.9041 },
  7854. "128.116.122.0": { city: "Paris", country: { name: "France", code: "FR" }, region: { name: "Île-de-France", code: "IDF" }, latitude: 48.8566, longitude: 2.3522 },
  7855. "128.116.123.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  7856. "128.116.124.0": { city: "Warsaw", country: { name: "Poland", code: "PL" }, region: { name: "Mazowieckie", code: "14" }, latitude: 52.2297, longitude: 21.0122 },
  7857. "128.116.125.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  7858. "128.116.126.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
  7859. "128.116.127.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 },
  7860. };
  7861. // find_game_id function does nothing lmao but its ere i guees
  7862. function find_game_id() {
  7863. ConsoleLogEnabled('Trying to find game id');
  7864. const gameIdMatch = window.location.pathname.match(/\/games\/(\d+)\//);
  7865. if (!gameIdMatch) {
  7866. ConsoleLogEnabled("Game ID not found in URL!");
  7867. return;
  7868. }
  7869.  
  7870. const gameId = gameIdMatch[1];
  7871. }
  7872.  
  7873.  
  7874. // end of the check for the url
  7875. }
  7876.  
  7877. /*******************************************************
  7878. End of code for the random hop button and the filter button on roblox.com/games/*
  7879. *******************************************************/
  7880.  
  7881.  
  7882.  
  7883.  
  7884. })();