diep.io Spade Script - Public

Press TAB to show Server Selector.

  1. // ==UserScript==
  2. // @name diep.io Spade Script - Public
  3. // @namespace spade-squad.com
  4. // @author DiE ♠ BiTCH // ♠-squad
  5. // @version 1.0.0
  6. // @description Press TAB to show Server Selector.
  7. // @homepage http://spade-squad.com
  8. // @icon http://spade-squad.com/pub/userscript/icon64.png
  9. // @match *://*.diep.io/*
  10. // @run-at document-start
  11. // @grant GM_addStyle
  12. // @grant GM_getResourceText
  13. // @grant GM_setClipboard
  14. // @grant GM_notification
  15. // @grant unsafeWindow
  16. // @require http://code.jquery.com/jquery-3.2.1.slim.min.js
  17. // @resource spadeCSS http://spade-squad.com/pub/userscript/spadeuserscript.css
  18. // ==/UserScript==
  19.  
  20. //TODO: handle keypress fix partycache
  21.  
  22. /* eslint-disable */
  23. let spadeCSS = GM_getResourceText("spadeCSS");
  24. GM_addStyle(spadeCSS);
  25. /* eslint-enable */
  26.  
  27. (function () {
  28. "use strict";
  29.  
  30. let defaultConfig = {
  31. "hotkey": {
  32. "connectUI": "\t" // TAB
  33. },
  34. "gameModeName": {
  35. "ffa": "FFA",
  36. "survival": "Survival",
  37. "teams": "2TDM",
  38. "4teams": "4TDM",
  39. "dom": "Domination",
  40. "maze": "Maze",
  41. "tag": "Tag",
  42. "sandbox": "Sandbox"
  43. },
  44. "team": {
  45. "blue": [[0, 178, 225, 255], [76, 201, 234, 255]],
  46. "red": [[241, 78, 84, 255], [245, 131, 135, 255]],
  47. "green": [[0, 225, 110, 255], [76, 234, 153, 255]],
  48. "purple": [[191, 127, 245, 255], [210, 165, 248, 255]]
  49. },
  50. "settings": {
  51. "firstRunDisable": false
  52. },
  53. "script": {
  54. "currentServer": {},
  55. "debugging": false
  56. }
  57. };
  58.  
  59. const isObject = (obj) => {
  60. return obj instanceof Object && obj.constructor === Object;
  61. };
  62.  
  63. const dataStorage = {
  64. set (key, value) {
  65. localStorage.setItem(key, JSON.stringify(value));
  66. },
  67. get (key) {
  68. const value = localStorage.getItem(key);
  69. return value && JSON.parse(value);
  70. }
  71. };
  72.  
  73. (function () {
  74. let privateConfig;
  75. unsafeWindow.Config = {};
  76. const proxify = (obj) => {
  77. for (const subkey in obj) {
  78. if (Object.prototype.hasOwnProperty.call(obj, subkey)) {
  79. unsafeWindow.Config[subkey] = new Proxy(obj[subkey], {
  80. get (target, propKey, receiver) {
  81. if (propKey in target) {
  82. return Reflect.get(target, propKey, receiver);
  83. }
  84. throw new ReferenceError("Unknown property: " + propKey);
  85. },
  86. set (target, propKey, value, receiver) {
  87. target[propKey] = value;
  88. dataStorage.set("spadepublic", obj);
  89. return Reflect.set(target, propKey, value, receiver);
  90. }
  91. });
  92. }
  93. }
  94. };
  95.  
  96. if (dataStorage.get("spadepublic")) {
  97. privateConfig = dataStorage.get("spadepublic");
  98. } else {
  99. dataStorage.set("spadepublic", defaultConfig);
  100. privateConfig = defaultConfig;
  101. }
  102.  
  103. proxify(privateConfig);
  104.  
  105. unsafeWindow.resetConfig = () => {
  106. dataStorage.set("spadepublic", defaultConfig);
  107. unsafeWindow.Config = {};
  108. privateConfig = defaultConfig;
  109. proxify(privateConfig);
  110. };
  111. })();
  112.  
  113. let playing = () => {
  114. return false;
  115. };
  116.  
  117. $(window).on("load", () => {
  118. (function setBack () {
  119. try {
  120. if (unsafeWindow.input.should_prevent_unload) {
  121. playing = () => {
  122. return !!unsafeWindow.input.should_prevent_unload();
  123. };
  124. }
  125. } catch (error) {
  126. setTimeout(() => {
  127. setBack();
  128. }, 100);
  129. }
  130. })();
  131. });
  132.  
  133. let canvas, ctx;
  134. $(() => {
  135. canvas = $("#canvas").get(0);
  136. ctx = canvas.getContext("2d");
  137. });
  138.  
  139. HTMLElement.prototype.focus = () => {};
  140. HTMLElement.prototype.blur = () => {};
  141.  
  142. const capitalizeFirstLetter = (string) => {
  143. return string && string[0].toUpperCase() + string.slice(1);
  144. };
  145.  
  146. const createEl = (elObj, parent) => {
  147. let element;
  148. if (typeof elObj === "string") {
  149. element = $(document.createTextNode(elObj));
  150. } else {
  151. element = $(`<${elObj.node}>`);
  152. if (elObj.att) {
  153. let attributes = elObj.att;
  154. for (let key in attributes) {
  155. if (attributes.hasOwnProperty(key)) {
  156. if (key.charAt(0) === "@") {
  157. element.attr(key.substring(1), attributes[key]);
  158. } else {
  159. element.text(attributes[key]);
  160. }
  161. }
  162. }
  163. }
  164. if (elObj.evl) {
  165. element.on(elObj.evl.type, elObj.evl.f);
  166. }
  167. if (elObj.child) {
  168. elObj.child.forEach((node) => {
  169. createEl(node, element.get(0));
  170. });
  171. }
  172. }
  173. if (parent) {
  174. parent.append(element.get(0));
  175. }
  176. return element;
  177. };
  178.  
  179. const scriptBody = $("<body>").get(0);
  180. createEl({
  181. node: "div", att: {"@id": "main", "@class": "base"},
  182. child: [ {
  183. node: "div", att: {"@class": "top"},
  184. child: [ {
  185. node: "h2", att: {"@class": "title"},
  186. child: [ {
  187. node: "span", att: {"@class": "spadesymbol", textContent: "♠"}
  188. }, " Select diep.io Server ", {
  189. node: "span", att: {"@class": "spadesymbol", textContent: "♠"}
  190. }, {
  191. } ]
  192. }, {
  193. node: "span", att: {"@class": "menu"},
  194. child: [ {
  195. node: "a", att: {"@class": "menuButton close", textContent: "X"},
  196. evl: {
  197. type: "click",
  198. f: () => {
  199. $(".appear").removeClass("appear");
  200. }}
  201. } ]
  202. }]
  203. }, {
  204. node: "lable", att: {textContent: "Gamemode"},
  205. child: [ {
  206. node: "select", att: {"@id": "gamemode"}
  207. } ]
  208. }, {
  209. node: "lable", att: {textContent: "Server"},
  210. child: [ {
  211. node: "select", att: {"@id": "server"}
  212. } ]
  213. }, {
  214. node: "span", att: {"@id": "more", textContent: "+"}
  215. }, {
  216. node: "div",
  217. child: [ {
  218. node: "button", att: {"@type": "button", "@id": "connect", "@class": "commandButton", textContent: "Connect"},
  219. evl: {
  220. type: "click",
  221. f: () => {
  222. connectServer();
  223. setTimeout(() => {
  224. $(".appear").removeClass( "appear" );
  225. }, 800);
  226. }}
  227. }, {
  228. node: "button", att: {"@type": "button", "@id": "disconnect", "@class": "commandButton", textContent: "Disconnect"},
  229. evl: {
  230. type: "click",
  231. f: () => {
  232. unsafeWindow.m28nOverride = false;
  233. unsafeWindow.input.execute("lb_reconnect");
  234. }}
  235. } ]
  236. }, {
  237. node: "p", att: {"@class": "ctag", textContent: "// © "},
  238. child: [ {
  239. node: "a", att: {"@class": "spadeweb", "@href": "http://spade-squad.com", "@target": "_blank", textContent: "spade-squad.com"}
  240. } ]
  241. } ]
  242. }, scriptBody);
  243.  
  244. $(() => {
  245. // View script info only on firstRun
  246. if (!unsafeWindow.Config.settings.firstRunDisable) {
  247. createEl({
  248. node: "div", att: {"@id": "firstrun", "@class": "base appear"},
  249. child: [ {
  250. node: "div", att: {"@class": "top"},
  251. child: [ {
  252. node: "h2", att: {"@class": "title"},
  253. child: [ {
  254. node: "span", att: {"@class": "spadesymbol", textContent: "♠"}
  255. }, " Spade Script - First Run ", {
  256. node: "span", att: {"@class": "spadesymbol", textContent: "♠"}
  257. } ]
  258. }, {
  259. node: "span", att: {"@class": "menu"},
  260. child: [ {
  261. node: "a", att: {"@class": "menuButton close", textContent: "X"},
  262. evl: {
  263. type: "click",
  264. f: () => {
  265. $("#firstrun").removeClass("appear");
  266. unsafeWindow.Config.settings.firstRunDisable = true;
  267. }}
  268. } ]
  269. } ]
  270. }, {
  271. node: "ul", att: {"@class": "settingslist"},
  272. child: [ {
  273. node: "h3", att: {"@class": "intro", textContent: "Spade Script was successfully installed and started"}
  274. }, {
  275. node: "li",
  276. child: ["- Press TAB to toggle the Server Selector."]
  277. }, {
  278. node: "li",
  279. child: ["- In the top right corner of a window is a X-Button to close it and O-Button so accesss aditional options."]
  280. }, {
  281. node: "li",
  282. child: ["- You are using the PUBLIC version with reduced features. Check out our Website/Discord for updates and please report any bugs."]
  283. }, {
  284. node: "li",
  285. child: ["- This info is only shown on the first start. Just close it and it won't appear again."]
  286. }
  287. ]
  288. } ]
  289. }, scriptBody);
  290. }
  291. });
  292.  
  293. $("body").after(scriptBody);
  294.  
  295. /* jshint ignore:start */
  296. const fetchServer = async (mode, times, ids = []) => {
  297. const url = "https://api.n.m28.io";
  298. const $serverSelect = $("#server");
  299. const $moreButton = $("#more");
  300. $moreButton.addClass("spin");
  301.  
  302. for (let i = 0; i < times; i++) {
  303. try {
  304. const response = await fetch(`${url}/endpoint/diepio-${mode}/findEach/`);
  305. const body = await response.json();
  306. if (body.hasOwnProperty("servers")) {
  307. Object.entries(body.servers).forEach(([key, val]) => {
  308. if (!ids.some((id) => {
  309. return id === val.id;
  310. })) {
  311. ids.push(val.id);
  312. const txt = key.replace(/(linode-|vultr-)/, "") + ` - ${val.id.toUpperCase()}`;
  313. $serverSelect.append($("<option>", {
  314. "value": JSON.stringify(val),
  315. "text": capitalizeFirstLetter(txt)
  316. }));
  317. }
  318. });
  319. }
  320. } catch (err) {
  321. console.error(err);
  322. }
  323. }
  324. $("#server option").detach().sort((a, b) => {
  325. a = $(a);
  326. b = $(b);
  327. return ((a.text() > b.text()) ?
  328. 1 :
  329. (a.text() < b.text()) ?
  330. -1 :
  331. 0);
  332. }).appendTo($serverSelect).filter(":first").attr("selected", true);
  333. $moreButton.on("click", () => {
  334. fetchServer(mode, 4, ids);
  335. }).removeClass("spin");
  336. };
  337. /* jshint ignore:end */
  338.  
  339. $(() => {
  340. const $gamemode = $("#gamemode");
  341. Object.entries(unsafeWindow.Config.gameModeName).forEach(([key, val]) => {
  342. $gamemode.append($("<option>", {
  343. "value": key,
  344. "text": val
  345. }));
  346. });
  347. $gamemode.change((event) => {
  348. $("#server").empty();
  349. fetchServer($(event.currentTarget).val(), 8, []);
  350. }).trigger("change");
  351. });
  352.  
  353. $(() => {
  354. unsafeWindow.m28n.findServerPreference = (endpoint, options, cb) => {
  355. if (unsafeWindow.m28nOverride)
  356. options(null, [JSON.parse($( "#server option:selected" ).val())]);
  357. if (typeof options == "function") {
  358. cb = options;
  359. options = {};
  360. }
  361. unsafeWindow.m28n.findServers(endpoint, (err, r) => {
  362. if (err)
  363. return cb(err);
  364. var availableRegions = [];
  365. for (var region in r.servers) {
  366. availableRegions.push(region);
  367. }
  368. if (availableRegions.length === 0) {
  369. cb("Couldn't find any servers in any region");
  370. return;
  371. }
  372. if (availableRegions.length === 1) {
  373. for (var region in r.servers) {
  374. cb(null, [r.servers[region]]);
  375. return;
  376. }
  377. }
  378. unsafeWindow.m28n.findRegionPreference(availableRegions, options, (err, regionList) => {
  379. if (err)
  380. return cb(err);
  381. var serverList = regionList.map((region) => {
  382. return r.servers[region];
  383. });
  384. cb(null, serverList);
  385. });
  386. });
  387. };
  388. });
  389.  
  390. const connectServer = () => {
  391. if ($("#server option:selected").length === 1) {
  392. const $autojoin = $("#autojoin");
  393. const $connect = $("#connect");
  394.  
  395. let Observer = new MutationObserver(mutation => {
  396. mutation.forEach(mutation => {
  397. if (mutation.target.style.display === "block") {
  398. if ($autojoin.prop("checked")) {
  399. const sequence = ["keydown", "keyup"];
  400. sequence.forEach(event => {
  401. $(canvas).trigger($.Event(event, {
  402. "keyCode": "\r".charCodeAt(0)
  403. }));
  404. });
  405. $(".appear").removeClass("appear");
  406. }
  407. $connect.removeClass("connecting");
  408. } else if (mutation.target.style.display === "none") {
  409. if (playing()) {
  410. Observer.disconnect();
  411. }
  412. unsafeWindow.m28nOverride = false;
  413. }
  414. });
  415. });
  416. $connect.addClass("connecting");
  417. unsafeWindow.m28nOverride = true;
  418. unsafeWindow.input.execute("lb_reconnect");
  419. Observer.observe($("#textInputContainer").get(0), {
  420. "attributes": true,
  421. "attributeFilter": ["style"]
  422. });
  423. }
  424. };
  425.  
  426. const WebSocketProxy = new Proxy(unsafeWindow.WebSocket, {
  427. construct (Target, args) {
  428. const instance = new Target(...args);
  429.  
  430. const messageHandler = (event) => {
  431. const buffer = new DataView(event.data);
  432. const opcode = buffer.getUint8(0);
  433. switch (opcode) {
  434. case 4:
  435. if (typeof unsafeWindow.Config.script.currentServer === "object") {
  436. const decoded = new TextDecoder("utf-8").decode(event.data);
  437. unsafeWindow.Config.script.currentServer = (/\W*(\w+).?((linode|vultr)-(\w+))/).exec(decoded);
  438. unsafeWindow.Config.script.currentServer[4] = capitalizeFirstLetter(unsafeWindow.Config.script.currentServer[4]);
  439. }
  440. break;
  441. default:
  442. break;
  443. }
  444. };
  445.  
  446. instance.addEventListener("message", messageHandler);
  447. return instance;
  448. }
  449. });
  450.  
  451. unsafeWindow.WebSocket = WebSocketProxy;
  452.  
  453. const drawServer = () => {
  454. const x = window.innerWidth * window.devicePixelRatio / 2;
  455. const y = window.innerHeight * window.devicePixelRatio * 0.575;
  456. if (unsafeWindow.Config.script.currentServer.length === 5) {
  457. ctx.textAlign = "center";
  458. ctx.font = "25px Ubuntu";
  459. ctx.lineWidth = 5;
  460. ctx.strokeStyle = "rgba(0, 0, 0, 1)";
  461. ctx.strokeText("Server:", x, y);
  462. ctx.fillStyle = "rgba(255, 255, 255, 1)";
  463. ctx.fillText("Server:", x, y);
  464.  
  465. ctx.font = "35px Ubuntu";
  466. ctx.lineWidth = 5;
  467. ctx.strokeStyle = "rgba(0, 0, 0, 1)";
  468. ctx.strokeText(unsafeWindow.Config.script.currentServer[2], x, y + 45);
  469. ctx.fillStyle = "rgba(255, 255, 255, 1)";
  470. ctx.fillText(unsafeWindow.Config.script.currentServer[2], x, y + 45);
  471. }
  472. };
  473.  
  474. unsafeWindow.requestAnimFrame = (function () {
  475. return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame ||
  476. window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback, element) {
  477. window.setTimeout(callback, 1000 / 60);
  478. };
  479. })();
  480.  
  481. $(window).on("load", function animate () {
  482. if ($("#textInputContainer").css("display") === "block" && !playing()) {
  483. drawServer();
  484. }
  485. unsafeWindow.requestAnimFrame(animate);
  486. });
  487.  
  488. const handleKeypress = (event) => {
  489. const key = String.fromCharCode(event.keyCode);
  490. switch (key) {
  491. case unsafeWindow.Config.hotkey.connectUI:
  492. event.preventDefault();
  493. event.stopPropagation();
  494. $("#main").toggleClass("appear");
  495. break;
  496. }
  497. };
  498.  
  499. $(document).keydown((event) => {
  500. handleKeypress(event);
  501. });
  502.  
  503. })();