RoLocate

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

目前为 2025-01-15 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name RoLocate
  3. // @namespace https://oqarshi.github.io/
  4. // @version 21.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/games/*
  8. // @license MIT
  9. // @icon data:image/jpeg;base64,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
  10. // @grant GM_xmlhttpRequest
  11. // ==/UserScript==
  12.  
  13.  
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. /*********************************************************************************************************************************************************************************************************************************************
  19. 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
  20.  
  21. *********************************************************************************************************************************************************************************************************************************************/
  22.  
  23. function createPopup() {
  24. const popup = document.createElement('div');
  25. popup.className = 'server-filters-dropdown-box'; // Unique class name
  26. popup.style.cssText = `
  27. position: absolute;
  28. width: 210px;
  29. height: 382px;
  30. right: 0px;
  31. top: 30px;
  32. z-index: 1000;
  33. border-radius: 5px;
  34. background-color: rgb(30, 32, 34);
  35. display: flex;
  36. flex-direction: column;
  37. padding: 5px;
  38. `;
  39.  
  40. // Create the header section
  41. const header = document.createElement('div');
  42. header.style.cssText = `
  43. display: flex;
  44. align-items: center;
  45. padding: 10px;
  46. border-bottom: 1px solid #444;
  47. margin-bottom: 5px;
  48. `;
  49.  
  50. // Add the logo (base64 image)
  51. const logo = document.createElement('img');
  52. logo.src = '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'; // Replace with your base64 logo
  53. logo.style.cssText = `
  54. width: 24px;
  55. height: 24px;
  56. margin-right: 10px;
  57. `;
  58.  
  59. // Add the title
  60. const title = document.createElement('span');
  61. title.textContent = 'RoLocate';
  62. title.style.cssText = `
  63. color: white;
  64. font-size: 18px;
  65. font-weight: bold;
  66. `;
  67.  
  68. // Append logo and title to the header
  69. header.appendChild(logo);
  70. header.appendChild(title);
  71.  
  72. // Append the header to the popup
  73. popup.appendChild(header);
  74.  
  75. // Define unique names and tooltips for each button
  76. const buttonData = [{
  77. name: "Smallest Servers",
  78. tooltip: "**Reverses the order of the server list.** The emptiest servers will be displayed first."
  79. },
  80. {
  81. name: "Available Space",
  82. tooltip: "**Filters out servers which are full.** Servers with space will only be shown."
  83. },
  84. {
  85. name: "Player Count",
  86. tooltip: "**Roblox Locator finds 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."
  87. },
  88. {
  89. name: "Random Shuffle",
  90. tooltip: "**Display servers in a completely random order.** Shows servers with space and servers with low player counts in a randomized order."
  91. },
  92. {
  93. name: "Server Region",
  94. 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."
  95. },
  96. {
  97. name: "Best Connection",
  98. 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."
  99. },
  100. {
  101. name: "Auto Join Small Server",
  102. 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."
  103. },
  104. {
  105. name: "About",
  106. tooltip: "**The Credits.** Rolocate was created by Oqarshi. Special thanks to BTRoblox ❤️. Enjoy using Rolocate!"
  107. }
  108. ];
  109.  
  110. // Create buttons with unique names and tooltips
  111. buttonData.forEach((data, index) => {
  112. const buttonContainer = document.createElement('div');
  113. buttonContainer.className = 'server-filter-option';
  114. buttonContainer.style.cssText = `
  115. width: 190px;
  116. height: 30px;
  117. background-color: #393B3D;
  118. margin: 5px;
  119. border-radius: 5px;
  120. padding: 3.5px;
  121. position: relative;
  122. cursor: pointer;
  123. display: flex;
  124. align-items: center;
  125. justify-content: center;
  126. transition: background-color 0.3s ease;
  127. `;
  128.  
  129. const tooltip = document.createElement('div');
  130. tooltip.className = 'filter-tooltip';
  131. tooltip.style.cssText = `
  132. display: none;
  133. position: absolute;
  134. top: -10px;
  135. left: 200px;
  136. width: auto;
  137. inline-size: 200px;
  138. height: auto;
  139. background-color: #191B1D;
  140. color: white;
  141. padding: 5px;
  142. border-radius: 5px;
  143. white-space: pre-wrap;
  144. font-size: 14px;
  145. `;
  146.  
  147. // Parse tooltip text and replace **...** with bold HTML tags
  148. tooltip.innerHTML = data.tooltip.replace(/\*\*(.*?)\*\*/g, "<b style='color: #068f00;'>$1</b>");
  149.  
  150. const buttonText = document.createElement('p');
  151. buttonText.style.cssText = `
  152. margin: 0;
  153. color: white;
  154. font-size: 16px;
  155. `;
  156. buttonText.textContent = data.name;
  157.  
  158. buttonContainer.appendChild(tooltip);
  159. buttonContainer.appendChild(buttonText);
  160.  
  161. buttonContainer.addEventListener('mouseover', () => {
  162. tooltip.style.display = 'block';
  163. buttonContainer.style.backgroundColor = '#4A4C4E'; // Hover effect
  164. });
  165. buttonContainer.addEventListener('mouseout', () => {
  166. tooltip.style.display = 'none';
  167. buttonContainer.style.backgroundColor = '#393B3D'; // Revert to original color
  168. });
  169.  
  170. buttonContainer.addEventListener('click', () => {
  171. switch (index) {
  172. case 0:
  173. smallest_servers();
  174. break;
  175. case 1:
  176. available_space_servers();
  177. break;
  178. case 2:
  179. player_count_tab();
  180. break;
  181. case 3:
  182. random_servers();
  183. break;
  184. case 4:
  185. createServerCountPopup((totalLimit) => {
  186. rebuildServerList(gameId, totalLimit);
  187. });
  188. break;
  189. case 5:
  190. rebuildServerList(gameId, 50, true);
  191. break;
  192. case 6:
  193. auto_join_small_server();
  194. break;
  195. case 7:
  196. credits();
  197. break;
  198. }
  199. });
  200.  
  201. popup.appendChild(buttonContainer);
  202. });
  203.  
  204. return popup;
  205. }
  206.  
  207.  
  208. /*******************************************************
  209. name of function: An Observer for the filter button
  210. description: to put the filter button on the page
  211. *******************************************************/
  212.  
  213. // Wait for the server list options container to load
  214. const observer = new MutationObserver((mutations, obs) => {
  215. const serverListOptions = document.querySelector('.server-list-options');
  216. if (serverListOptions) {
  217. // Create the filter button
  218. const filterButton = document.createElement('a');
  219. filterButton.className = 'RL-filter-button'; // Unique class name
  220. filterButton.style.cssText = `
  221. color: white;
  222. font-weight: bold;
  223. text-decoration: none;
  224. cursor: pointer;
  225. margin-left: 10px;
  226. padding: 5px 10px;
  227. display: flex;
  228. align-items: center;
  229. gap: 5px;
  230. position: relative;
  231. margin-top: 4px
  232. `;
  233. filterButton.addEventListener('mouseover', () => {
  234. filterButton.style.textDecoration = 'underline';
  235. });
  236. filterButton.addEventListener('mouseout', () => {
  237. filterButton.style.textDecoration = 'none';
  238. });
  239.  
  240. // Add the "Filter" text
  241. const buttonText = document.createElement('span');
  242. buttonText.className = 'RL-filter-text'; // Unique class name
  243. buttonText.textContent = 'Filters';
  244. filterButton.appendChild(buttonText);
  245.  
  246. // Add the icon (three horizontal dashes)
  247. const icon = document.createElement('span');
  248. icon.className = 'RL-filter-icon'; // Unique class name
  249. icon.textContent = '≡';
  250. icon.style.cssText = `
  251. font-size: 18px;
  252. `;
  253. filterButton.appendChild(icon);
  254.  
  255. // Append the button to the server list options container
  256. serverListOptions.appendChild(filterButton);
  257.  
  258. // Handle click event to show/hide the popup
  259. let popup = null;
  260. filterButton.addEventListener('click', (event) => {
  261. event.stopPropagation(); // Prevent event bubbling
  262. if (popup) {
  263. popup.remove(); // Remove the popup if it already exists
  264. popup = null;
  265. } else {
  266. popup = createPopup();
  267. // Position the popup next to the filter button
  268. popup.style.top = `${filterButton.offsetHeight}px`;
  269. popup.style.left = '0';
  270. filterButton.appendChild(popup);
  271. }
  272. });
  273.  
  274. // Close the popup when clicking outside
  275. document.addEventListener('click', (event) => {
  276. if (popup && !filterButton.contains(event.target)) {
  277. popup.remove();
  278. popup = null;
  279. }
  280. });
  281.  
  282. // Stop observing once the button is added
  283. obs.disconnect();
  284. }
  285. })
  286.  
  287. /*********************************************************************************************************************************************************************************************************************************************
  288. 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
  289.  
  290. *********************************************************************************************************************************************************************************************************************************************/
  291.  
  292.  
  293. /*********************************************************************************************************************************************************************************************************************************************
  294. Functions for the 1st button
  295.  
  296. *********************************************************************************************************************************************************************************************************************************************/
  297.  
  298.  
  299. /*******************************************************
  300. name of function: smallest_servers FIRST FUNCTION
  301. description: Fetches the smallest servers, disables the "Load More" button, shows a loading bar, and recreates the server cards.
  302. *******************************************************/
  303. async function smallest_servers() {
  304. // Disable the "Load More" button and show the loading bar
  305. Loadingbar(true);
  306. disableFilterButton(true);
  307. disableLoadMoreButton();
  308.  
  309. // Get the game ID from the URL
  310. const gameId = window.location.pathname.split('/')[2];
  311.  
  312. // Retry mechanism
  313. let retries = 3;
  314. let success = false;
  315.  
  316. while (retries > 0 && !success) {
  317. try {
  318. // Fetch server data from the Roblox API
  319. const response = await fetch(`https://games.roblox.com/v1/games/${gameId}/servers/0?sortOrder=1&excludeFullGames=true&limit=100`);
  320.  
  321. // Check if the response status is 429 (Too Many Requests)
  322. if (response.status === 429) {
  323. throw new Error('429: Too Many Requests');
  324. }
  325.  
  326. const data = await response.json();
  327.  
  328. // Process each server
  329. for (const server of data.data) {
  330. const {
  331. id: serverId,
  332. playerTokens,
  333. maxPlayers,
  334. playing
  335. } = server;
  336.  
  337. // Pass the server data to the card creation function
  338. await rbx_card(serverId, playerTokens, maxPlayers, playing, gameId);
  339. }
  340.  
  341. success = true; // Mark as successful if no errors occurred
  342. } catch (error) {
  343. retries--; // Decrement the retry count
  344.  
  345. if (error.message === '429: Too Many Requests' && retries > 0) {
  346. console.log('Encountered a 429 error. Retrying in 10 seconds...');
  347. await new Promise(resolve => setTimeout(resolve, 10000)); // Wait for 10 seconds
  348. } else {
  349. console.error('Error fetching server data:', error);
  350. break; // Exit the loop if it's not a 429 error or no retries left
  351. }
  352. } finally {
  353. if (success || retries === 0) {
  354. // Hide the loading bar and enable the filter button
  355. Loadingbar(false);
  356. disableFilterButton(false);
  357. }
  358. }
  359. }
  360. }
  361.  
  362.  
  363.  
  364. /*********************************************************************************************************************************************************************************************************************************************
  365. Functions for the 2nd button
  366.  
  367. *********************************************************************************************************************************************************************************************************************************************/
  368.  
  369.  
  370. /*******************************************************
  371. name of function: available_space_servers
  372. description: Fetches servers with available space, disables the "Load More" button, shows a loading bar, and recreates the server cards.
  373. *******************************************************/
  374. async function available_space_servers() {
  375. // Disable the "Load More" button and show the loading bar
  376. Loadingbar(true);
  377. disableLoadMoreButton();
  378. disableFilterButton(true);
  379.  
  380. // Get the game ID from the URL
  381. const gameId = window.location.pathname.split('/')[2];
  382.  
  383. // Retry mechanism
  384. let retries = 3;
  385. let success = false;
  386.  
  387. while (retries > 0 && !success) {
  388. try {
  389. // Fetch server data from the Roblox API
  390. const response = await fetch(`https://games.roblox.com/v1/games/${gameId}/servers/0?sortOrder=2&excludeFullGames=true&limit=100`);
  391.  
  392. // Check if the response status is 429 (Too Many Requests)
  393. if (response.status === 429) {
  394. throw new Error('429: Too Many Requests');
  395. }
  396.  
  397. const data = await response.json();
  398.  
  399. // Process each server
  400. for (const server of data.data) {
  401. const {
  402. id: serverId,
  403. playerTokens,
  404. maxPlayers,
  405. playing
  406. } = server;
  407.  
  408. // Pass the server data to the card creation function
  409. await rbx_card(serverId, playerTokens, maxPlayers, playing, gameId);
  410. }
  411.  
  412. success = true; // Mark as successful if no errors occurred
  413. } catch (error) {
  414. retries--; // Decrement the retry count
  415.  
  416. if (error.message === '429: Too Many Requests' && retries > 0) {
  417. console.log('Encountered a 429 error. Retrying in 10 seconds...');
  418. await new Promise(resolve => setTimeout(resolve, 10000)); // Wait for 10 seconds
  419. } else {
  420. console.error('Error fetching server data:', error);
  421. break; // Exit the loop if it's not a 429 error or no retries left
  422. }
  423. } finally {
  424. if (success || retries === 0) {
  425. // Hide the loading bar and enable the filter button
  426. Loadingbar(false);
  427. disableFilterButton(false);
  428. }
  429. }
  430. }
  431. }
  432.  
  433. /*********************************************************************************************************************************************************************************************************************************************
  434. Functions for the 3rd button
  435.  
  436. *********************************************************************************************************************************************************************************************************************************************/
  437.  
  438.  
  439. /*******************************************************
  440. name of function: player_count_tab
  441. description: Opens a popup for the user to select the max player count using a slider and filters servers accordingly.
  442. *******************************************************/
  443. function player_count_tab() {
  444. // Create the popup container
  445. const popup = document.createElement('div');
  446. popup.className = 'player-count-popup';
  447. popup.style.cssText = `
  448. position: fixed;
  449. top: 50%;
  450. left: 50%;
  451. transform: translate(-50%, -50%);
  452. background-color: rgb(30, 32, 34);
  453. padding: 20px;
  454. border-radius: 5px;
  455. z-index: 10000;
  456. box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
  457. display: flex;
  458. flex-direction: column;
  459. align-items: center;
  460. gap: 10px;
  461. `;
  462.  
  463. // Add a title
  464. const title = document.createElement('h3');
  465. title.textContent = 'Select Max Player Count';
  466. title.style.cssText = `
  467. color: white;
  468. margin: 0;
  469. font-size: 18px;
  470. `;
  471. popup.appendChild(title);
  472.  
  473. // Add a slider
  474. const slider = document.createElement('input');
  475. slider.type = 'range';
  476. slider.min = '1';
  477. slider.max = '100';
  478. slider.value = '1'; // Default value
  479. slider.style.cssText = `
  480. width: 200px;
  481. cursor: pointer;
  482. `;
  483. popup.appendChild(slider);
  484.  
  485. // Add a display for the slider value
  486. const sliderValue = document.createElement('span');
  487. sliderValue.textContent = slider.value;
  488. sliderValue.style.cssText = `
  489. color: white;
  490. font-size: 16px;
  491. `;
  492. popup.appendChild(sliderValue);
  493.  
  494. // Update the slider value display when the slider changes
  495. slider.addEventListener('input', () => {
  496. sliderValue.textContent = slider.value;
  497. });
  498.  
  499. // Add a submit button
  500. const submitButton = document.createElement('button');
  501. submitButton.textContent = 'Search';
  502. submitButton.style.cssText = `
  503. padding: 5px 10px;
  504. font-size: 16px;
  505. background-color: #00A2FF;
  506. color: white;
  507. border: none;
  508. border-radius: 3px;
  509. cursor: pointer;
  510. `;
  511. popup.appendChild(submitButton);
  512.  
  513. // Add a close button
  514. const closeButton = document.createElement('button');
  515. closeButton.textContent = 'Close';
  516. closeButton.style.cssText = `
  517. padding: 5px 10px;
  518. font-size: 16px;
  519. background-color: #555;
  520. color: white;
  521. border: none;
  522. border-radius: 3px;
  523. cursor: pointer;
  524. `;
  525. popup.appendChild(closeButton);
  526.  
  527. // Append the popup to the body
  528. document.body.appendChild(popup);
  529.  
  530. // Handle submit button click
  531. submitButton.addEventListener('click', () => {
  532. const maxPlayers = parseInt(slider.value, 10);
  533. if (!isNaN(maxPlayers) && maxPlayers > 0) {
  534. filterServersByPlayerCount(maxPlayers);
  535. popup.remove();
  536. } else {
  537. notifications('Error: Please enter a number greater than 0', 'error', '⚠️');
  538. }
  539. });
  540. // Handle close button click
  541. closeButton.addEventListener('click', () => {
  542. popup.remove();
  543. });
  544.  
  545. // Close the popup when clicking outside
  546. document.addEventListener('click', (event) => {
  547. if (!popup.contains(event.target)) {
  548. popup.remove();
  549. }
  550. });
  551. }
  552.  
  553. /*******************************************************
  554. name of function: fetchServersWithRetry
  555. description: Fetches server data with retry logic and a delay between requests to avoid rate-limiting.
  556. Uses GM_xmlhttpRequest instead of fetch.
  557. *******************************************************/
  558. async function fetchServersWithRetry(url, retries = 15, currentDelay = 750) {
  559. return new Promise((resolve, reject) => {
  560. GM_xmlhttpRequest({
  561. method: 'GET',
  562. url: url,
  563. onload: function(response) {
  564. // Check for 429 Rate Limit error
  565. if (response.status === 429) {
  566. if (retries > 0) {
  567. const newDelay = currentDelay * 1; // Exponential backoff
  568. console.log(`[DEBUG] Rate limited. Waiting ${newDelay / 1000} seconds before retrying...`);
  569. setTimeout(() => {
  570. resolve(fetchServersWithRetry(url, retries - 1, newDelay)); // Retry with increased delay
  571. }, newDelay);
  572. } else {
  573. console.error('[DEBUG] Rate limit retries exhausted.');
  574. notifications('Error: Rate limited please try again later.', 'error', '⚠️')
  575. reject(new Error('RateLimit'));
  576. }
  577. return;
  578. }
  579.  
  580. // Handle other HTTP errors
  581. if (response.status < 200 || response.status >= 300) {
  582. console.error('[DEBUG] HTTP error:', response.status, response.statusText);
  583. reject(new Error(`HTTP error: ${response.status}`));
  584. return;
  585. }
  586.  
  587. // Parse and return the JSON data
  588. try {
  589. const data = JSON.parse(response.responseText);
  590. console.log('[DEBUG] Fetched data successfully:', data);
  591. resolve(data);
  592. } catch (error) {
  593. console.error('[DEBUG] Error parsing JSON:', error);
  594. reject(error);
  595. }
  596. },
  597. onerror: function(error) {
  598. console.error('[DEBUG] Error in GM_xmlhttpRequest:', error);
  599. reject(error);
  600. }
  601. });
  602. });
  603. }
  604.  
  605. /*******************************************************
  606. name of function: filterServersByPlayerCount
  607. description: Filters servers to show only those with a player count equal to or below the specified max.
  608. If no exact matches are found, prioritizes servers with player counts lower than the input.
  609. Keeps fetching until at least 8 servers are found, with a dynamic delay between requests.
  610. *******************************************************/
  611. async function filterServersByPlayerCount(maxPlayers) {
  612. // Validate maxPlayers before proceeding
  613. if (isNaN(maxPlayers) || maxPlayers < 1 || !Number.isInteger(maxPlayers)) {
  614. console.error('[DEBUG] Invalid input for maxPlayers.');
  615. notifications('Error: Please input a valid whole number greater than or equal to 1.', 'error', '⚠️');
  616. return;
  617. }
  618.  
  619. // Disable UI elements and clear the server list
  620. Loadingbar(true);
  621. disableLoadMoreButton();
  622. disableFilterButton(true);
  623. const serverList = document.querySelector('#rbx-game-server-item-container');
  624. serverList.innerHTML = '';
  625.  
  626. const gameId = window.location.pathname.split('/')[2];
  627. let cursor = null;
  628. let serversFound = 0;
  629. let serverMaxPlayers = null;
  630. let isCloserToOne = null;
  631. let topDownServers = []; // Servers collected during top-down search
  632. let bottomUpServers = []; // Servers collected during bottom-up search
  633. let currentDelay = 500; // Initial delay of 0.5 seconds
  634. const timeLimit = 3 * 60 * 1000; // 3 minutes in milliseconds
  635. const startTime = Date.now(); // Record the start time
  636. notifications('Will search for a maximum of 3 minutes to find a server.', 'success', '🔎');
  637.  
  638.  
  639. try {
  640. while (serversFound < 16) {
  641. // Check if the time limit has been exceeded
  642. if (Date.now() - startTime > timeLimit) {
  643. console.log('[DEBUG] Time limit reached. Proceeding to fallback servers.');
  644. notifications('Warning: Time limit reached. Proceeding to fallback servers.', 'warning', '❗');
  645. break;
  646. }
  647.  
  648. // Fetch initial data to determine serverMaxPlayers and isCloserToOne
  649. if (!serverMaxPlayers) {
  650. const initialUrl = cursor ?
  651. `https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=100&cursor=${cursor}` :
  652. `https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=100`;
  653.  
  654. const initialData = await fetchServersWithRetry(initialUrl);
  655. if (initialData.data.length > 0) {
  656. serverMaxPlayers = initialData.data[0].maxPlayers;
  657. isCloserToOne = maxPlayers <= (serverMaxPlayers / 2);
  658. } else {
  659. console.error('[DEBUG] No servers found in initial fetch.');
  660. break;
  661. }
  662. }
  663.  
  664. // Validate maxPlayers against serverMaxPlayers
  665. if (maxPlayers >= serverMaxPlayers) {
  666. console.error('[DEBUG] Invalid input: maxPlayers is greater than or equal to serverMaxPlayers.');
  667. notifications(`Error: Please input a number between 1 through ${serverMaxPlayers - 1}`, 'error', '⚠️');
  668. return;
  669. }
  670.  
  671. // Adjust the URL based on isCloserToOne
  672. const baseUrl = isCloserToOne ?
  673. `https://games.roblox.com/v1/games/${gameId}/servers/public?sortOrder=1&excludeFullGames=true&limit=100` :
  674. `https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=100`; // why does this work lmao
  675.  
  676. const url = cursor ? `${baseUrl}&cursor=${cursor}` : baseUrl;
  677. const data = await fetchServersWithRetry(url);
  678.  
  679. // Safety check: Ensure the server list is valid and iterable
  680. if (!Array.isArray(data.data)) {
  681. console.error('[DEBUG] Invalid server list received. Waiting 1 second before retrying...');
  682. await delay(1000); // Wait 1 second before retrying
  683. continue; // Skip the rest of the loop and retry
  684. }
  685.  
  686. // Filter and process servers
  687. for (const server of data.data) {
  688. if (server.playing === maxPlayers) {
  689. await rbx_card(server.id, server.playerTokens, server.maxPlayers, server.playing, gameId);
  690. serversFound++;
  691.  
  692. if (serversFound >= 16) {
  693. break;
  694. }
  695. } else if (!isCloserToOne && server.playing > maxPlayers) {
  696. topDownServers.push(server); // Add to top-down fallback list
  697. } else if (isCloserToOne && server.playing < maxPlayers) {
  698. bottomUpServers.push(server); // Add to bottom-up fallback list
  699. }
  700. }
  701.  
  702. // Exit if no more servers are available
  703. if (!data.nextPageCursor) {
  704. break;
  705. }
  706.  
  707. cursor = data.nextPageCursor;
  708.  
  709. // Adjust delay dynamically
  710. if (currentDelay > 150) {
  711. currentDelay = Math.max(150, currentDelay / 2); // Gradually reduce delay
  712. }
  713. console.log(`[DEBUG] Waiting ${currentDelay / 1000} seconds before next request...`);
  714. await delay(currentDelay);
  715. }
  716.  
  717. // If no exact matches were found or time limit reached, use fallback servers
  718. if (serversFound === 0 && (topDownServers.length > 0 || bottomUpServers.length > 0)) {
  719. // Sort top-down servers by player count (ascending)
  720. topDownServers.sort((a, b) => a.playing - b.playing);
  721.  
  722. // Sort bottom-up servers by player count (descending)
  723. bottomUpServers.sort((a, b) => b.playing - a.playing);
  724.  
  725. // Combine both fallback lists (prioritize top-down servers first)
  726. const combinedFallback = [...topDownServers, ...bottomUpServers];
  727.  
  728. for (const server of combinedFallback) {
  729. await rbx_card(server.id, server.playerTokens, server.maxPlayers, server.playing, gameId);
  730. serversFound++;
  731.  
  732. if (serversFound >= 16) {
  733. break;
  734. }
  735. }
  736. }
  737.  
  738. if (serversFound <= 0) {
  739. notifications('No Servers Found Within The Provided Criteria', 'info', '🔎');
  740. }
  741. } catch (error) {
  742. console.error('[DEBUG] Error in filterServersByPlayerCount:', error);
  743. } finally {
  744. Loadingbar(false);
  745. disableFilterButton(false);
  746. }
  747. }
  748.  
  749. /*********************************************************************************************************************************************************************************************************************************************
  750. Functions for the 4th button
  751.  
  752. *********************************************************************************************************************************************************************************************************************************************/
  753.  
  754. /*******************************************************
  755. name of function: random_servers
  756. 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.
  757. *******************************************************/
  758. async function random_servers() {
  759. // Disable the "Load More" button and show the loading bar
  760. Loadingbar(true);
  761. disableFilterButton(true);
  762. disableLoadMoreButton();
  763.  
  764. // Get the game ID from the URL
  765. const gameId = window.location.pathname.split('/')[2];
  766.  
  767. try {
  768. // Fetch servers from the first URL with retry logic
  769. const firstUrl = `https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=10`;
  770. const firstData = await fetchWithRetry(firstUrl, 3); // Retry up to 3 times
  771.  
  772. // Wait for 5 seconds
  773. await delay(5000);
  774.  
  775. // Fetch servers from the second URL with retry logic
  776. const secondUrl = `https://games.roblox.com/v1/games/${gameId}/servers/public?sortOrder=1&excludeFullGames=true&limit=10`;
  777. const secondData = await fetchWithRetry(secondUrl, 3); // Retry up to 3 times
  778.  
  779. // Combine the servers from both URLs
  780. const combinedServers = [...firstData.data, ...secondData.data];
  781.  
  782. // Remove duplicates by server ID
  783. const uniqueServers = [];
  784. const seenServerIds = new Set();
  785.  
  786. for (const server of combinedServers) {
  787. if (!seenServerIds.has(server.id)) {
  788. seenServerIds.add(server.id);
  789. uniqueServers.push(server);
  790. }
  791. }
  792.  
  793. // Shuffle the unique servers array
  794. const shuffledServers = shuffleArray(uniqueServers);
  795.  
  796. // Get the first 16 shuffled servers
  797. const selectedServers = shuffledServers.slice(0, 16);
  798.  
  799. // Process each server in random order
  800. for (const server of selectedServers) {
  801. const {
  802. id: serverId,
  803. playerTokens,
  804. maxPlayers,
  805. playing
  806. } = server;
  807.  
  808. // Pass the server data to the card creation function
  809. await rbx_card(serverId, playerTokens, maxPlayers, playing, gameId);
  810. }
  811. } catch (error) {
  812. console.error('Error fetching server data:', error);
  813. notifications('Error: Failed to fetch server data. Please try again later.', 'error', '⚠️');
  814. } finally {
  815. // Hide the loading bar and enable the filter button
  816. Loadingbar(false);
  817. disableFilterButton(false);
  818. }
  819. }
  820.  
  821. /*******************************************************
  822. name of function: fetchWithRetry
  823. description: Fetches data from a URL with retry logic for 429 errors. this is for this unique function
  824. *******************************************************/
  825. async function fetchWithRetry(url, retries) {
  826. for (let i = 0; i < retries; i++) {
  827. try {
  828. const response = await fetch(url);
  829. if (response.status === 429) {
  830. // If 429 error, wait 10 seconds and retry
  831. console.log(`Rate limited. Retrying in 10 seconds... (Attempt ${i + 1}/${retries})`);
  832. await delay(10000); // Wait 10 seconds
  833. continue;
  834. }
  835. if (!response.ok) {
  836. throw new Error(`HTTP error: ${response.status}`);
  837. }
  838. return await response.json();
  839. } catch (error) {
  840. if (i === retries - 1) {
  841. // If no retries left, throw the error
  842. throw error;
  843. }
  844. }
  845. }
  846. }
  847.  
  848. /*******************************************************
  849. name of function: shuffleArray
  850. description: Shuffles an array using the Fisher-Yates algorithm.
  851. *******************************************************/
  852. function shuffleArray(array) {
  853. for (let i = array.length - 1; i > 0; i--) {
  854. const j = Math.floor(Math.random() * (i + 1)); // Random index from 0 to i
  855. [array[i], array[j]] = [array[j], array[i]]; // Swap elements
  856. }
  857. return array;
  858. }
  859.  
  860.  
  861. /*********************************************************************************************************************************************************************************************************************************************
  862. Functions for the 5th button. taken from my other project
  863.  
  864. *********************************************************************************************************************************************************************************************************************************************/
  865.  
  866. // so we inject css into the page. if ur on light mode some stuff may look weird so not my fault
  867. const style = document.createElement('style');
  868. style.textContent = `
  869. /* Overlay for the stupid thingy black screen*/
  870. .overlay {
  871. position: fixed;
  872. top: 0;
  873. left: 0;
  874. width: 100%;
  875. height: 100%;
  876. background-color: rgba(0, 0, 0, 0.7); /* Dark semi-transparent background */
  877. z-index: 1000; /* Ensure overlay is below the popup */
  878. }
  879.  
  880. /* Popup Container for the server region*/
  881. .filter-popup {
  882. background-color: #2d2d2d; /* Dark background */
  883. color: #ffffff; /* White text */
  884. padding: 20px;
  885. border-radius: 10px;
  886. box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
  887. width: 300px;
  888. max-width: 90%;
  889. position: fixed; /* Fixed positioning */
  890. top: 50%; /* Center vertically */
  891. left: 50%; /* Center horizontally */
  892. transform: translate(-50%, -50%); /* Offset to truly center */
  893. text-align: center;
  894. z-index: 1001; /* Ensure popup is above the overlay */
  895. }
  896.  
  897. /* Close Button for the server selector*/
  898. #closePopup {
  899. position: absolute;
  900. top: 10px;
  901. right: 10px;
  902. background: #ff4444; /* Red background */
  903. border: none;
  904. color: white;
  905. font-size: 16px;
  906. cursor: pointer;
  907. width: 24px;
  908. height: 24px;
  909. border-radius: 50%;
  910. display: flex;
  911. align-items: center;
  912. justify-content: center;
  913. }
  914.  
  915. #closePopup:hover {
  916. background: #cc0000; /* Darker red on hover */
  917. }
  918.  
  919. /* Label */
  920. .filter-popup label {
  921. display: block;
  922. margin-bottom: 10px;
  923. font-size: 16px;
  924. color: #ffffff;
  925. }
  926.  
  927. /* Dropdown */
  928. .filter-popup select {
  929. background-color: #444; /* Dark gray background */
  930. color: #ffffff; /* White text */
  931. padding: 8px;
  932. border-radius: 5px;
  933. border: 1px solid #666; /* Gray border */
  934. width: 100%;
  935. margin-bottom: 10px;
  936. font-size: 14px;
  937. }
  938.  
  939. .filter-popup select:focus {
  940. border-color: #888; /* Lighter border on focus */
  941. outline: none;
  942. }
  943.  
  944. /* Custom Input */
  945. .filter-popup input[type="number"] {
  946. background-color: #444; /* Dark gray background */
  947. color: #ffffff; /* White text */
  948. padding: 8px;
  949. border-radius: 5px;
  950. border: 1px solid #666; /* Gray border */
  951. width: 100%;
  952. margin-bottom: 10px;
  953. font-size: 14px;
  954. }
  955.  
  956. .filter-popup input[type="number"]:focus {
  957. border-color: #888; /* Lighter border on focus */
  958. outline: none;
  959. }
  960.  
  961. /* Confirm Button */
  962. #confirmServerCount {
  963. background-color: #444; /* Dark gray background */
  964. color: #ffffff; /* White text */
  965. padding: 8px 16px;
  966. border: 1px solid #666; /* Gray border */
  967. border-radius: 5px;
  968. cursor: pointer;
  969. font-size: 14px;
  970. width: 100%;
  971. transition: background-color 0.3s ease;
  972. }
  973.  
  974. #confirmServerCount:hover {
  975. background-color: #666; /* Lighter gray on hover */
  976. }
  977. .rbx-game-server-item.highlighted {
  978. border: 2px solid green;
  979. border-radius: 8px;
  980. }
  981. .fetch-button:disabled {
  982. opacity: 0.5;
  983. cursor: not-allowed;
  984. }
  985. `;
  986. document.head.appendChild(style);
  987.  
  988.  
  989. // Function to show the message under the "Load More" button
  990. function showMessage(message) {
  991. const loadMoreButtonContainer = document.querySelector('.rbx-running-games-footer');
  992.  
  993. if (!loadMoreButtonContainer) {
  994. console.error("Load More button container not found!");
  995. return;
  996. }
  997.  
  998. // Create the message element
  999. const messageElement = document.createElement('div');
  1000. messageElement.className = 'filter-message';
  1001. messageElement.textContent = message;
  1002.  
  1003. // Clear any existing message and append the new one
  1004. const existingMessage = loadMoreButtonContainer.querySelector('.filter-message');
  1005. if (existingMessage) {
  1006. existingMessage.remove(); // Remove the existing message if it exists
  1007. }
  1008.  
  1009. loadMoreButtonContainer.appendChild(messageElement);
  1010.  
  1011. return messageElement;
  1012. }
  1013.  
  1014. // Function to hide the message of the showmessage functioon
  1015. function hideMessage() {
  1016. const messageElement = document.querySelector('.filter-message');
  1017. if (messageElement) messageElement.remove();
  1018. }
  1019.  
  1020. // Function to show the popup for random stuff
  1021. function showPopup() {
  1022. const overlay = document.createElement('div');
  1023. overlay.className = 'overlay';
  1024.  
  1025. const popup = document.createElement('div');
  1026. popup.className = 'filter-popup';
  1027. popup.textContent = 'Filtering servers, please wait...';
  1028.  
  1029. document.body.appendChild(overlay);
  1030. document.body.appendChild(popup);
  1031.  
  1032. return popup;
  1033. }
  1034.  
  1035. // Function to hide the popup for the stuff
  1036. function hidePopup() {
  1037. const popup = document.querySelector('.filter-popup');
  1038. const overlay = document.querySelector('.overlay');
  1039.  
  1040. if (popup) popup.remove();
  1041. if (overlay) overlay.remove();
  1042. }
  1043.  
  1044. // Function to fetch server details so game id and job id. yea!
  1045. async function fetchServerDetails(gameId, jobId) {
  1046. return new Promise((resolve, reject) => {
  1047. GM_xmlhttpRequest({
  1048. method: "POST",
  1049. url: "https://gamejoin.roblox.com/v1/join-game-instance", // url for game id
  1050. headers: { // doesent need cookie cuase of magic
  1051. "Content-Type": "application/json",
  1052. "User-Agent": "Roblox/WinInet",
  1053. },
  1054. data: JSON.stringify({
  1055. placeId: gameId,
  1056. gameId: jobId
  1057. }),
  1058. onload: function(response) {
  1059. const json = JSON.parse(response.responseText);
  1060.  
  1061. console.log("API Response:", json); // This prints the full response
  1062.  
  1063. // Check if the response indicates that the user needs to purchase the game
  1064. if (json.status === 12 && json.message === 'You need to purchase access to this game before you can play.') { // yea error message!
  1065. reject('purchase_required'); // Special error code for this case yea!
  1066. return;
  1067. }
  1068.  
  1069. const address = json?.joinScript?.UdmuxEndpoints?.[0]?.Address ?? json?.joinScript?.MachineAddress;
  1070.  
  1071. if (!address) {
  1072. console.error("API Response (Unknown Location) Which means Full Server!:", json); // Log the API response for debug
  1073. reject(`Unable to fetch server location: Status ${json.status}`); // debug
  1074. return;
  1075. }
  1076.  
  1077. const location = serverRegionsByIp[address.replace(/^(128\.116\.\d+)\.\d+$/, "$1.0")]; // lmao all servers atart with this so yea dont argue with me
  1078.  
  1079. if (!location) {
  1080. console.error("API Response (Unknown Location):", json); // Log the API response into the chat. might remove it from production but idc rn
  1081. reject(`Unknown server address ${address}`);
  1082. return;
  1083. }
  1084.  
  1085. resolve(location);
  1086. },
  1087. onerror: function(error) {
  1088. console.error("API Request Failed:", error); // damn if this happpens idk what to tell u
  1089. reject(`Failed to fetch server details: ${error}`);
  1090. },
  1091. });
  1092. });
  1093. }
  1094.  
  1095. // cusomt delay also known as sleep fucntion in js cause this language sucks and doesent have a default function
  1096. function delay(ms) {
  1097. return new Promise(resolve => setTimeout(resolve, ms));
  1098. }
  1099.  
  1100. // Function to create a popup for selecting the number of servers
  1101. // basically yea thats what it doesent
  1102. function createServerCountPopup(callback) {
  1103. const overlay = document.createElement('div');
  1104. overlay.className = 'overlay';
  1105.  
  1106. const popup = document.createElement('div');
  1107. popup.className = 'filter-popup'; // reason 100 is selected because thjats how many the api will show per request
  1108. popup.innerHTML = `
  1109. <button id="closePopup">X</button>
  1110. <label for="serverCount">Enter the number of servers to search (higher values yield more location variety).</label>
  1111. <select id="serverCount">
  1112. <option value="10">10 Servers</option>
  1113. <option value="25">25 Servers</option>
  1114. <option value="50">50 Servers</option>
  1115. <option value="100" selected>100 Servers</option>
  1116. <option value="200">200 Servers</option>
  1117. <option value="500">500 Servers</option>
  1118. <option value="1000">1000 Servers</option>
  1119. <option value="custom">Custom</option>
  1120. </select>
  1121. <input id="customServerCount" type="number" min="1" max="1000" placeholder="Enter a number (1-1000)" style="display: none;">
  1122. <button id="confirmServerCount">Confirm</button>
  1123. `;
  1124.  
  1125. document.body.appendChild(overlay);
  1126. document.body.appendChild(popup);
  1127.  
  1128. const serverCountDropdown = popup.querySelector('#serverCount');
  1129. const customServerCountInput = popup.querySelector('#customServerCount');
  1130. const confirmButton = popup.querySelector('#confirmServerCount');
  1131. const closeButton = popup.querySelector('#closePopup');
  1132.  
  1133. // Show/hide custom input based on dropdown selection
  1134. serverCountDropdown.addEventListener('change', () => {
  1135. if (serverCountDropdown.value === 'custom') {
  1136. customServerCountInput.style.display = 'block';
  1137. } else {
  1138. customServerCountInput.style.display = 'none';
  1139. }
  1140. });
  1141.  
  1142. // button click on start or what ever
  1143. confirmButton.addEventListener('click', () => {
  1144. let serverCount;
  1145.  
  1146. if (serverCountDropdown.value === 'custom') {
  1147. serverCount = parseInt(customServerCountInput.value);
  1148.  
  1149. // Validate custom input
  1150. if (isNaN(serverCount) || serverCount < 1 || serverCount > 1000) {
  1151. notifications('Error: Please enter a valid number between 1 and 1000.', 'error', '⚠️')
  1152. return;
  1153. }
  1154. } else {
  1155. serverCount = parseInt(serverCountDropdown.value);
  1156. }
  1157.  
  1158. // Show an alert if the user selects a number above 100
  1159. if (serverCount > 100) { // error cause people dont know about this maybe. idk yea so here. also if u think this is a stupid way i should have done it before the button press idc so yea
  1160. notifications('Warning: Searching over 100 servers may take some time and you might get rate limited!', 'warning', '❗');
  1161. }
  1162.  
  1163. // Pass the selected server count to the callback
  1164. callback(serverCount);
  1165. disableFilterButton(true); // disbale filter button
  1166. disableLoadMoreButton(true); // disable load more button
  1167. notifications('Note: Filter Button is disabled as this function is resource intensive. \nRefresh the page to call other functions/press other buttons.', 'info', '⚠️')
  1168. hidePopup();
  1169. Loadingbar(true); // enable loading bar
  1170. });
  1171.  
  1172. // Close button logic :))
  1173. closeButton.addEventListener('click', () => {
  1174. hidePopup();
  1175. });
  1176.  
  1177. // Function to hide the popup
  1178. // yea im dumb and used the same function name but it works and im too lazy to change it
  1179. function hidePopup() {
  1180. document.body.removeChild(overlay);
  1181. document.body.removeChild(popup);
  1182. }
  1183. }
  1184.  
  1185. // Function to fetch public servers
  1186. // totallimit is amount of sevrers to fetch
  1187. async function fetchPublicServers(gameId, totalLimit) {
  1188. let servers = [];
  1189. let cursor = null;
  1190.  
  1191. while (servers.length < totalLimit) { // too lazy to comment any of this. hopefully i remember what this does in the future
  1192. const url = `https://games.roblox.com/v1/games/${gameId}/servers/public?excludeFullGames=true&limit=100${cursor ? `&cursor=${cursor}` : ''}`;
  1193.  
  1194. const response = await new Promise((resolve, reject) => {
  1195. GM_xmlhttpRequest({
  1196. method: "GET",
  1197. url: url,
  1198. onload: function(response) {
  1199. resolve(JSON.parse(response.responseText));
  1200. },
  1201. onerror: function(error) {
  1202. reject(`Failed to fetch public servers: ${error}`);
  1203. },
  1204. });
  1205. });
  1206.  
  1207. servers = servers.concat(response.data);
  1208.  
  1209. if (!response.nextPageCursor || servers.length >= totalLimit) {
  1210. break;
  1211. }
  1212.  
  1213. cursor = response.nextPageCursor;
  1214. await delay(3000); // wait 3 seconds before each page request. if u think this is slow i tried 1 second i got rate limited :|
  1215. }
  1216.  
  1217. return servers.slice(0, totalLimit);
  1218. }
  1219.  
  1220. // Function to create dropdown menus for filtering
  1221. function createFilterDropdowns(servers) {
  1222. const filterContainer = document.createElement('div');
  1223. filterContainer.className = 'filter-container';
  1224.  
  1225. const countryDropdown = document.createElement('select');
  1226. countryDropdown.id = 'countryFilter';
  1227. countryDropdown.innerHTML = '<option value="">All Countries</option>';
  1228. countryDropdown.style.backgroundColor = '#333'; // Dark gray background
  1229. countryDropdown.style.color = '#fff'; // White text
  1230. countryDropdown.style.borderRadius = '8px'; // Rounded corners
  1231. countryDropdown.style.padding = '8px'; // Increase size
  1232. countryDropdown.style.fontSize = '16px'; // Increase font size
  1233. countryDropdown.style.border = 'none'; // Remove default border
  1234.  
  1235. const cityDropdown = document.createElement('select');
  1236. cityDropdown.id = 'cityFilter';
  1237. cityDropdown.innerHTML = '<option value="">All Cities</option>';
  1238. cityDropdown.style.backgroundColor = '#333'; // Dark gray background
  1239. cityDropdown.style.color = '#fff'; // White text
  1240. cityDropdown.style.borderRadius = '8px'; // Rounded corners
  1241. cityDropdown.style.padding = '8px'; // Increase size hehehehe
  1242. cityDropdown.style.fontSize = '16px'; // Increase font size
  1243. cityDropdown.style.border = 'none'; // Remove default border
  1244. cityDropdown.style.marginLeft = '5px'; // move right cause im too lazy to fix
  1245.  
  1246. // Count the number of servers per country and add them to the dropdown
  1247. const countryCounts = {};
  1248. servers.forEach(server => {
  1249. const country = server.location.country.name;
  1250. countryCounts[country] = (countryCounts[country] || 0) + 1;
  1251. });
  1252.  
  1253. // Populate country dropdown with server counts
  1254. Object.keys(countryCounts).forEach(country => {
  1255. const option = document.createElement('option');
  1256. option.value = country;
  1257. option.textContent = `${country} (${countryCounts[country]})`;
  1258. countryDropdown.appendChild(option);
  1259. });
  1260.  
  1261. // add the city dropdown based on selected country
  1262. countryDropdown.addEventListener('change', () => {
  1263. const selectedCountry = countryDropdown.value;
  1264. cityDropdown.innerHTML = '<option value="">All Cities</option>';
  1265.  
  1266. if (selectedCountry) {
  1267. // Count the number of servers per city in the selected country
  1268. const cityCounts = {};
  1269. servers
  1270. .filter(server => server.location.country.name === selectedCountry)
  1271. .forEach(server => {
  1272. const city = server.location.city;
  1273. const region = server.location.region?.name;
  1274. const cityKey = region ? `${city}, ${region}` : city;
  1275. cityCounts[cityKey] = (cityCounts[cityKey] || 0) + 1;
  1276. });
  1277.  
  1278. // Populate city dropdown with server counts
  1279. Object.keys(cityCounts).forEach(city => {
  1280. const option = document.createElement('option');
  1281. option.value = city;
  1282. option.textContent = `${city} (${cityCounts[city]})`;
  1283. cityDropdown.appendChild(option);
  1284. });
  1285.  
  1286. // Auto-select the city if there's only one make users life easier
  1287. // wow ik i made the users life easier for once thats crazy!!! :OOOOO
  1288. const cities = Object.keys(cityCounts);
  1289. if (cities.length === 1) {
  1290. cityDropdown.value = cities[0];
  1291. // displayFilteredServers(selectedCountry, cities[0]); // if this breaks something which it doesent seem like it i will enable it later
  1292. }
  1293. }
  1294. });
  1295.  
  1296. filterContainer.appendChild(countryDropdown);
  1297. filterContainer.appendChild(cityDropdown);
  1298.  
  1299. return filterContainer;
  1300. }
  1301.  
  1302. // Function to filter servers based on selected country and city cause im lazy
  1303. function filterServers(servers, country, city) {
  1304. return servers.filter(server => {
  1305. const matchesCountry = !country || server.location.country.name === country;
  1306. const matchesCity = !city || `${server.location.city}${server.location.region?.name ? `, ${server.location.region.name}` : ''}` === city;
  1307. return matchesCountry && matchesCity;
  1308. });
  1309. }
  1310.  
  1311. // Function to sort servers by ping. maybe inaccurate but thats roblox's problem not mine
  1312. function sortServersByPing(servers) {
  1313. return servers.sort((a, b) => a.server.ping - b.server.ping);
  1314. }
  1315.  
  1316. async function fetchPlayerThumbnails_servers(playerTokens) {
  1317. const body = playerTokens.map(token => ({
  1318. requestId: `0:${token}:AvatarHeadshot:150x150:png:regular`,
  1319. type: "AvatarHeadShot",
  1320. targetId: 0,
  1321. token,
  1322. format: "png",
  1323. size: "150x150",
  1324. }));
  1325.  
  1326. const response = await fetch("https://thumbnails.roblox.com/v1/batch", {
  1327. method: "POST",
  1328. headers: {
  1329. "Content-Type": "application/json",
  1330. Accept: "application/json",
  1331. },
  1332. body: JSON.stringify(body),
  1333. });
  1334.  
  1335. const data = await response.json();
  1336. return data.data || [];
  1337. }
  1338.  
  1339. async function rebuildServerList(gameId, totalLimit, best_connection) {
  1340. const serverListContainer = document.getElementById("rbx-game-server-item-container");
  1341.  
  1342.  
  1343. // If "Best Connection" is enabled
  1344. // FUNCTION FOR THE 6TH BUTTON!
  1345. if (best_connection === true) {
  1346. // Ask for the user's location
  1347. const userLocation = await getUserLocation();
  1348. if (!userLocation) {
  1349. //notifications('Error: Unable to fetch your location. Please enable location access.', 'error', '⚠️');
  1350. return;
  1351. }
  1352.  
  1353. // Fetch 50 servers
  1354. const servers = await fetchPublicServers(gameId, 50);
  1355. if (servers.length === 0) {
  1356. notifications('Error: No servers found. Please try again later.', 'error', '⚠️');
  1357. return;
  1358. }
  1359.  
  1360. // Calculate distances and find the closest server
  1361. let closestServer = null;
  1362. let minDistance = Infinity;
  1363. let closestServerLocation = null;
  1364.  
  1365. for (const server of servers) {
  1366. const {
  1367. id: serverId,
  1368. maxPlayers,
  1369. playing
  1370. } = server;
  1371.  
  1372. // Skip full servers
  1373. if (playing >= maxPlayers) {
  1374. continue;
  1375. }
  1376.  
  1377. try {
  1378. // Fetch server location
  1379. const location = await fetchServerDetails(gameId, serverId);
  1380.  
  1381. // Calculate distance
  1382. const distance = calculateDistance(
  1383. userLocation.latitude,
  1384. userLocation.longitude,
  1385. location.latitude,
  1386. location.longitude
  1387. );
  1388.  
  1389. // Update closest server
  1390. if (distance < minDistance) {
  1391. minDistance = distance;
  1392. closestServer = server;
  1393. closestServerLocation = location;
  1394. }
  1395. } catch (error) {
  1396. console.error(`Error fetching details for server ${serverId}:`, error);
  1397. // Skip this server and continue with the next one
  1398. continue;
  1399. }
  1400. }
  1401.  
  1402. if (closestServer) {
  1403. // Automatically join the closest server
  1404. Roblox.GameLauncher.joinGameInstance(gameId, closestServer.id);
  1405. notifications(`Joining nearest server!
  1406. Server ID: ${closestServer.id}
  1407. Distance: ${(minDistance / 1.609).toFixed(2)} miles | ${minDistance.toFixed(2)} km
  1408. Location (Country): ${closestServerLocation.country.name}.`, 'success', '🚀');
  1409.  
  1410. disableFilterButton(false);
  1411. Loadingbar(false);
  1412. } else {
  1413. notifications('No valid servers found. Please try again later after refreshing the webpage.', 'error', '⚠️');
  1414. }
  1415.  
  1416. return; // Exit the function after joining the best server
  1417. }
  1418.  
  1419. // Rest of the original function (for non-"Best Connection" mode)
  1420. if (!serverListContainer) {
  1421. console.error("Server list container not found!");
  1422. const popup = showPopup();
  1423. notifications('Error: No Servers found. There is nobody playing this game. :(', 'warning', '❗');
  1424. return;
  1425. }
  1426.  
  1427. const messageElement = showMessage("Filtering servers, please wait...");
  1428.  
  1429. try {
  1430. const servers = await fetchPublicServers(gameId, totalLimit);
  1431. const totalServers = servers.length;
  1432. let skippedServers = 0;
  1433.  
  1434. messageElement.textContent = `Filtering servers, please do not leave this page as it slows down the search...\n${totalServers} servers found, 0 servers loaded.`;
  1435. notifications(`Please do not leave this page as it slows down the search. \nFound a total of ${totalServers} servers found.`, 'success', '👍');
  1436.  
  1437. const serverDetails = [];
  1438. for (let i = 0; i < servers.length; i++) {
  1439. const server = servers[i];
  1440. const {
  1441. id: serverId,
  1442. maxPlayers,
  1443. playing,
  1444. ping,
  1445. fps,
  1446. playerTokens
  1447. } = server;
  1448.  
  1449. let location;
  1450. try {
  1451. location = await fetchServerDetails(gameId, serverId);
  1452. } catch (error) {
  1453. if (error === 'purchase_required') {
  1454. messageElement.textContent = "Cannot access server data because you haven't purchased the game.";
  1455. notifications('Error: Cannot access server data because you haven\'t purchased the game.', 'error', '⚠️');
  1456. Loadingbar(false); // disable loading bar
  1457. return;
  1458. } else {
  1459. console.error(error);
  1460. location = {
  1461. city: "Unknown",
  1462. country: {
  1463. name: "Unknown",
  1464. code: "??"
  1465. }
  1466. };
  1467. }
  1468. }
  1469.  
  1470. if (location.city === "Unknown" || playing >= maxPlayers) {
  1471. console.log(`Skipping server ${serverId} because it is full or location is unknown.`);
  1472. skippedServers++;
  1473. continue;
  1474. }
  1475.  
  1476. // Fetch player thumbnails
  1477. const playerThumbnails = playerTokens && playerTokens.length > 0 ? await fetchPlayerThumbnails_servers(playerTokens) : [];
  1478.  
  1479. serverDetails.push({
  1480. server,
  1481. location,
  1482. playerThumbnails
  1483. });
  1484.  
  1485. messageElement.textContent = `Filtering servers, please do not leave this page...\n${totalServers} servers found, ${i + 1} server locations found`;
  1486. }
  1487.  
  1488. if (serverDetails.length === 0) {
  1489. messageElement.textContent = "No servers found. Please try again with an increase in the number of servers to search for.";
  1490. notifications('Error: No servers found. Please try again with an increase in the number of servers to search for.', 'error', '⚠️');
  1491. Loadingbar(false); // disable loading bar
  1492. return;
  1493. }
  1494.  
  1495. const loadedServers = totalServers - skippedServers;
  1496. notifications(`Filtering complete!\n${totalServers} servers found, ${loadedServers} servers loaded, ${skippedServers} servers skipped (full).`, 'success', '👍');
  1497. messageElement.textContent = `Filtering complete!\n${totalServers} servers found, ${loadedServers} servers loaded, ${skippedServers} servers skipped (full).`;
  1498. Loadingbar(false); // disable loading bar
  1499.  
  1500. // Add filter dropdowns
  1501. const filterContainer = createFilterDropdowns(serverDetails);
  1502. serverListContainer.parentNode.insertBefore(filterContainer, serverListContainer);
  1503.  
  1504. // Style the server list container to use a grid layout
  1505. serverListContainer.style.display = "grid";
  1506. serverListContainer.style.gridTemplateColumns = "repeat(4, 1fr)"; // 4 columns
  1507. serverListContainer.style.gap = "16px"; // Gap between cards
  1508.  
  1509. const displayFilteredServers = (country, city) => {
  1510. serverListContainer.innerHTML = "";
  1511.  
  1512. const filteredServers = filterServers(serverDetails, country, city);
  1513. const sortedServers = sortServersByPing(filteredServers);
  1514.  
  1515. sortedServers.forEach(({
  1516. server,
  1517. location,
  1518. playerThumbnails
  1519. }) => {
  1520. const serverCard = document.createElement("li");
  1521. serverCard.className = "rbx-game-server-item col-md-3 col-sm-4 col-xs-6";
  1522.  
  1523. // Set consistent width and height for the server card
  1524. serverCard.style.width = "100%"; // Take up full width of the grid cell
  1525. serverCard.style.minHeight = "400px"; // Set a minimum height
  1526. serverCard.style.display = "flex";
  1527. serverCard.style.flexDirection = "column";
  1528. serverCard.style.justifyContent = "space-between";
  1529. serverCard.style.boxSizing = "border-box"; // Include padding and border in dimensions
  1530.  
  1531. // Remove any conflicting outline (e.g., from .highlighted class)
  1532. serverCard.style.outline = 'none';
  1533.  
  1534. // Determine the group and set the outline color
  1535. let outlineColor;
  1536. if (server.ping < 100) {
  1537. outlineColor = 'green'; // Best ping
  1538. } else if (server.ping < 200) {
  1539. outlineColor = 'orange'; // Medium ping
  1540. } else {
  1541. outlineColor = 'red'; // Bad ping
  1542. }
  1543.  
  1544. // Apply the new outline and outlineOffset
  1545. serverCard.style.outline = `3px solid ${outlineColor}`;
  1546. serverCard.style.outlineOffset = '-6px';
  1547. serverCard.style.padding = '6px';
  1548. serverCard.style.borderRadius = '8px';
  1549.  
  1550. // Create a container for player thumbnails
  1551. const thumbnailsContainer = document.createElement("div");
  1552. thumbnailsContainer.className = "player-thumbnails-container";
  1553. thumbnailsContainer.style.display = "grid";
  1554. thumbnailsContainer.style.gridTemplateColumns = "repeat(3, 60px)"; // 3 columns
  1555. thumbnailsContainer.style.gridTemplateRows = "repeat(2, 60px)"; // 2 rows
  1556. thumbnailsContainer.style.gap = "5px";
  1557. thumbnailsContainer.style.marginBottom = "10px";
  1558.  
  1559. // Add player thumbnails to the container (max 5)
  1560. const maxThumbnails = 5;
  1561. const displayedThumbnails = playerThumbnails.slice(0, maxThumbnails);
  1562. displayedThumbnails.forEach(thumb => {
  1563. if (thumb && thumb.imageUrl) {
  1564. const img = document.createElement("img");
  1565. img.src = thumb.imageUrl;
  1566. img.className = "avatar-card-image";
  1567. img.style.width = "60px";
  1568. img.style.height = "60px";
  1569. img.style.borderRadius = "50%";
  1570. thumbnailsContainer.appendChild(img);
  1571. }
  1572. });
  1573.  
  1574. // Add a placeholder for hidden players
  1575. const hiddenPlayers = server.playing - displayedThumbnails.length;
  1576. if (hiddenPlayers > 0) {
  1577. const placeholder = document.createElement("div");
  1578. placeholder.className = "avatar-card-image";
  1579. placeholder.style.width = "60px";
  1580. placeholder.style.height = "60px";
  1581. placeholder.style.borderRadius = "50%";
  1582. placeholder.style.backgroundColor = "#BDBEBE80"; // Dark gray background
  1583. placeholder.style.display = "flex";
  1584. placeholder.style.alignItems = "center";
  1585. placeholder.style.justifyContent = "center";
  1586. placeholder.style.color = "#fff"; // White text
  1587. placeholder.style.fontSize = "14px";
  1588. placeholder.textContent = `+${hiddenPlayers}`;
  1589. thumbnailsContainer.appendChild(placeholder);
  1590. }
  1591.  
  1592. // Server card content
  1593. const cardItem = document.createElement("div");
  1594. cardItem.className = "card-item";
  1595. cardItem.style.display = "flex";
  1596. cardItem.style.flexDirection = "column";
  1597. cardItem.style.justifyContent = "space-between";
  1598. cardItem.style.height = "100%"; // Ensure the card content takes up the full height
  1599.  
  1600. cardItem.innerHTML = `
  1601. <!-- Player thumbnails at the top -->
  1602. ${thumbnailsContainer.outerHTML}
  1603. <div class="rbx-game-server-details game-server-details">
  1604. <div class="text-info rbx-game-status rbx-game-server-status text-overflow">
  1605. ${server.playing} of ${server.maxPlayers} people max
  1606. </div>
  1607. <div class="server-player-count-gauge border">
  1608. <div class="gauge-inner-bar border" style="width: ${(server.playing / server.maxPlayers) * 100}%;"></div>
  1609. </div>
  1610. <span data-placeid="${gameId}">
  1611. <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>
  1612. </span>
  1613. </div>
  1614. <!-- Generated info (ping, location, FPS) at the bottom -->
  1615. <div style="margin-top: 10px; text-align: center;">
  1616. <div class="ping-info">Ping: ${server.ping}ms</div>
  1617. <div class="location-info">${location.city}, ${location.country.name}</div>
  1618. <div class="fps-info">FPS: ${Math.round(server.fps)}</div>
  1619. </div>
  1620. `;
  1621.  
  1622. const joinButton = cardItem.querySelector(".rbx-game-server-join");
  1623. joinButton.addEventListener("click", () => {
  1624. console.log(`Roblox.GameLauncher.joinGameInstance(${gameId}, "${server.id}")`);
  1625. Roblox.GameLauncher.joinGameInstance(gameId, server.id); // join server
  1626. });
  1627.  
  1628. const container = adjustJoinButtonContainer(joinButton);
  1629. const inviteButton = createInviteButton(gameId, server.id);
  1630. container.appendChild(inviteButton);
  1631.  
  1632. serverCard.appendChild(cardItem);
  1633. serverListContainer.appendChild(serverCard);
  1634. });
  1635. };
  1636.  
  1637. // Add event listeners to dropdowns
  1638. const countryFilter = document.getElementById('countryFilter');
  1639. const cityFilter = document.getElementById('cityFilter');
  1640.  
  1641. countryFilter.addEventListener('change', () => {
  1642. displayFilteredServers(countryFilter.value, cityFilter.value);
  1643. });
  1644.  
  1645. cityFilter.addEventListener('change', () => {
  1646. displayFilteredServers(countryFilter.value, cityFilter.value);
  1647. });
  1648.  
  1649. // Display all servers initially
  1650. displayFilteredServers("", "");
  1651.  
  1652. setTimeout(() => {
  1653. hideMessage();
  1654. }, 3000);
  1655. } catch (error) {
  1656. console.error("Error rebuilding server list:", error);
  1657. notifications('An error occurred while filtering servers. Please try again.', 'error', '😔');
  1658. messageElement.textContent = "An error occurred while filtering servers. Please try again.";
  1659. Loadingbar(false); // enable loading bar
  1660. } finally {
  1661. 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
  1662. }
  1663. }
  1664.  
  1665. // Function to extract the game ID from the URL
  1666. function extractGameId() {
  1667. const url = window.location.href;
  1668. const match = url.match(/roblox\.com\/games\/(\d+)/);
  1669.  
  1670. if (match && match[1]) {
  1671. return match[1]; // Return the game ID
  1672. }
  1673. return null; // Return null if no game ID is found
  1674. }
  1675.  
  1676. // Log the game ID to the console
  1677. const gameId = extractGameId();
  1678.  
  1679. // Function to create and append the Invite button
  1680. function createInviteButton(placeId, serverId) { // too lazy to comment this function tbh just ready the name
  1681. const inviteButton = document.createElement('button');
  1682. inviteButton.textContent = 'Invite';
  1683. inviteButton.className = 'btn-control-xs btn-primary-md btn-min-width btn-full-width';
  1684. inviteButton.style.width = '25%';
  1685. inviteButton.style.marginLeft = '5px';
  1686.  
  1687. inviteButton.style.padding = '4px 8px';
  1688. inviteButton.style.fontSize = '12px';
  1689. inviteButton.style.borderRadius = '8px';
  1690. inviteButton.style.backgroundColor = '#393b3d';
  1691. inviteButton.style.borderColor = '#bdbebe';
  1692. inviteButton.style.color = '#bdbebe';
  1693. inviteButton.style.cursor = 'pointer';
  1694. inviteButton.style.fontWeight = '500';
  1695. inviteButton.style.textAlign = 'center';
  1696. inviteButton.style.whiteSpace = 'nowrap';
  1697. inviteButton.style.verticalAlign = 'middle';
  1698. inviteButton.style.lineHeight = '100%';
  1699. inviteButton.style.fontFamily = 'Builder Sans, Helvetica Neue, Helvetica, Arial, Lucida Grande, sans-serif';
  1700. inviteButton.style.textRendering = 'auto';
  1701. inviteButton.style.webkitFontSmoothing = 'antialiased';
  1702. inviteButton.style.mozOsxFontSmoothing = 'grayscale';
  1703.  
  1704. inviteButton.addEventListener('mouseenter', () => {
  1705. inviteButton.style.color = '#ffffff';
  1706. inviteButton.style.borderColor = '#ffffff';
  1707. });
  1708. inviteButton.addEventListener('mouseleave', () => {
  1709. inviteButton.style.color = '#bdbebe';
  1710. inviteButton.style.borderColor = '#bdbebe';
  1711. });
  1712.  
  1713. inviteButton.addEventListener('click', () => {
  1714. const inviteLink = `https://oqarshi.github.io/Invite/?placeid=${placeId}&serverid=${serverId}`;
  1715. navigator.clipboard.writeText(inviteLink).then(() => {
  1716. console.log(`Invite link copied to clipboard: ${inviteLink}`);
  1717. notifications('Success! Invite link copied to clipboard!', 'success', '🎉');
  1718. }).catch(() => {
  1719. console.error('Failed to copy invite link.');
  1720. notifications('Error: Failed to copy invite link', 'error', '😔');
  1721. });
  1722. });
  1723.  
  1724. return inviteButton;
  1725. }
  1726.  
  1727. // Function to adjust the Join button and its container
  1728. function adjustJoinButtonContainer(joinButton) {
  1729. const container = document.createElement('div');
  1730. container.style.display = 'flex';
  1731. container.style.width = '100%';
  1732.  
  1733. joinButton.style.width = '75%';
  1734.  
  1735. joinButton.parentNode.insertBefore(container, joinButton);
  1736. container.appendChild(joinButton);
  1737.  
  1738. return container;
  1739. }
  1740.  
  1741.  
  1742.  
  1743.  
  1744. /*********************************************************************************************************************************************************************************************************************************************
  1745. Functions for the 6th button.
  1746.  
  1747. *********************************************************************************************************************************************************************************************************************************************/
  1748.  
  1749.  
  1750.  
  1751.  
  1752. function calculateDistance(lat1, lon1, lat2, lon2) {
  1753. const R = 6371; // Radius of the Earth in kilometers
  1754. const dLat = (lat2 - lat1) * (Math.PI / 180);
  1755. const dLon = (lon2 - lon1) * (Math.PI / 180);
  1756. const a =
  1757. Math.sin(dLat / 2) * Math.sin(dLat / 2) +
  1758. Math.cos(lat1 * (Math.PI / 180)) * Math.cos(lat2 * (Math.PI / 180)) *
  1759. Math.sin(dLon / 2) * Math.sin(dLon / 2);
  1760. const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  1761. return R * c; // Distance in kilometers
  1762. }
  1763.  
  1764.  
  1765. function getUserLocation() {
  1766. return new Promise((resolve, reject) => {
  1767. if (navigator.geolocation) {
  1768. navigator.geolocation.getCurrentPosition(
  1769. (position) => {
  1770. notifications('We successfully detected your location.\nConnecting you to a nearby server in about 15-20 seconds. 😊', 'success', '🌎');
  1771. disableLoadMoreButton(true);
  1772. disableFilterButton(true);
  1773. Loadingbar(true);
  1774. resolve({
  1775. latitude: position.coords.latitude,
  1776. longitude: position.coords.longitude,
  1777. });
  1778. },
  1779. (error) => {
  1780. console.error('Error getting user location:', error);
  1781. disableLoadMoreButton(true);
  1782. disableFilterButton(true);
  1783. Loadingbar(true);
  1784. notifications('Error getting user location.\nPlease enable location permissions for this website.\nAssuming your location is New York in the United States.', 'error', '⚠️');
  1785. // Fallback to a default location (e.g., New York City)
  1786. resolve({
  1787. latitude: 40.7128, // Default latitude (New York City)
  1788. longitude: -74.0060, // Default longitude (New York City)
  1789. });
  1790. }
  1791. );
  1792. } else {
  1793. console.error('Geolocation is not supported by this browser.');
  1794. disableLoadMoreButton(true);
  1795. disableFilterButton(true);
  1796. Loadingbar(true);
  1797. notifications('Error getting user location.\nThis browser doesent support location.\nAssuming your location is New York in the United States.', 'error', '⚠️');
  1798. // Fallback to a default location (e.g., New York City)
  1799. resolve({
  1800. latitude: 40.7128, // Default latitude (New York City)
  1801. longitude: -74.0060, // Default longitude (New York City)
  1802. });
  1803. }
  1804. });
  1805. }
  1806.  
  1807.  
  1808.  
  1809. /*********************************************************************************************************************************************************************************************************************************************
  1810. Functions for the 7th button.
  1811.  
  1812. *********************************************************************************************************************************************************************************************************************************************/
  1813. async function auto_join_small_server() {
  1814. // Disable the "Load More" button and show the loading bar
  1815. Loadingbar(true);
  1816. disableFilterButton(true);
  1817. disableLoadMoreButton();
  1818.  
  1819. // Get the game ID from the URL
  1820. const gameId = window.location.pathname.split('/')[2];
  1821.  
  1822. // Retry mechanism for 429 errors
  1823. let retries = 3; // Number of retries
  1824. let success = false;
  1825.  
  1826. while (retries > 0 && !success) {
  1827. try {
  1828. // Fetch server data using GM_xmlhttpRequest
  1829. const data = await new Promise((resolve, reject) => {
  1830. GM_xmlhttpRequest({
  1831. method: "GET",
  1832. url: `https://games.roblox.com/v1/games/${gameId}/servers/public?sortOrder=1&excludeFullGames=true&limit=100`,
  1833. onload: function(response) {
  1834. if (response.status === 429) {
  1835. reject('429: Too Many Requests');
  1836. } else if (response.status >= 200 && response.status < 300) {
  1837. resolve(JSON.parse(response.responseText));
  1838. } else {
  1839. reject(`HTTP error: ${response.status}`);
  1840. }
  1841. },
  1842. onerror: function(error) {
  1843. reject(error);
  1844. },
  1845. });
  1846. });
  1847.  
  1848. // Find the server with the lowest player count
  1849. let minPlayers = Infinity;
  1850. let targetServer = null;
  1851.  
  1852. for (const server of data.data) {
  1853. if (server.playing < minPlayers) {
  1854. minPlayers = server.playing;
  1855. targetServer = server;
  1856. }
  1857. }
  1858.  
  1859. if (targetServer) {
  1860. // Join the server with the lowest player count
  1861. Roblox.GameLauncher.joinGameInstance(gameId, targetServer.id);
  1862. notifications(`Joining a server with ${targetServer.playing} player(s).`, 'success', '🚀');
  1863. success = true; // Mark as successful
  1864. } else {
  1865. notifications('No available servers found.', 'error', '⚠️');
  1866. break; // Exit the loop if no servers are found
  1867. }
  1868. } catch (error) {
  1869. if (error === '429: Too Many Requests' && retries > 0) {
  1870. console.log('Rate limited. Retrying in 10 seconds...');
  1871. notifications('Rate limited. Retrying in 10 seconds...', 'warning', '⏳');
  1872. await delay(10000); // Wait 10 seconds before retrying
  1873. retries--;
  1874. } else {
  1875. console.error('Error fetching server data:', error);
  1876. notifications('Error: Failed to fetch server data. Please try again later.', 'error', '⚠️');
  1877. break; // Exit the loop if it's not a 429 error or no retries left
  1878. }
  1879. }
  1880. }
  1881.  
  1882. // Hide the loading bar and enable the filter button
  1883. Loadingbar(false);
  1884. disableFilterButton(false);
  1885. }
  1886.  
  1887.  
  1888. /*********************************************************************************************************************************************************************************************************************************************
  1889. Functions for the 8th button.
  1890.  
  1891. *********************************************************************************************************************************************************************************************************************************************/
  1892.  
  1893. function credits() {
  1894. // Inject CSS for the popup
  1895. const css = `
  1896. .credits-popup {
  1897. display: flex;
  1898. position: fixed;
  1899. left: 0;
  1900. top: 0;
  1901. width: 100%;
  1902. height: 100%;
  1903. background-color: rgba(0, 0, 0, 0.8);
  1904. justify-content: center;
  1905. align-items: center;
  1906. z-index: 1000;
  1907. opacity: 0;
  1908. animation: fadeIn 0.5s ease-in-out forwards;
  1909. }
  1910. .credits-popup-content {
  1911. background-color: #000;
  1912. padding: 20px;
  1913. border-radius: 10px;
  1914. width: 300px;
  1915. box-shadow: 0 5px 15px rgba(255, 255, 255, 0.1);
  1916. text-align: center;
  1917. position: relative;
  1918. color: #fff;
  1919. }
  1920. .credits-popup-content h2 {
  1921. margin-top: 0;
  1922. color: #fff;
  1923. }
  1924. .credits-popup-content .version {
  1925. font-size: 14px;
  1926. color: #aaa;
  1927. margin-bottom: 10px;
  1928. }
  1929. .credits-popup-content ul {
  1930. list-style-type: none;
  1931. padding: 0;
  1932. }
  1933. .credits-popup-content ul li {
  1934. margin: 10px 0;
  1935. color: #bbb;
  1936. }
  1937. .credits-popup-content a {
  1938. color: #4da6ff;
  1939. text-decoration: none;
  1940. }
  1941. .credits-popup-content a:hover {
  1942. text-decoration: underline;
  1943. }
  1944. .credits-popup-close {
  1945. position: absolute;
  1946. top: 10px;
  1947. right: 10px;
  1948. font-size: 24px;
  1949. font-weight: bold;
  1950. cursor: pointer;
  1951. color: #fff;
  1952. }
  1953. .credits-popup-close:hover {
  1954. color: #ccc;
  1955. }
  1956. @keyframes fadeIn {
  1957. from { opacity: 0; }
  1958. to { opacity: 1; }
  1959. }
  1960. @keyframes fadeOut {
  1961. from { opacity: 1; }
  1962. to { opacity: 0; }
  1963. }
  1964. `;
  1965.  
  1966. // Add CSS to the document
  1967. const style = document.createElement('style');
  1968. style.type = 'text/css';
  1969. style.innerHTML = css;
  1970. document.head.appendChild(style);
  1971.  
  1972. // Create the popup HTML
  1973. const popupHTML = `
  1974. <div class="credits-popup">
  1975. <div class="credits-popup-content">
  1976. <span class="credits-popup-close">&times;</span>
  1977. <div class="version">Rolocate: Version 21.3</div>
  1978. <h2>Credits</h2>
  1979. <p>This project was created by:</p>
  1980. <ul>
  1981. <li>Developer: <a href="https://www.roblox.com/users/545334824/profile" target="_blank">Oqarshi</a></li>
  1982. <li>Special Thanks: <a href="https://chromewebstore.google.com/detail/btroblox-making-roblox-be/hbkpclpemjeibhioopcebchdmohaieln" target="_blank">Btroblox Team ❤️</a></li>
  1983. <li>Source Code for Roblox Locate is available at <a href="https://greasyfork.org/en/scripts/522164-roblox-locate" target="_blank">GreasyFork</a></li>
  1984. <li>Source Code for the invite feature is available at <a href="https://github.com/Oqarshi/Invite" target="_blank">Github</a></li>
  1985. </ul>
  1986. </div>
  1987. </div>
  1988. `;
  1989.  
  1990. // Add the popup to the document
  1991. const popupContainer = document.createElement('div');
  1992. popupContainer.innerHTML = popupHTML;
  1993. document.body.appendChild(popupContainer);
  1994.  
  1995. // Add event listener to close the popup with animation
  1996. const closeButton = document.querySelector('.credits-popup-close');
  1997. const popup = document.querySelector('.credits-popup');
  1998. closeButton.addEventListener('click', () => {
  1999. popup.style.animation = 'fadeOut 0.5s ease-in-out forwards';
  2000. setTimeout(() => {
  2001. popup.remove(); // Remove the popup from the DOM after animation
  2002. }, 500); // Match the duration of the fadeOut animation
  2003. });
  2004. }
  2005.  
  2006. /*********************************************************************************************************************************************************************************************************************************************
  2007. End of: This is all the functions for the 8 buttons
  2008.  
  2009. *********************************************************************************************************************************************************************************************************************************************/
  2010.  
  2011.  
  2012.  
  2013. /*********************************************************************************************************************************************************************************************************************************************
  2014. The Universal Functions
  2015.  
  2016. *********************************************************************************************************************************************************************************************************************************************/
  2017.  
  2018.  
  2019. /*******************************************************
  2020. name of function: disableLoadMoreButton
  2021. description: Disables the "Load More" button
  2022. *******************************************************/
  2023. function disableLoadMoreButton() {
  2024. const loadMoreButton = document.querySelector('.rbx-running-games-load-more');
  2025. if (loadMoreButton) {
  2026. loadMoreButton.disabled = true;
  2027. loadMoreButton.style.opacity = '0.5'; // Optional: Make the button look disabled
  2028. loadMoreButton.style.cursor = 'not-allowed'; // Optional: Change cursor to indicate disabled state
  2029. loadMoreButton.title = 'Disabled by Roblox Locator'; // Set tooltip text
  2030. } else {
  2031. console.warn('Load More button not found!');
  2032. }
  2033. }
  2034.  
  2035.  
  2036.  
  2037. /*******************************************************
  2038. name of function: Loadingbar
  2039. description: Shows or hides a loading bar
  2040. *******************************************************/
  2041. function Loadingbar(disable) {
  2042. const serverListSection = document.querySelector('#rbx-running-games');
  2043. const serverCardsContainer = document.querySelector('#rbx-game-server-item-container');
  2044.  
  2045. if (disable) {
  2046. // Remove server cards and disable the "Load More" button
  2047. serverCardsContainer.innerHTML = '';
  2048.  
  2049. // Create and display the loading bar
  2050. const loadingBar = document.createElement('div');
  2051. loadingBar.id = 'loading-bar';
  2052. loadingBar.style.cssText = `
  2053. width: 100%;
  2054. height: 4px;
  2055. background-color: #1E1E1E;
  2056. position: relative;
  2057. overflow: hidden;
  2058. border-radius: 2px;
  2059. margin-top: 10px;
  2060. `;
  2061.  
  2062. const loadingBarInner = document.createElement('div');
  2063. loadingBarInner.style.cssText = `
  2064. width: 50%;
  2065. height: 100%;
  2066. background-color: #00A2FF;
  2067. position: absolute;
  2068. animation: loading 1.5s infinite ease-in-out;
  2069. border-radius: 2px;
  2070. `;
  2071.  
  2072. // Add animation keyframes
  2073. const styleSheet = document.createElement('style');
  2074. styleSheet.textContent = `
  2075. @keyframes loading {
  2076. 0% { left: -50%; }
  2077. 100% { left: 100%; }
  2078. }
  2079. `;
  2080. document.head.appendChild(styleSheet);
  2081.  
  2082. loadingBar.appendChild(loadingBarInner);
  2083. serverListSection.appendChild(loadingBar);
  2084. } else {
  2085. // Remove the loading bar
  2086. const loadingBar = document.querySelector('#loading-bar');
  2087. if (loadingBar) {
  2088. loadingBar.remove();
  2089. }
  2090. }
  2091. }
  2092.  
  2093.  
  2094.  
  2095. /*******************************************************
  2096. name of function: fetchPlayerThumbnails
  2097. description: Fetches player thumbnails for up to 5 players. Skips the batch if an error occurs.
  2098. *******************************************************/
  2099. async function fetchPlayerThumbnails(playerTokens) {
  2100. // Limit to the first 5 player tokens
  2101. const limitedTokens = playerTokens.slice(0, 5);
  2102.  
  2103. const body = limitedTokens.map(token => ({
  2104. requestId: `0:${token}:AvatarHeadshot:150x150:png:regular`,
  2105. type: "AvatarHeadShot",
  2106. targetId: 0,
  2107. token,
  2108. format: "png",
  2109. size: "150x150",
  2110. }));
  2111.  
  2112. try {
  2113. const response = await fetch("https://thumbnails.roblox.com/v1/batch", {
  2114. method: "POST",
  2115. headers: {
  2116. "Content-Type": "application/json",
  2117. Accept: "application/json",
  2118. },
  2119. body: JSON.stringify(body),
  2120. });
  2121.  
  2122. // Check if the response is successful
  2123. if (!response.ok) {
  2124. throw new Error(`HTTP error! Status: ${response.status}`);
  2125. }
  2126.  
  2127. const data = await response.json();
  2128. return data.data || []; // Return the data or an empty array if no data is present
  2129. } catch (error) {
  2130. console.error('Error fetching player thumbnails:', error);
  2131. return []; // Return an empty array if an error occurs
  2132. }
  2133. }
  2134.  
  2135.  
  2136. /*******************************************************
  2137. name of function: disableFilterButton
  2138. description: Disables or enables the filter button based on the input.
  2139. *******************************************************/
  2140. function disableFilterButton(disable) {
  2141. const filterButton = document.querySelector('.RL-filter-button');
  2142. const overlayId = 'filter-button-overlay';
  2143.  
  2144. if (filterButton) {
  2145. const parent = filterButton.parentElement;
  2146.  
  2147. if (disable) {
  2148. // Disable the filter button with an overlay
  2149. filterButton.disabled = true;
  2150. filterButton.style.opacity = '0.5'; // Make the button look disabled
  2151. filterButton.style.cursor = 'not-allowed'; // Change cursor to indicate disabled state
  2152.  
  2153. // Create an overlay if it doesn't exist
  2154. let overlay = document.getElementById(overlayId);
  2155. if (!overlay) {
  2156. overlay = document.createElement('div');
  2157. overlay.id = overlayId;
  2158. overlay.style.position = 'absolute';
  2159. overlay.style.top = '0';
  2160. overlay.style.left = '0';
  2161. overlay.style.width = '100%';
  2162. overlay.style.height = '100%';
  2163. overlay.style.backgroundColor = 'transparent'; // Transparent to maintain UI
  2164. overlay.style.zIndex = '9999'; // High z-index to cover the button
  2165. overlay.style.pointerEvents = 'all'; // Block all interactions
  2166. parent.style.position = 'relative'; // Ensure parent is positioned for absolute overlay
  2167. parent.appendChild(overlay);
  2168. }
  2169. } else {
  2170. // Enable the filter button
  2171. filterButton.disabled = false;
  2172. filterButton.style.opacity = '1'; // Restore opacity
  2173. filterButton.style.cursor = 'pointer'; // Restore cursor
  2174.  
  2175. // Remove the overlay if it exists
  2176. const overlay = document.getElementById(overlayId);
  2177. if (overlay) {
  2178. overlay.remove();
  2179. }
  2180. }
  2181. } else {
  2182. console.warn('Filter button not found!');
  2183. }
  2184. }
  2185.  
  2186.  
  2187.  
  2188. async function rbx_card(serverId, playerTokens, maxPlayers, playing, gameId) {
  2189. // Fetch player thumbnails (up to 5)
  2190. const thumbnails = await fetchPlayerThumbnails(playerTokens);
  2191.  
  2192. // Create the server card container
  2193. const cardItem = document.createElement('li');
  2194. cardItem.className = 'rbx-game-server-item col-md-3 col-sm-4 col-xs-6';
  2195.  
  2196. // Create the player thumbnails container
  2197. const playerThumbnailsContainer = document.createElement('div');
  2198. playerThumbnailsContainer.className = 'player-thumbnails-container';
  2199.  
  2200. // Add player thumbnails to the container (up to 5)
  2201. thumbnails.forEach(thumbnail => {
  2202. const playerAvatar = document.createElement('span');
  2203. playerAvatar.className = 'avatar avatar-headshot-md player-avatar';
  2204.  
  2205. const thumbnailImage = document.createElement('span');
  2206. thumbnailImage.className = 'thumbnail-2d-container avatar-card-image';
  2207.  
  2208. const img = document.createElement('img');
  2209. img.src = thumbnail.imageUrl;
  2210. img.alt = '';
  2211. img.title = '';
  2212.  
  2213. thumbnailImage.appendChild(img);
  2214. playerAvatar.appendChild(thumbnailImage);
  2215. playerThumbnailsContainer.appendChild(playerAvatar);
  2216. });
  2217.  
  2218. // Add the 6th placeholder for remaining players
  2219. if (playing > 5) {
  2220. const remainingPlayers = playing - 5;
  2221. const placeholder = document.createElement('span');
  2222. placeholder.className = 'avatar avatar-headshot-md player-avatar hidden-players-placeholder';
  2223. placeholder.textContent = `+${remainingPlayers}`;
  2224. placeholder.style.cssText = `
  2225. background-color: #7b7c7d; /* Gray background */
  2226. color: white;
  2227. display: flex;
  2228. align-items: center;
  2229. justify-content: center;
  2230. border-radius: 50%; /* Fully round */
  2231. font-size: 16px; /* Larger font size */
  2232. width: 60px; /* Larger width */
  2233. height: 60px; /* Larger height */
  2234. `;
  2235. playerThumbnailsContainer.appendChild(placeholder);
  2236. }
  2237.  
  2238. // Create the server details container
  2239. const serverDetails = document.createElement('div');
  2240. serverDetails.className = 'rbx-game-server-details game-server-details';
  2241.  
  2242. // Add server status (e.g., "15 of 15 people max")
  2243. const serverStatus = document.createElement('div');
  2244. serverStatus.className = 'text-info rbx-game-status rbx-game-server-status text-overflow';
  2245. serverStatus.textContent = `${playing} of ${maxPlayers} people max`;
  2246. serverDetails.appendChild(serverStatus);
  2247.  
  2248. // Add the player count gauge
  2249. const gaugeContainer = document.createElement('div');
  2250. gaugeContainer.className = 'server-player-count-gauge border';
  2251.  
  2252. const gaugeInner = document.createElement('div');
  2253. gaugeInner.className = 'gauge-inner-bar border';
  2254. gaugeInner.style.width = `${(playing / maxPlayers) * 100}%`;
  2255.  
  2256. gaugeContainer.appendChild(gaugeInner);
  2257. serverDetails.appendChild(gaugeContainer);
  2258.  
  2259. // Create a container for the buttons
  2260. const buttonContainer = document.createElement('div');
  2261. buttonContainer.className = 'button-container';
  2262. buttonContainer.style.cssText = `
  2263. display: flex;
  2264. gap: 8px; /* Space between buttons */
  2265. `;
  2266.  
  2267. // Add the "Join" button
  2268. const joinButton = document.createElement('button');
  2269. joinButton.type = 'button';
  2270. joinButton.className = 'btn-full-width btn-control-xs rbx-game-server-join game-server-join-btn btn-primary-md btn-min-width';
  2271. joinButton.textContent = 'Join';
  2272.  
  2273. // Add click event to join the server
  2274. joinButton.addEventListener('click', () => {
  2275. Roblox.GameLauncher.joinGameInstance(gameId, serverId);
  2276. });
  2277.  
  2278. buttonContainer.appendChild(joinButton);
  2279.  
  2280. // Add the "Invite" button
  2281. const inviteButton = document.createElement('button');
  2282. inviteButton.type = 'button';
  2283. inviteButton.className = 'btn-full-width btn-control-xs rbx-game-server-invite game-server-invite-btn btn-secondary-md btn-min-width';
  2284. inviteButton.textContent = 'Invite';
  2285.  
  2286. // Add click event to log the invite link
  2287. inviteButton.addEventListener('click', () => {
  2288. const inviteLink = `https://oqarshi.github.io/Invite/?placeid=${gameId}&serverid=${serverId}`;
  2289. //console.log('Copied invite link:', inviteLink);
  2290. navigator.clipboard.writeText(inviteLink).then(() => {
  2291. notifications('Success! Invite link copied to clipboard!', 'success', '🎉');
  2292. //console.log('Invite link copied to clipboard');
  2293. }).catch(err => {
  2294. console.error('Failed to copy invite link:', err);
  2295. });
  2296. });
  2297.  
  2298. buttonContainer.appendChild(inviteButton);
  2299.  
  2300. // Add the button container to the server details
  2301. serverDetails.appendChild(buttonContainer);
  2302.  
  2303. // Assemble the card
  2304. const cardContainer = document.createElement('div');
  2305. cardContainer.className = 'card-item';
  2306. cardContainer.appendChild(playerThumbnailsContainer);
  2307. cardContainer.appendChild(serverDetails);
  2308.  
  2309. cardItem.appendChild(cardContainer);
  2310.  
  2311. // Add the card to the server list
  2312. const serverList = document.querySelector('#rbx-game-server-item-container');
  2313. serverList.appendChild(cardItem);
  2314. }
  2315.  
  2316. /*********************************************************************************************************************************************************************************************************************************************
  2317. The function for the notification function
  2318.  
  2319. *********************************************************************************************************************************************************************************************************************************************/
  2320.  
  2321. // Create the toast container
  2322. const toastContainer = document.createElement('div');
  2323. toastContainer.id = 'toast-container';
  2324. document.body.appendChild(toastContainer);
  2325.  
  2326. // Define toast styles
  2327. const styles = `
  2328. #toast-container {
  2329. position: fixed;
  2330. top: 20px;
  2331. right: 20px;
  2332. z-index: 9999;
  2333. display: flex;
  2334. flex-direction: column;
  2335. align-items: flex-end;
  2336. }
  2337.  
  2338. .toast {
  2339. position: relative;
  2340. min-width: 250px;
  2341. padding: 15px 20px;
  2342. margin-bottom: 10px;
  2343. border-radius: 5px;
  2344. color: white;
  2345. font-family: 'Arial', sans-serif;
  2346. font-size: 14px;
  2347. box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  2348. opacity: 0;
  2349. transform: translateX(100%);
  2350. animation: slideIn 0.5s ease-out forwards, bounce 0.5s ease-out, fadeOut 0.5s ease-out 7.5s forwards;
  2351. display: flex;
  2352. align-items: center;
  2353. }
  2354.  
  2355. .toast.success {
  2356. background-color: #4CAF50;
  2357. }
  2358.  
  2359. .toast.error {
  2360. background-color: #F44336;
  2361. }
  2362.  
  2363. .toast.warning {
  2364. background-color: #FF9800;
  2365. }
  2366.  
  2367. .toast.info {
  2368. background-color: #2196F3;
  2369. }
  2370.  
  2371. .toast::after {
  2372. content: '';
  2373. position: absolute;
  2374. bottom: 0;
  2375. left: 0;
  2376. width: 100%;
  2377. height: 4px;
  2378. background-color: rgba(255, 255, 255, 0.5);
  2379. animation: progressBar 8s linear forwards;
  2380. }
  2381.  
  2382. .toast .icon {
  2383. margin-right: 10px;
  2384. font-size: 20px;
  2385. }
  2386.  
  2387. @keyframes slideIn {
  2388. to {
  2389. opacity: 1;
  2390. transform: translateX(0);
  2391. }
  2392. }
  2393.  
  2394. @keyframes fadeOut {
  2395. to {
  2396. opacity: 0;
  2397. transform: translateX(100%);
  2398. }
  2399. }
  2400.  
  2401. @keyframes progressBar {
  2402. to {
  2403. width: 0;
  2404. }
  2405. }
  2406.  
  2407. @keyframes bounce {
  2408. 0%, 20%, 50%, 80%, 100% {
  2409. transform: translateY(0);
  2410. }
  2411. 40% {
  2412. transform: translateY(-20px);
  2413. }
  2414. 60% {
  2415. transform: translateY(-10px);
  2416. }
  2417. }
  2418. `;
  2419.  
  2420. // Add styles to the document
  2421. const styleSheet = document.createElement('style');
  2422. styleSheet.type = 'text/css';
  2423. styleSheet.innerText = styles;
  2424. document.head.appendChild(styleSheet);
  2425.  
  2426. // Function to create a toast
  2427. function notifications(message, type, icon) {
  2428. const toast = document.createElement('div');
  2429. toast.className = `toast ${type}`;
  2430.  
  2431. // Add icon
  2432. const iconElement = document.createElement('span');
  2433. iconElement.className = 'icon';
  2434. iconElement.innerHTML = icon;
  2435. toast.appendChild(iconElement);
  2436.  
  2437. // Add message
  2438. const messageElement = document.createElement('span');
  2439. messageElement.innerText = message;
  2440. toast.appendChild(messageElement);
  2441.  
  2442. toastContainer.appendChild(toast);
  2443.  
  2444. // Remove the toast after the animation ends
  2445. setTimeout(() => {
  2446. toast.remove();
  2447. }, 8000);
  2448. }
  2449.  
  2450. /*********************************************************************************************************************************************************************************************************************************************
  2451. End of function for the notification function
  2452.  
  2453. *********************************************************************************************************************************************************************************************************************************************/
  2454.  
  2455. const serverRegionsByIp = {
  2456. "128.116.0.0": { city: "Hong Kong", country: { name: "Hong Kong", code: "HK" }, latitude: 22.3193, longitude: 114.1694 },
  2457. "128.116.1.0": { city: "Los Angeles", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 34.0522, longitude: -118.2437 },
  2458. "128.116.2.0": { city: "Warsaw", country: { name: "Poland", code: "PL" }, region: { name: "Mazowieckie", code: "14" }, latitude: 52.2297, longitude: 21.0122 },
  2459. "128.116.3.0": { city: "Warsaw", country: { name: "Poland", code: "PL" }, region: { name: "Mazowieckie", code: "14" }, latitude: 52.2297, longitude: 21.0122 },
  2460. "128.116.4.0": { city: "Paris", country: { name: "France", code: "FR" }, region: { name: "Île-de-France", code: "IDF" }, latitude: 48.8566, longitude: 2.3522 },
  2461. "128.116.5.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  2462. "128.116.6.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
  2463. "128.116.7.0": { city: "Mumbai", country: { name: "India", code: "IN" }, region: { name: "Mahārāshtra", code: "MH" }, latitude: 19.0760, longitude: 72.8777 },
  2464. "128.116.8.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  2465. "128.116.9.0": { city: "Mumbai", country: { name: "India", code: "IN" }, region: { name: "Mahārāshtra", code: "MH" }, latitude: 19.0760, longitude: 72.8777 },
  2466. "128.116.10.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  2467. "128.116.11.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  2468. "128.116.12.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  2469. "128.116.13.0": { city: "Amsterdam", country: { name: "Netherlands", code: "NL" }, region: { name: "Noord-Holland", code: "NH" }, latitude: 52.3676, longitude: 4.9041 },
  2470. "128.116.14.0": { city: "Hong Kong", country: { name: "Hong Kong", code: "HK" }, latitude: 22.3193, longitude: 114.1694 },
  2471. "128.116.15.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
  2472. "128.116.16.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
  2473. "128.116.17.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
  2474. "128.116.18.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 },
  2475. "128.116.19.0": { city: "Paris", country: { name: "France", code: "FR" }, region: { name: "Île-de-France", code: "IDF" }, latitude: 48.8566, longitude: 2.3522 },
  2476. "128.116.20.0": { city: "Paris", country: { name: "France", code: "FR" }, region: { name: "Île-de-France", code: "IDF" }, latitude: 48.8566, longitude: 2.3522 },
  2477. "128.116.21.0": { city: "Amsterdam", country: { name: "Netherlands", code: "NL" }, region: { name: "Noord-Holland", code: "NH" }, latitude: 52.3676, longitude: 4.9041 },
  2478. "128.116.22.0": { city: "Atlanta", country: { name: "United States", code: "US" }, region: { name: "Georgia", code: "GA" }, latitude: 33.7490, longitude: -84.3880 },
  2479. "128.116.23.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
  2480. "128.116.24.0": { city: "Atlanta", country: { name: "United States", code: "US" }, region: { name: "Georgia", code: "GA" }, latitude: 33.7490, longitude: -84.3880 },
  2481. "128.116.25.0": { city: "Atlanta", country: { name: "United States", code: "US" }, region: { name: "Georgia", code: "GA" }, latitude: 33.7490, longitude: -84.3880 },
  2482. "128.116.26.0": { city: "Paris", country: { name: "France", code: "FR" }, region: { name: "Île-de-France", code: "IDF" }, latitude: 48.8566, longitude: 2.3522 },
  2483. "128.116.27.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  2484. "128.116.28.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  2485. "128.116.29.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  2486. "128.116.30.0": { city: "Hong Kong", country: { name: "Hong Kong", code: "HK" }, latitude: 22.3193, longitude: 114.1694 },
  2487. "128.116.31.0": { city: "Warsaw", country: { name: "Poland", code: "PL" }, region: { name: "Mazowieckie", code: "14" }, latitude: 52.2297, longitude: 21.0122 },
  2488. "128.116.32.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
  2489. "128.116.33.0": { city: "Slough", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5105, longitude: -0.5950 },
  2490. "128.116.34.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  2491. "128.116.35.0": { city: "Slough", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5105, longitude: -0.5950 },
  2492. "128.116.36.0": { city: "Slough", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5105, longitude: -0.5950 },
  2493. "128.116.37.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 },
  2494. "128.116.38.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 },
  2495. "128.116.39.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  2496. "128.116.40.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  2497. "128.116.41.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  2498. "128.116.42.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  2499. "128.116.43.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  2500. "128.116.44.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  2501. "128.116.45.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 },
  2502. "128.116.46.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  2503. "128.116.47.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  2504. "128.116.48.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  2505. "128.116.49.0": { city: "Los Angeles", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 34.0522, longitude: -118.2437 },
  2506. "128.116.50.0": { city: "Singapore", country: { name: "Singapore", code: "SG" }, latitude: 1.3521, longitude: 103.8198 },
  2507. "128.116.51.0": { city: "Sydney", country: { name: "Australia", code: "AU" }, region: { name: "New South Wales", code: "NSW" }, latitude: -33.8688, longitude: 151.2093 },
  2508. "128.116.52.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  2509. "128.116.53.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  2510. "128.116.54.0": { city: "Amsterdam", country: { name: "Netherlands", code: "NL" }, region: { name: "Noord-Holland", code: "NH" }, latitude: 52.3676, longitude: 4.9041 },
  2511. "128.116.55.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
  2512. "128.116.56.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  2513. "128.116.57.0": { city: "San Jose", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3382, longitude: -121.8863 },
  2514. "128.116.58.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
  2515. "128.116.59.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
  2516. "128.116.60.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
  2517. "128.116.61.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  2518. "128.116.62.0": { city: "Seattle", country: { name: "United States", code: "US" }, region: { name: "Washington", code: "WA" }, latitude: 47.6062, longitude: -122.3321 },
  2519. "128.116.63.0": { city: "Los Angeles", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 34.0522, longitude: -118.2437 },
  2520. "128.116.64.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  2521. "128.116.65.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
  2522. "128.116.66.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
  2523. "128.116.67.0": { city: "San Jose", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3382, longitude: -121.8863 },
  2524. "128.116.68.0": { city: "Santa Clara", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3541, longitude: -121.9552 },
  2525. "128.116.69.0": { city: "Santa Clara", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3541, longitude: -121.9552 },
  2526. "128.116.70.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  2527. "128.116.71.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  2528. "128.116.72.0": { city: "Slough", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5105, longitude: -0.5950 },
  2529. "128.116.73.0": { city: "Slough", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5105, longitude: -0.5950 },
  2530. "128.116.74.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  2531. "128.116.75.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  2532. "128.116.76.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  2533. "128.116.77.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  2534. "128.116.78.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  2535. "128.116.79.0": { city: "Singapore", country: { name: "Singapore", code: "SG" }, latitude: 1.3521, longitude: 103.8198 },
  2536. "128.116.80.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  2537. "128.116.81.0": { city: "Santa Clara", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3541, longitude: -121.9552 },
  2538. "128.116.82.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
  2539. "128.116.83.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
  2540. "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 },
  2541. "128.116.85.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 },
  2542. "128.116.86.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  2543. "128.116.87.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  2544. "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 },
  2545. "128.116.89.0": { city: "Slough", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5105, longitude: -0.5950 },
  2546. "128.116.90.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  2547. "128.116.91.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  2548. "128.116.92.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  2549. "128.116.93.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  2550. "128.116.94.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  2551. "128.116.95.0": { city: "Dallas", country: { name: "United States", code: "US" }, region: { name: "Texas", code: "TX" }, latitude: 32.7767, longitude: -96.7970 },
  2552. "128.116.96.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  2553. "128.116.97.0": { city: "Singapore", country: { name: "Singapore", code: "SG" }, latitude: 1.3521, longitude: 103.8198 },
  2554. "128.116.98.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  2555. "128.116.99.0": { city: "Atlanta", country: { name: "United States", code: "US" }, region: { name: "Georgia", code: "GA" }, latitude: 33.7490, longitude: -84.3880 },
  2556. "128.116.100.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  2557. "128.116.101.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  2558. "128.116.102.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  2559. "128.116.103.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  2560. "128.116.104.0": { city: "Mumbai", country: { name: "India", code: "IN" }, region: { name: "Mahārāshtra", code: "MH" }, latitude: 19.0760, longitude: 72.8777 },
  2561. "128.116.105.0": { city: "Santa Clara", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3541, longitude: -121.9552 },
  2562. "128.116.106.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  2563. "128.116.107.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  2564. "128.116.108.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  2565. "128.116.109.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  2566. "128.116.110.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  2567. "128.116.111.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  2568. "128.116.112.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  2569. "128.116.113.0": { city: "Chicago", country: { name: "United States", code: "US" }, region: { name: "Illinois", code: "IL" }, latitude: 41.8781, longitude: -87.6298 },
  2570. "128.116.114.0": { city: "Ashburn", country: { name: "United States", code: "US" }, region: { name: "Virginia", code: "VA" }, latitude: 39.0438, longitude: -77.4874 },
  2571. "128.116.115.0": { city: "Seattle", country: { name: "United States", code: "US" }, region: { name: "Washington", code: "WA" }, latitude: 47.6062, longitude: -122.3321 },
  2572. "128.116.116.0": { city: "Los Angeles", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 34.0522, longitude: -118.2437 },
  2573. "128.116.117.0": { city: "San Jose", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.3382, longitude: -121.8863 },
  2574. "128.116.118.0": { city: "Hong Kong", country: { name: "Hong Kong", code: "HK" }, latitude: 22.3193, longitude: 114.1694 },
  2575. "128.116.119.0": { city: "Slough", country: { name: "United Kingdom", code: "GB" }, region: { name: "England", code: "ENG" }, latitude: 51.5105, longitude: -0.5950 },
  2576. "128.116.120.0": { city: "Tokyo", country: { name: "Japan", code: "JP" }, region: { name: "Tokyo", code: "13" }, latitude: 35.6895, longitude: 139.6917 },
  2577. "128.116.121.0": { city: "Amsterdam", country: { name: "Netherlands", code: "NL" }, region: { name: "Noord-Holland", code: "NH" }, latitude: 52.3676, longitude: 4.9041 },
  2578. "128.116.122.0": { city: "Paris", country: { name: "France", code: "FR" }, region: { name: "Île-de-France", code: "IDF" }, latitude: 48.8566, longitude: 2.3522 },
  2579. "128.116.123.0": { city: "Frankfurt am Main", country: { name: "Germany", code: "DE" }, region: { name: "Hessen", code: "HE" }, latitude: 50.1109, longitude: 8.6821 },
  2580. "128.116.124.0": { city: "Warsaw", country: { name: "Poland", code: "PL" }, region: { name: "Mazowieckie", code: "14" }, latitude: 52.2297, longitude: 21.0122 },
  2581. "128.116.125.0": { city: "San Mateo", country: { name: "United States", code: "US" }, region: { name: "California", code: "CA" }, latitude: 37.5630, longitude: -122.3255 },
  2582. "128.116.126.0": { city: "Secaucus", country: { name: "United States", code: "US" }, region: { name: "New Jersey", code: "NJ" }, latitude: 40.7895, longitude: -74.0565 },
  2583. "128.116.127.0": { city: "Miami", country: { name: "United States", code: "US" }, region: { name: "Florida", code: "FL" }, latitude: 25.7617, longitude: -80.1918 },
  2584. };
  2585.  
  2586. // Main function does nothing lmao but its ere i guees
  2587. function main() {
  2588. const gameIdMatch = window.location.pathname.match(/\/games\/(\d+)\//);
  2589. if (!gameIdMatch) {
  2590. console.error("Game ID not found in URL!");
  2591. return;
  2592. }
  2593.  
  2594. const gameId = gameIdMatch[1];
  2595. }
  2596.  
  2597. /*******************************************************
  2598. name of function: Initiate the observer
  2599. description: Start observing the document for changes
  2600. *******************************************************/
  2601.  
  2602. // Start observing the document for changes
  2603. observer.observe(document.body, {
  2604. childList: true,
  2605. subtree: true
  2606. });
  2607. main();
  2608. })();