Diep.io Server Selector + Restore Player Count

Select servers from different gamemodes and regions for Diep. Also restores player count for Diep.io.

  1. // ==UserScript==
  2. // @name Diep.io Server Selector + Restore Player Count
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.7.0
  5. // @description Select servers from different gamemodes and regions for Diep. Also restores player count for Diep.io.
  6. // @author Altanis + Bismuth
  7. // @match *://diep.io/*
  8. // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
  9. // @grant unsafeWindow
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (async function() {
  14. // Special credits to Diep7444 for paving the way to an effective region changer.
  15.  
  16. const textShadow = 'text-shadow:black 0.18vh 0, black -0.18vh 0, black 0 -0.18vh, black 0 0.18vh, black 0.18vh 0.18vh, black -0.18vh 0.18vh, black 0.18vh -0.18vh, black -0.18vh -0.18vh, black 0.09vh 0.18vh, black -0.09vh 0.18vh, black 0.09vh -0.18vh, black -0.09vh -0.18vh, black 0.18vh 0.09vh, black -0.18vh 0.09vh, black 0.18vh -0.09vh, black -0.18vh -0.09vh'
  17. const regions = ["lnd-sfo", "lnd-atl", "lnd-fra", "lnd-syd"];
  18. const modes = ["ffa", "survival", "teams", "4teams", "dom", "tag", "maze", "sandbox"];
  19. const colors = ["#E8B18A", "#E666EA", "#9566EA", "#6690EA", "#E7D063", "#EA6666", "#92EA66", "#66EAE6"];
  20.  
  21. let key = 'KeyT'; // Go to https://keycode.info, press desired key, copy event.code, paste into quotes.
  22. let special = 'alt'; // alt, shift, ctrl, meta (Windows/Command)
  23.  
  24. let includeUncommon = true;
  25.  
  26. if (!['alt', 'shift', 'ctrl', 'meta'].includes(special)) special = 'alt'; // Default = Alt.
  27.  
  28. var PLAYER_COUNT = 0;
  29. unsafeWindow.API_STATE = { code: 0, message: 'Connecting...'};
  30.  
  31. const modeHTML = document.createElement("div");
  32. document.body.appendChild(modeHTML);
  33. modeHTML.innerHTML = `
  34. <div class='parent' id='ServerSelector' style='user-select:none; position:fixed; top:25%; right:0.5%; text-align:center; width:15vw; font-family:Ubuntu; color:#FFFFFF; font-style:normal; font-size:0.9vw; ${textShadow}'><div class='child' style='line-height:2vh; opacity:75%'>
  35. TAB to toggle server selector
  36. <br>
  37. <p style="font-size:12px;">Created by Altanis and Bismuth</p>
  38. <p style="font-size:12px;" id="status">Status: ${unsafeWindow.API_STATE.message}</p>
  39. <hr>
  40. </div>
  41. <p style="font-size:10px">Game Mode</p>
  42. <button class='child' type='button' id='ffa' value='mode' style='width:3.5vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[7]}; color:#FFFFFF; font-style:normal; font-size:0.9vw; ${textShadow}'>FFA</button>
  43. <button class='child' type='button' id='survival' value='mode' style='width:3.5vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[6]}; color:#FFFFFF; font-style:normal; font-size:0.9vw; ${textShadow}'>SURV</button>
  44. <button class='child' type='button' id='teams' value='mode' style='width:3.5vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[5]}; color:#FFFFFF; font-style:normal; font-size:0.9vw; ${textShadow}'>2TDM</button>
  45. <button class='child' type='button' id='4teams' value='mode' style='width:3.5vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[4]}; color:#FFFFFF; font-style:normal; font-size:0.9vw; ${textShadow}'>4TDM</button>
  46. <button class='child' type='button' id='dom' value='mode' style='width:3.5vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[3]}; color:#FFFFFF; font-style:normal; font-size:0.9vw; ${textShadow}'>DOM</button>
  47. <button class='child' type='button' id='tag' value='mode' style='width:3.5vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[2]}; color:#FFFFFF; font-style:normal; font-size:0.9vw; ${textShadow}'>TAG</button>
  48. <button class='child' type='button' id='maze' value='mode' style='width:3.5vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[1]}; color:#FFFFFF; font-style:normal; font-size:0.9vw;${textShadow}'>MAZE</button>
  49. <button class='child' type='button' id='sandbox' value='mode' style='width:3.5vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[0]}; color:#FFFFFF; font-style:normal; font-size:0.9vw;${textShadow}'>SBX</button>
  50. <p style="font-size:10px">Region</p>
  51. <button class='child' type='button' id=${regions[0]} value='region' style='width:3.5vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[3]}; color:#FFFFFF; font-style:normal; font-size:0.9vw; ${textShadow}'>SFO</button>
  52. <button class='child' type='button' id=${regions[1]} value='region' style='width:3.5vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[2]}; color:#FFFFFF; font-style:normal; font-size:0.9vw; ${textShadow}'>ATL</button>
  53. <button class='child' type='button' id=${regions[2]} value='region' style='width:3.5vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[6]}; color:#FFFFFF; font-style:normal; font-size:0.9vw; ${textShadow}'>FRA</button>
  54. <button class='child' type='button' id=${regions[3]} value='region' style='width:3.5vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[5]}; color:#FFFFFF; font-style:normal; font-size:0.9vw;${textShadow}'>SYD</button>
  55. <div class='parent' id='choice' style='user-select:none; position:relative; top:65%; left:0.5%; text-align:center; width:15vw; font-family:Ubuntu; color:#FFFFFF; font-style:normal; font-size:0.9vw; ${textShadow}'>
  56. <br>
  57. </div>
  58. </div>
  59. `
  60. document.getElementById('ServerSelector').style.display = 'block';
  61. for (let mode of modes) {
  62. addButtonListener(mode);
  63. }
  64. for (let region of regions) {
  65. addButtonListener(region);
  66. }
  67.  
  68. function refreshStatus() { document.getElementById('status').innerHTML = `Status: ${unsafeWindow.API_STATE.message}`; }
  69. function refreshHTML() {
  70. let json = `<div class='parent' id='choice' style='user-select:none; position:relative; top:65%; left:0.5%; text-align:center; width:15vw; font-family:Ubuntu; color:#FFFFFF; font-style:normal; font-size:0.9vw; ${textShadow}'><p style="font-size:12px"> </p><div class='child' style='line-height:2vh; opacity:75%'> Servers for ${choices.mode} ${choices.region}<hr></div>`;
  71. for (let n = 0; n < serverWithoutCSS[choices.mode][choices.region].lobbies.length; n++) {
  72. json += `<button class='child' type='button' id='choice${n}'value='${n}'style='width:9vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[n % 8]}; color:#FFFFFF; font-style:normal; font-size:0.9vw; filter: ${currentServer.includes(serverWithoutCSS[choices.mode][choices.region].lobbies[n].slice(0,8))? 'brightness(100%)': 'brightness(50%)'}; ${textShadow}'>${serverWithoutCSS[choices.mode][choices.region].lobbies[n].slice(0,8)} (${serverWithoutCSS[choices.mode][choices.region].info[serverWithoutCSS[choices.mode][choices.region].lobbies[n].slice(0, 36)].total_player_count || '??'}/${serverWithoutCSS[choices.mode][choices.region].info[serverWithoutCSS[choices.mode][choices.region].lobbies[n].slice(0, 36)].max_players_direct || '??'})</button>`;
  73. }
  74.  
  75. document.getElementById('choice').innerHTML = `${json}`;
  76.  
  77. for (let n = 0; n < serverWithoutCSS[choices.mode][choices.region].lobbies.length; n++) {
  78. addButtonListener('choice' + n);
  79. }
  80. }
  81.  
  82. function buttonAction(id) {
  83. let button = document.getElementById(id);
  84. if (button.value === 'mode') {
  85. choices.mode = id;
  86. fetchServer(choices.mode, choices.region, 3);
  87. } else if (button.value === 'region') {
  88. choices.region = id;
  89. fetchServer(choices.mode, choices.region, 3);
  90. } else {
  91. (connectTo(choices.mode, choices.region, button.value));
  92. }
  93. refreshHTML();
  94. }
  95.  
  96. function addButtonListener(id) {
  97. document.getElementById(id).addEventListener("click", function() {
  98. buttonAction(id)
  99. });
  100. document.getElementById(id).addEventListener("mouseenter", function() {
  101. lightenColor(id)
  102. });
  103. document.getElementById(id).addEventListener("mouseleave", function() {
  104. resetColor(id)
  105. });
  106. }
  107.  
  108. function lightenColor(id) {
  109. document.getElementById(id).style.opacity = '100%'
  110. }
  111.  
  112. function resetColor(id) {
  113. document.getElementById(id).style.opacity = '60%'
  114. }
  115.  
  116. function getServerLink(server) {
  117. let link = '';
  118. for (const char of server) {
  119. const code = char.charCodeAt(0);
  120. const value = (`00${code.toString(16)}`).slice(-2);
  121. link += value.split("").reverse().join("");
  122. }
  123. return link;
  124. }
  125.  
  126. async function calcPlayerCount() {
  127. const body = JSON.parse(localStorage.lists)?.list;
  128.  
  129. if (!body.hasOwnProperty('game_modes') && !body.hasOwnProperty('message')) {
  130. unsafeWindow.API_STATE.code = 2; return;
  131. };
  132. if (body.message === 'too many requests') {
  133. unsafeWindow.API_STATE.code = 3; return;
  134. };
  135.  
  136. PLAYER_COUNT = 0;
  137. body.game_modes.forEach(function(g) {
  138. if (['dom', 'sandbox', 'survival', 'tag'].includes(g.game_mode_name) && !includeUncommon) return;
  139. g.regions.forEach(function(r) {
  140. r.lobbies.forEach(function(l) {
  141. PLAYER_COUNT += l.total_player_count;
  142. });
  143. });
  144. });
  145. }
  146.  
  147. const choices = {
  148. mode: modes[0],
  149. region: regions[0],
  150. }
  151. const serverWithoutCSS = {};
  152.  
  153. modes.forEach(function(gamemode) {
  154. serverWithoutCSS[gamemode] = {};
  155. regions.forEach(function(region) {
  156. serverWithoutCSS[gamemode][region] = { lobbies: [], info: {} };
  157. });
  158. });
  159.  
  160. unsafeWindow.API_STATE = new Proxy(unsafeWindow.API_STATE, {
  161. set: function(t, k, v) {
  162. t[k] = v;
  163.  
  164. if ([0, 1, 2, 4].includes(v)) {
  165. t.message = v === 0 ? 'Connecting...' : (v === 1 ? 'Ready!' : (v === 2 ? 'Endpoint is down.' : 'Lobbies don\'t exist.'))
  166. } else if (v === 3) {
  167. const date = new Date(Date.now());
  168.  
  169. const curMin = date.getMinutes();
  170. const intervals = [0, 15, 30, 45];
  171.  
  172. intervals.sort((a, b) => {
  173. return Math.abs(curMin - a) - Math.abs(curMin - b);
  174. });
  175.  
  176. let retry_at = intervals[0] < curMin ? intervals[1] : intervals[0];
  177. let time = `${retry_at === 0 ? date.getHours() + 1 : date.getHours()}:${retry_at.length === 1 ? `0${retry_at}` : retry_at}`;
  178. t.message = `Ratelimited! Retry-At: ${time}.`;
  179.  
  180. const interval = setInterval(function() {
  181. let [hours, minutes] = time.split(':').map(t => parseInt(t));
  182. if (new Date(Date.now()).getHours() >= hours && new Date(Date.now()).getMinutes() >= minutes) {
  183. t.code = 1;
  184. t.message = 'Ready!';
  185. refreshStatus();
  186. clearInterval(interval);
  187. }
  188. }, 150);
  189. }
  190. refreshStatus();
  191. return true;
  192. }
  193. });
  194.  
  195. if (!localStorage.lists) {
  196. var _resp = await fetch(`https://api-game.rivet.gg/v1/matchmaker/lobbies/list`);
  197. var lists = await _resp.json();
  198.  
  199. unsafeWindow.API_STATE.code = _resp.status === 200 ? 1 : (_resp.status === 429 ? 3 : 2);
  200.  
  201. localStorage.lists = JSON.stringify({
  202. timestamp: Date.now(),
  203. list: lists,
  204. });
  205. }
  206.  
  207. setInterval(async function() {
  208. if (!localStorage.lists) {
  209. var _resp = await fetch(`https://api-game.rivet.gg/v1/matchmaker/lobbies/list`);
  210. var lists = await _resp.json();
  211.  
  212. unsafeWindow.API_STATE.code = _resp.status === 200 ? 1 : (_resp.status === 429 ? 3 : 2);
  213. localStorage.lists = JSON.stringify({
  214. timestamp: Date.now(),
  215. list: lists,
  216. });
  217. }
  218.  
  219. if (Date.now() - JSON.parse(localStorage.lists)?.timestamp > 3e5) {
  220. var _resp = await fetch(`https://api-game.rivet.gg/v1/matchmaker/lobbies/list`);
  221. var lists = await _resp.json();
  222.  
  223. unsafeWindow.API_STATE.code = _resp.status === 200 ? 1 : (_resp.status === 429 ? 3 : 2);
  224.  
  225. localStorage.lists = JSON.stringify({
  226. timestamp: Date.now(),
  227. list: lists,
  228. });
  229.  
  230. console.log('Refreshed server list.');
  231. }
  232. if (unsafeWindow.API_STATE.code === 0) unsafeWindow.API_STATE.code = 1;
  233. }, 5000);
  234.  
  235. const GAMEMODES_MAP = [];
  236. const REGIONS_MAP = [];
  237.  
  238. try {
  239. JSON.parse(localStorage.lists).list.game_modes.forEach(function(data) {
  240. GAMEMODES_MAP.push(data.game_mode_name);
  241. if (REGIONS_MAP.length === 0) {
  242. data.regions.forEach(function(data2) {
  243. REGIONS_MAP.push(data2.region_name);
  244. });
  245. }
  246. });
  247. } catch (er) {
  248. alert('API of Rivet is down!');
  249. unsafeWindow.API_STATE.code = 2;
  250. }
  251.  
  252. var do_connect = false;
  253. var currentServer = '';
  254. var override = false;
  255. async function fetchServer(mode, region) {
  256. const body = JSON.parse(localStorage.lists)?.list;
  257.  
  258. if (!body.hasOwnProperty('game_modes') && !body.hasOwnProperty('message')) {
  259. unsafeWindow.API_STATE.code = 2;
  260. return;
  261. }
  262. if (body.message === 'too many requests') {
  263. unsafeWindow.API_STATE.code = 3;
  264. return;
  265. }
  266.  
  267. calcPlayerCount();
  268.  
  269. const lobbies = body.game_modes[GAMEMODES_MAP.indexOf(mode)]?.regions[REGIONS_MAP.indexOf(region)]?.lobbies;
  270.  
  271. if (!lobbies) {
  272. unsafeWindow.API_STATE.code = 4;
  273. return;
  274. };
  275.  
  276. lobbies.forEach(function({
  277. lobby_id, total_player_count, max_players_direct
  278. }) {
  279. serverWithoutCSS[mode][region].info[lobby_id] = { total_player_count, max_players_direct };
  280.  
  281. if (!serverWithoutCSS[mode][region].lobbies.some(function(host) {
  282. return host === `${lobby_id}-80.lobby.${region}.hiss.io:443`
  283. })) {
  284. serverWithoutCSS[mode][region].lobbies.push(`${lobby_id}-80.lobby.${region}.hiss.io:443`);
  285. }
  286. });
  287. unsafeWindow.API_STATE.code = 1;
  288.  
  289. refreshHTML();
  290. }
  291.  
  292. function appendServers(mode, region) {
  293. fetchServer(mode, region);
  294. }
  295.  
  296. function connectTo(mode, region, number) {
  297. if (!serverWithoutCSS[mode][region].lobbies[number]) return;
  298. currentServer = "wss://" + serverWithoutCSS[mode][region].lobbies[number];
  299. do_connect = true;
  300. unsafeWindow.input.execute('lb_reconnect');
  301. }
  302. unsafeWindow.servers = serverWithoutCSS;
  303.  
  304. var _WebSocket = unsafeWindow.WebSocket;
  305. unsafeWindow.WebSocket = function(wss) {
  306. let triggered = do_connect;
  307.  
  308. if (do_connect) wss = currentServer;
  309. currentServer = wss;
  310.  
  311. const socket = new _WebSocket(wss);
  312. socket.addEventListener('error', function(er) {
  313. if (triggered)
  314. alert('Connection to lobby failed, redirecting...'); // This is most likely due to the target server closing or reaching player limit.
  315. });
  316.  
  317. do_connect = false;
  318. return socket;
  319. };
  320.  
  321. (function(realHTMLInputElement) {
  322. Object.defineProperty(HTMLTextAreaElement.prototype, 'value', {
  323. set: function(value) {
  324. if (!value.startsWith('diep.io/#')) return realHTMLInputElement.set.call(this, value);
  325.  
  326. let [serverID, party] = value.replace('diep.io/#', '').split('00');
  327. serverID = getServerLink(currentServer.split("://")[1].split("-80.lobby")[0]).toUpperCase();
  328. value = `https://diep.io/#${serverID}00${party}`;
  329.  
  330. return realHTMLInputElement.set.call(this, value);
  331. },
  332. });
  333. }(Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')));
  334.  
  335. document.body.onkeydown = function(e) {
  336. if (String.fromCharCode(e.keyCode) === '\t') {
  337. if (document.getElementById('ServerSelector').style.display === "none") {
  338. document.getElementById('ServerSelector').style.display = "block";
  339. document.getElementById('choice').style.display = "block";
  340. } else {
  341. document.getElementById('ServerSelector').style.display = "none";
  342. document.getElementById('choice').style.display = "none";
  343. }
  344. }
  345.  
  346. if (e.code === key && e[`${special}Key`]) {
  347. includeUncommon = !includeUncommon;
  348. calcPlayerCount();
  349. }
  350. }
  351.  
  352. calcPlayerCount();
  353. setInterval(calcPlayerCount, 60000);
  354.  
  355. const crx = CanvasRenderingContext2D.prototype;
  356.  
  357. crx.fillText = new Proxy(crx.fillText, {
  358. apply(f, _this, args) {
  359. if (args[0].includes('ms lnd'))
  360. args[0] += ` ${PLAYER_COUNT} players`;
  361. return f.apply(_this, args);
  362. }
  363. });
  364.  
  365. crx.strokeText = new Proxy(crx.strokeText, {
  366. apply(f, _this, args) {
  367. if (args[0].includes('ms lnd'))
  368. args[0] += ` ${PLAYER_COUNT} players`;
  369. return f.apply(_this, args);
  370. }
  371. });
  372.  
  373. crx.measureText = new Proxy(crx.measureText, {
  374. apply(f, _this, args) {
  375. if (args[0].includes('ms lnd'))
  376. args[0] += ` ${PLAYER_COUNT} players`;
  377. return f.apply(_this, args);
  378. }
  379. });
  380. })();