Advanced Agma

clickable chat links, discord in game chat (/discord ... hi), remove specific animations, remove food completely (toggle mouse), advanced user stats (agma.io/stats.php), increase number of stackable animations

  1. // ==UserScript==
  2. // @name Advanced Agma
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1.3
  5. // @author Big watermelon (credits: Nersai, Vintrex)
  6. // @description clickable chat links, discord in game chat (/discord ... hi), remove specific animations, remove food completely (toggle mouse), advanced user stats (agma.io/stats.php), increase number of stackable animations
  7. // @match https://agma.io/*
  8. // @match https://discord.com/*
  9. // @require https://cdn.jsdelivr.net/npm/pako@2.0.4/dist/pako.min.js
  10. // @license GPL-3.0-or-later
  11. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAMAAADDpiTIAAAC9FBMVEUAAAC9vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb0VZcATXrgNR6ENR6EVZcAVZcAVZcAVZcARVrEOSaMNR6ENR6ENR6ENR6EVZcAVZcAVY74QUq0NR6ENR6EVZcATXrgOS6UNR6ENR6G9vb0RVK+9vb0VZcANR6ESWrQNR6EVZcAUX7oPTacNR6EVZcAQUKu0tLSrq6uwsLAVZcC5ubmenp5+fn51dXV6enqQkJCTp74qcMCnp6ezuL1Uhr8NR6GVlZWioqKJor4ga8CMjIyosr01dr+Dg4PGxsbJycnDw8NfjL/c3Nzu7u7o6Oh+nL7MzMzr6+vi4uKerb7W1tZKgb91dXV1dXXS0tLl5eXAwMBpkb8PT6l1dXV1dXWZmZmJiYl1dXV1dXV1dXV1dXV1dXV1dXXZ2dk/e791dXV1dXVvcniusbdbaoAUSp4YTqOGmLTf398SWLJIYYhEbKqytruHh4cRV7IUYbzPz88VZcCRoLZae60uXaanrroVZcA5ZagjVqWcp7hwibFPc6wWaMMad9MbftoYcc0Xa8YdhOAhlvMTXLZ7kbMgkO0VZcAcgd0NR6EVZcAXbsofjekaetYdh+N1dXUeiuYgk/AZdNBlgq+ZmZm9vb2apbcNR6Gosr2yt70ha8Cbq751l77/wQf/wQf/wQdTfo//wQf/wQf/wQfFqjVQfJL/wQf/wQe2pEEycamKk2Qka7Tith5egobwuxN7jW//wQf/wQdtiHuZmVinn0zTsCpBdp0PTqgSWbQjabOHhlbwuRErVo6kk0FKZnvDoy5FY33/wQccT5d3fF6GhFT/wQf/wQcQUasOSaMOSqQOSaMOSqQPTacvX6goWaVpaWmPj4+bm5usrKxhYWFhYWFnZ2eEhISgoKBhYWFtbW23t7d4eHhhYWFhYWGysrJhYWFhYWFycnJhYWFhYWGGhoZnZ2dhYWGpqalhYWGMjIx6enphYWFhYWFhYWFhYWFhYWFiRaieAAAA/HRSTlMAMIC/3//Pn2AQr0DvjzBAQBAQcM//////v49QQO///++fj///32BQ/3CvIP+AgP//zyD/////UP//////////////MP/////////////////////////////fv///////nzAgr89AYI/vIP//EK////////////////+v//9g/////5////////////////////+//3Df/////4D//////yBQr4Dv3+9wEFCA74/v////MN///////////3C///////+PUP/////////PYP///59AMM+P769Q/8+v////QP////+/////IJ//EO//YHDv71Dfj+/vzzDfgK9Air+ZAAAYg0lEQVR4AezBtQHDMAAAsMRM/79bmkprJks6AAAAAAAAAAC41hliyi+ltn6wlR5S/jDmwTZ6yL/iPNjDXPmvOzt3oeTGDkQBtAMdRq8VK2R2mDkxhJk83jAzMzMv/PWCrDGM3LJf0ZO2+vzCvSW1BjQX7ObNX7AQvMcmI2UaPQrE+uJCiEUJuXjJUmA+m460OZOgs2XLhZJMSSnTmQUw8XD+dANiWdGQy8sxhRXAPM6fNmclGObFRYvkKul/BTh/2gyI6FsulMgiIAs+DwOcP201tIqtEYZkSiqL14L3OH/TdGP6M6xLSGU9eI7ztzdggyBs3CSVzbwIeJ8//UBgS1yQklulkuZh0Nf8t23fvoNswOTJMybtXC5sFsm6zEJgzps5Ddvs2r2nOKZU3oakyl5htU7yNuBr/vtKxVB5F1L2HxBWuYRU0geBeZX/oWKL0mEkHTkqbDYmeBDwMP9dx4pa90Xg+ImeGiC5AR7lf7IYdTI6CZw6ffr0GVTOTuwGcP5KqfU8cKYaKLXqKRzTb90GctLrBnD+ZgPOnQ8aLoxX4KJ1G1jncwM4f7MBZ3T+2iVE3H9ZWKzyswGc/5WrV0sdGnAtaHf9BiLeFBa3pObRaZDzv30HAO7eKzbtQ+VSEFE71WUQSIaDYHotOIVNmkrkf/8B1D0shvYgUYDgvBoEjvYwCG5eCA5hk+YQ+T+ChjuPi3WHwhkg6NyA/Sd6GAMy4D7O//FDaPHkflHZjtrTwFC7Md6AZ4K0yaNBkPO/A22evyiOefkKQxcCw3Ucd0BQNnozBnD+959A1KNi8fWbt+9Qu/E+MFxCtB4GPkhtMTiN87/6HEwPP8oxnz6jVg0Mp3Bcv6Dc4k3A5fzxS3j6gw5iX6Xy9h09B1xAewO+SS29EJhz+ZeN8b/9u//cJinbFoHvQdRpVCpHyQ+EtCXAXM3/IXTwY3lLfm9/Gm8EtPeokA8Eknmp8Rzo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
  12. // @grant unsafeWindow
  13. // @grant GM_setValue
  14. // @grant GM_getValue
  15. // @run-at document-start
  16. // ==/UserScript==
  17.  
  18. /*
  19. FIXIT: token isnt always grabbed
  20. FIXIT: GM_ functions don't seem to work always so maybe use localStorage
  21. but that's kinda risky so maybe hash it with some values inside the script
  22. TODO: message edit => edit message
  23. TODO: message too long to fit in chat => wrap arround (well maybe not worth it)
  24. */
  25.  
  26. (function() {
  27. "use strict";
  28.  
  29. if (unsafeWindow.top !== unsafeWindow.self || document.querySelector("title")?.textContent?.includes("Just a moment"))
  30. return;
  31.  
  32. const settings = Object.assign({
  33. removeFood: true,
  34. removeAnimations: [3, 6, 7, 8, 11],
  35. maxStackableAnimations: 3,
  36. chatSize: 15,
  37. chatMaxRows: 12,
  38. discordChat: false,
  39. discordSavedChannels: [],
  40. discordPresence: false
  41. }, GM_getValue("settings", {}));
  42.  
  43. const discordToken = GM_getValue("discordToken", null);
  44. if (settings.discordChat && unsafeWindow.location.href.startsWith("https://discord.com")) {
  45. if (!discordToken) {
  46. GM_setValue("discordToken", JSON.parse(unsafeWindow.localStorage.token));
  47. unsafeWindow.alert("Discord Chat for agma.io has updated your token !");
  48. }
  49. return;
  50. }
  51. const numberFormat = Intl.NumberFormat("fr-FR");
  52. const userprofiles = unsafeWindow.localStorage.userprofiles ? JSON.parse(unsafeWindow.localStorage.userprofiles) : {};
  53. if (settings.discordChat && !discordToken) {
  54. unsafeWindow.alert("Discord Chat can not work since you didnt log in into your browser with discord.\nIf you want to use the discord chat feature wait for an alert saying that your token has been saved.\n\nhttps://discord.com");
  55. settings.discordChat = false;
  56. }
  57.  
  58. const discordPresence = {
  59. status: "online",
  60. since: 0,
  61. activities: [{
  62. name: "Agma.io",
  63. type: 0,
  64. url: "https://agma.io",
  65. details: "Playing Agma.io",
  66. timestamps: {
  67. start: Date.now()
  68. }
  69. }],
  70. afk: false
  71. };
  72.  
  73. const animations = {
  74. 1: { name: "Recombine", style: "border-color: #337ab7; background-image: url('./img/store/recombine-min.png'); padding-left: 30px; padding-left: 30px;" },
  75. 2: { name: "Cell Select", style: "border-color: #fe3f3f;" },
  76. 3: { name: "Spin", style: "border-color: #b3b3b3;" },
  77. 4: { name: "360 Shot", style: "border-color: #337ab7; background-image: url('./img/push_lo.png'); padding-left: 30px;" },
  78. 5: { name: "Level Up", style: "border-color: #fe3f3f;" },
  79. 6: { name: "Flip Spin", style: "border-color: #b3b3b3;" },
  80. 7: { name: "Flip", style: "border-color: #b3b3b3;" },
  81. 8: { name: "Shake", style: "border-color: #b3b3b3;" },
  82. 9: { name: "Explosion", style: "border-color: #f0ad4e; background-image: url('./emotes/1f4a5.png'); padding-left: 30px;" },
  83. 10: { name: "1st Medal", style: "border-color: #f0ad4e;" },
  84. 11: { name: "Jump", style: "border-color: #b3b3b3;" },
  85. 12: { name: "Wacky", style: "border-color: #f0ad4e; background-image: url('./emotes/1f61c.png'); padding-left: 30px;" },
  86. 13: { name: "White cell for 1 frame", style: "border-color: #fe3f3f;" },
  87. 14: { name: "Freeze", style: "border-color: #337ab7; background-image: url('./img/inv_freeze2.png'); padding-left: 30px;" },
  88. 15: { name: "Speed", style: "border-color: #337ab7; background-image: url('./img/store/speed-min.png'); padding-left: 30px;" },
  89. 16: { name: "Idk", style: "border-color: #fe3f3f;" }, // weird nothing
  90. 17: { name: "Upgrade", style: "border-color: #fe3f3f;" },
  91. 18: { name: "Snowball", style: "border-color: #fe3f3f;" },
  92. 20: { name: "Anti freeze", style: "border-color: #337ab7; background-image: url('./skins/objects/20.png'); padding-left: 30px;" },
  93. 21: { name: "Anti recombine", style: "border-color: #337ab7; background-image: url('./skins/objects/21.png'); padding-left: 30px;" },
  94. 23: { name: "Shield", style: "border-color: #337ab7; background-image: url('img/inv_shield5.png'); color: rgba(82, 152, 203, 0.6); padding-left: 30px;" },
  95. 24: { name: "Shield", style: "border-color: #337ab7; background-image: url('img/inv_shield5.png'); color: rgba(84, 211, 77, 0.6); padding-left: 30px;" },
  96. 25: { name: "Shield", style: "border-color: #337ab7; background-image: url('img/inv_shield5.png'); color: rgba(243, 46, 46, 0.6); padding-left: 30px;" },
  97. 26: { name: "Shield", style: "border-color: #337ab7; background-image: url('img/inv_shield5.png'); color: rgba(127, 59, 227, 0.6); padding-left: 30px;" },
  98. 30: { name: "Wave", style: "border-color: #f0ad4e; background-image: url('./emotes/1f44b.png'); padding-left: 30px;" },
  99. 31: { name: "Head Explosion", style: "border-color: #f0ad4e; background-image: url('./emotes/1f61cd.png'); padding-left: 30px;" },
  100. 32: { name: "Hearts Face", style: "border-color: #f0ad4e; background-image: url('./emotes/1f60d.png'); padding-left: 30px;" },
  101. 41: { name: "Angry Pumpkin", style: "border-color: #f0ad4e; background-image: url('./emotes/angry_emote3.png'); padding-left: 30px;" },
  102. 42: { name: "Scared Pumpkin", style: "border-color: #f0ad4e; background-image: url('./emotes/scared_emote.png'); padding-left: 30px;" },
  103. 43: { name: "Yawn Pumpkin", style: "border-color: #f0ad4e; background-image: url('./emotes/yawn_emote.png'); padding-left: 30px;" },
  104. 44: { name: "Throwup", style: "border-color: #f0ad4e; background-image: url('./emotes/throwup.png'); padding-left: 30px;" },
  105. 45: { name: "Hot face", style: "border-color: #f0ad4e; background-image: url('./emotes/hotface.png'); padding-left: 30px;" },
  106. 46: { name: "Tears Joy", style: "border-color: #f0ad4e; background-image: url('./emotes/tearsjoy.png'); padding-left: 30px;" },
  107. 47: { name: "No No", style: "border-color: #f0ad4e; background-image: url('./emotes/nonu.png'); padding-left: 30px;" },
  108. 48: { name: "Clap", style: "border-color: #f0ad4e;" },
  109. 49: { name: "Crying", style: "border-color: #f0ad4e;" },
  110. 50: { name: "Devil Smile", style: "border-color: #f0ad4e;" },
  111. 51: { name: "Eatman", style: "border-color: #f0ad4e;" },
  112. 52: { name: "Trophy", style: "border-color: #f0ad4e;" },
  113. 53: { name: "Hearts", style: "border-color: #f0ad4e;" }
  114. };
  115. const animationsIds = [
  116. 1, 4, 14, 15, 20, 21, 23, 24, 25, 26, // Powers
  117. 3, 6, 7, 8, 11, // Annoying
  118. 9, 10, 12, 30, 31, 32, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, // Emotes
  119. 2, 5, 13, 16, 17, 18 // System
  120. ];
  121.  
  122. const discordIcon = new Image();
  123. discordIcon.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAMAAADTRh9nAAAAS1BMVEVHcExYZfJYZfJYZfJYZfJYZfJYZfJYZfJYZfJYZfJYZfJYZfJYZfJYZfJYZfJYZfJYZfJYZfJYZfJYZfJYZfJYZfJYZfJYZfJYZfLaTvQiAAAAGHRSTlMAGTDyq+e2/N/4CMgjf1vXSTaJZ5HSEHVuv+kNAAAAqElEQVQY02VQSRLDMAgjXvGSxGvC/19acNpDphwQyIA0BgDYDoN7Virv1hwbrFApEGm0FjVRSEq4aOgVJjJZwpsM5X9wjVYrhX/6BbbCLuiOxiK6HU66HQQsK/IVA5BlzwFyPtncIBps+eQWQYtgj2rym4pdrARYt3WSBcKkl9wivf/ZeSpwDDgc6hA0usEb3sHVp6eUt1pK3XIiP/sln3TPBt9o82YnH6drET/OdjF6AAAAAElFTkSuQmCC";
  124. const urlRegex = /https?:\/\/[^\s]+/g;
  125. const invisibleChar = "᠋";
  126. const wordsToReplace = ["https", "www", ".gg", ".com", ".io", ".net", ".biz", "miracle", "palestine"].map(word => [
  127. new RegExp(`\\b(${word})\\b`, "gi"),
  128. word[0] + invisibleChar + word.slice(1)
  129. ]);
  130.  
  131. var discordUsername;
  132. var discordUserContext = null;
  133.  
  134. var discordWebSocket;
  135. var discordHeartbeatInterval;
  136. var discordReconnectRetryCount = 0;
  137. const discordGatewayUrl = "wss://gateway.discord.gg/?v=9&encoding=json";
  138. const sentMessagesIds = [];
  139. const originalSend = WebSocket.prototype.send;
  140. const sendWebsocketDiscordMessage = (op, d) => originalSend.call(discordWebSocket, JSON.stringify({ op, d }));
  141.  
  142. var chtTabs;
  143. var currentTab = '';
  144. var localChatMessages;
  145. var cellProtoOverwritten = false;
  146.  
  147. function initDiscordWebSocket() {
  148. discordWebSocket = new WebSocket(discordGatewayUrl);
  149. discordWebSocket.onopen = () => console.debug("[🔵] Connected");
  150. discordWebSocket.onmessage = ({ data }) => {
  151. if (data instanceof Blob) {
  152. const reader = new FileReader();
  153. reader.onload = () => {
  154. handleMessage(JSON.parse(pako.inflate(new Uint8Array(reader.result), { to: 'string' })));
  155. };
  156. reader.readAsArrayBuffer(data);
  157. } else {
  158. handleMessage(JSON.parse(data));
  159. }
  160. };
  161. discordWebSocket.onclose = () => {
  162. console.debug("[🔵] Disconnected");
  163. clearInterval(discordHeartbeatInterval);
  164. if (++discordReconnectRetryCount < 5) {
  165. initDiscordWebSocket();
  166. }
  167. };
  168. }
  169. function createDiscordChatMessage(message) {
  170. if (!message.channel_id || ![0, 19, 1, 2, 3, 6].includes(message.type) || sentMessagesIds.includes(message.id)) {
  171. return;
  172. }
  173. const channel = settings.discordSavedChannels.find(channel => channel.id == message.channel_id);
  174. if (!channel) {
  175. return;
  176. }
  177. sentMessagesIds.push(message.id);
  178. console.debug("[🔵] New message:", message);
  179. const tab = chtTabs.querySelector(`div[data-username="discord-${channel.id}"]`);
  180. if (!tab) {
  181. newDiscordTab(channel.name, channel.id);
  182. } else if (!tab.classList.contains('selected')) {
  183. tab.classList.add('blink');
  184. setTimeout(() => tab.classList.remove('blink'), 2000);
  185. }
  186. const author = message.author.global_name || message.author.username;
  187. switch (message.type) {
  188. case 0: // DEFAULT
  189. case 19: // REPLY
  190. var content = message.content || '';
  191. message.mentions.forEach(mention => content = content.replaceAll(
  192. `<@${mention.id}>`,
  193. mention.global_name || mention.username
  194. ));
  195. if (message.attachments.length) {
  196. content += ` +${message.attachments.length} files`;
  197. }
  198. localChatMessages.push({
  199. O: true,
  200. get v() {
  201. discordUserContext = this.discordAuthor;
  202. return -1;
  203. },
  204. o: 0,
  205. P: 0,
  206. U: 0,
  207. name: author,
  208. G: "discord-" + channel.id,
  209. color: discordUsername == message.author.username ? "#313338" : "#5662E9",
  210. message: content,
  211. category: 2,
  212. goldMember: 0,
  213. L: 0,
  214. Y: 0,
  215. q: 0,
  216. j: 0,
  217. J: 0,
  218. time: Date.now(),
  219. _cache: null,
  220. get cache() {
  221. return this._cache;
  222. },
  223. set cache(value) {
  224. value.icons = [ 14 ];
  225. return this._cache = value;
  226. },
  227. discordAuthor: message.author
  228. });
  229. break;
  230. case 1: // RECIPIENT_ADD
  231. case 2: // RECIPIENT_REMOVE
  232. const member = message.mentions[0];
  233. localChatMessages.push({
  234. O: false,
  235. v: 0,
  236. o: 0,
  237. P: 0,
  238. U: 0,
  239. name: '',
  240. G: "discord-" + channel.id,
  241. color: message.type == 1 ? "#00FF00" : "#FF0000",
  242. message: `${message.type == 1 ? "➡️" : "⬅️"} ${member.global_name || member.username} ${message.type == 1 ? "joined" : "left"} the group`,
  243. category: 2,
  244. goldMember: 0,
  245. L: 0,
  246. Y: 0,
  247. q: 0,
  248. j: 0,
  249. J: 0,
  250. time: Date.now(),
  251. _cache: null,
  252. get cache() {
  253. return this._cache;
  254. },
  255. set cache(value) {
  256. value.icons = [ 14 ];
  257. Object.defineProperties(value, {
  258. ie: { get: () => this.message },
  259. color2: { get: () => this.color }
  260. });
  261. return this._cache = value;
  262. }
  263. });
  264. break;
  265. case 3: // CALL
  266. localChatMessages.push({
  267. O: false,
  268. v: 0,
  269. o: 0,
  270. P: 0,
  271. U: 0,
  272. name: '',
  273. G: "discord-" + channel.id,
  274. color: "#00FF00",
  275. message: `${author} started a call`,
  276. category: 2,
  277. goldMember: 0,
  278. L: 0,
  279. Y: 0,
  280. q: 0,
  281. j: 0,
  282. J: 0,
  283. time: Date.now(),
  284. _cache: null,
  285. get cache() {
  286. return this._cache;
  287. },
  288. set cache(value) {
  289. value.icons = [ 14 ];
  290. Object.defineProperties(value, {
  291. ie: { get: () => this.message },
  292. color2: { get: () => this.color }
  293. });
  294. return this._cache = value;
  295. }
  296. });
  297. break;
  298. case 6: // CHANNEL_PINNED_MESSAGE
  299. // message_reference: { message_id: "1259902171630145651", channel_id: "853556382417420298" }
  300. localChatMessages.push({
  301. O: false,
  302. v: 0,
  303. o: 0,
  304. P: 0,
  305. U: 0,
  306. name: '',
  307. G: "discord-" + channel.id,
  308. color: "#00FF00",
  309. message: `${author} pinned a message`,
  310. category: 2,
  311. goldMember: 0,
  312. L: 0,
  313. Y: 0,
  314. q: 0,
  315. j: 0,
  316. J: 0,
  317. time: Date.now(),
  318. _cache: null,
  319. get cache() {
  320. return this._cache;
  321. },
  322. set cache(value) {
  323. value.icons = [ 14 ];
  324. Object.defineProperties(value, {
  325. ie: { get: () => this.message },
  326. color2: { get: () => this.color }
  327. });
  328. return this._cache = value;
  329. }
  330. });
  331. break;
  332. }
  333. }
  334. function handleMessage(message) {
  335. const { op, t, d } = message;
  336. switch (op) {
  337. case 10: // Hello
  338. console.debug("[🔵] Connection accepted");
  339. discordHeartbeatInterval = setInterval(() => sendWebsocketDiscordMessage(1, null), d.heartbeat_interval);
  340. sendWebsocketDiscordMessage(2, {
  341. token: discordToken,
  342. capabilities: 30717,
  343. properties: {}
  344. });
  345. settings.discordPresence && setTimeout(() => sendWebsocketDiscordMessage(3, discordPresence), 2000);
  346. break;
  347. case 0: // Dispatch
  348. switch (t) {
  349. case "MESSAGE_CREATE":
  350. createDiscordChatMessage(d);
  351. break;
  352. case "READY":
  353. discordUsername = d.user?.username || discordUsername;
  354. console.debug("[🔵] Logged as", discordUsername);
  355. break;
  356. // default:
  357. // console.debug("[🔵] Unhandled message type:", t);
  358. }
  359. break;
  360. case 1: // HEARTBEAT
  361. sendWebsocketDiscordMessage(1, null);
  362. break;
  363. case 11: // HEARTBEAT_ACK
  364. break;
  365. default:
  366. console.debug("[🔵] Unhandled opcode type:", op);
  367. }
  368. }
  369. function findUrlToOpen(event) {
  370. const x = event.clientX - event.target.offsetLeft,
  371. y = event.clientY - event.target.offsetTop;
  372. for (const message of localChatMessages) {
  373. if (message.cache && message.cache.urls) {
  374. for (const { url, rect } of message.cache.urls) {
  375. if (x >= rect.x0 && y >= rect.y0 && x <= rect.x1 && y <= rect.y1) {
  376. unsafeWindow.open(url, '_blank');
  377. }
  378. }
  379. }
  380. }
  381. }
  382. function timeFormat(s) {
  383. const h = Math.floor(s / 3600);
  384. const m = Math.floor(s % 3600 / 60);
  385. return (h ? h + 'h ' : '') + (m ? m + 'm ' : '') + Math.floor(s % 3600 % 60) + 's';
  386. }
  387. function getTable(payload) {
  388. if (payload == null)
  389. return "<table style=\"width: 100%; text-align: left;\"><tr><td>Player not registered</td><tr><td>You need to play at least 1 game of batle royale to be registered on <a href=\"https://agma.io/stats.php\" target=\"_blank\">Agma Stats</a></td></tr></table>";
  390. return (
  391. "<table style=\"width: 100%; text-align: left;\"><tr><td>Players Consumed: </td><td>"
  392. + numberFormat.format(payload.players_consumed)
  393. + "</td></tr><tr><td>Death Count: </td><td>"
  394. + numberFormat.format(payload.death_count)
  395. + "</td></tr><tr><td>K/D: </td><td>"
  396. + (payload.players_consumed / payload.death_count).toFixed(2)
  397. + "</td></tr><tr><td>Splits count: </td><td>"
  398. + numberFormat.format(payload.splits_count)
  399. + "</td></tr><tr><td>Total time alive: </td><td>"
  400. + timeFormat(payload.total_time_alive)
  401. + "</td></tr><tr><td>Splits per seconds: </td><td>"
  402. + (payload.splits_count / payload.total_time_alive).toFixed(2)
  403. + "</td></tr></table>"
  404. );
  405. }
  406.  
  407. let flag = false
  408. const originalMax = unsafeWindow.Math.max;
  409. unsafeWindow.Math.max = function(...args) {
  410. flag = false;
  411. if (args.length == 2) {
  412. if (args[1] == 8) {
  413. args[0] = settings.chatSize;
  414. } else if (args[1] == 3) {
  415. flag = true;
  416. }
  417. }
  418. return originalMax(...args);
  419. };
  420. const originalMin = unsafeWindow.Math.min;
  421. unsafeWindow.Math.min = function(...args) {
  422. if (flag && !(flag = false) && args.length == 2 && args[0] == 12)
  423. return settings.chatMaxRows;
  424. return originalMin(...args);
  425. };
  426. const originalDrawImage = CanvasRenderingContext2D.prototype.drawImage;
  427. unsafeWindow.CanvasRenderingContext2D.prototype.drawImage = function() {
  428. if (arguments[0] instanceof Image && arguments[0].src.startsWith("https://agma.io/img/chaticons") && arguments[1] == 280) {
  429. arguments[0] = discordIcon;
  430. arguments[1] = 0;
  431. arguments[6] += 3;
  432. }
  433. return originalDrawImage.apply(this, arguments);
  434. }
  435. const originalReplace = unsafeWindow.String.prototype.replace;
  436. unsafeWindow.String.prototype.replace = function(pattern, replacement) {
  437. return originalReplace.call(this, pattern, replacement instanceof Function && pattern instanceof RegExp && pattern.source.startsWith(":rolling") ? (match, offset) => match == ":/" && this?.[offset + 1] == '/' ? match : replacement(match, offset) : replacement);
  438. }
  439. const originalPush = unsafeWindow.Array.prototype.push;
  440. unsafeWindow.Array.prototype.push = function(elem) {
  441. if (elem?.time !== undefined && elem?.cache === null) {
  442. if (localChatMessages != this)
  443. unsafeWindow.localChatMessages = localChatMessages = this;
  444. if ((elem.message = elem.message.replaceAll(invisibleChar, "")).includes("https://")) {
  445. Object.defineProperty(elem, "cache", {
  446. get: function() {
  447. return this._cache;
  448. },
  449. set: function(cache) {
  450. if (elem._cache !== undefined) {
  451. cache.icons = [ 14 ];
  452. // see if this breaks calls and stuff but shouldnt
  453. }
  454. Object.defineProperty(cache, "ctx", {
  455. get: function() {
  456. return this._ctx;
  457. },
  458. set: function(ctx) {
  459. ctx.fillText = function(text, x, y) {
  460. if (this.fillStyle != "#f5f6ce" && this.fillStyle != "#444444") {
  461. return CanvasRenderingContext2D.prototype.fillText.call(this, text, x, y);
  462. }
  463. // ngl I asked chatgpt cuz Im lazy (and I edited some stuff)
  464. let match;
  465. let lastIndex = 0;
  466. let currentX = x;
  467. cache.urls = [];
  468. while ((match = urlRegex.exec(text)) != null) {
  469. const url = match[0];
  470. const urlStart = match.index;
  471. const urlEnd = urlStart + url.length;
  472.  
  473. const preText = text.slice(lastIndex, urlStart);
  474. if (preText) {
  475. CanvasRenderingContext2D.prototype.fillText.call(this, preText, currentX, y);
  476. currentX += ctx.measureText(preText).width;
  477. }
  478. ctx.save();
  479. ctx.fillStyle = '#1E90FF';
  480. CanvasRenderingContext2D.prototype.fillText.call(this, url, currentX, y);
  481.  
  482. const urlWidth = ctx.measureText(url).width;
  483. const textSize = parseInt(ctx.font.match(/\d+/), 10);
  484. const underlineY = y + 2;
  485.  
  486. ctx.beginPath();
  487. ctx.moveTo(currentX, underlineY);
  488. ctx.lineTo(currentX + urlWidth, underlineY);
  489. ctx.strokeStyle = '#1E90FF';
  490. ctx.lineWidth = 1;
  491. ctx.stroke();
  492. ctx.restore();
  493. cache.urls.push({
  494. url,
  495. rect: {
  496. x0: currentX,
  497. yd0: y - textSize,
  498. x1: currentX + urlWidth,
  499. yd1: y
  500. }
  501. });
  502. currentX += urlWidth;
  503. lastIndex = urlEnd;
  504. }
  505. const remainingText = text.slice(lastIndex);
  506. if (remainingText) {
  507. CanvasRenderingContext2D.prototype.fillText.call(this, remainingText, currentX, y);
  508. }
  509. }
  510. return this._ctx = ctx;
  511. },
  512. configurable: true
  513. });
  514. return this._cache = cache;
  515. },
  516. configurable: true
  517. });
  518. }
  519. } else if (elem?.ch !== undefined && elem?.x0 !== undefined) {
  520. elem.ch.cache?.urls?.forEach(urlObject => {
  521. urlObject.rect.y0 = urlObject.rect.yd0 + elem.y0;
  522. urlObject.rect.y1 = urlObject.rect.yd1 + elem.y0;
  523. });
  524. } else if (elem?.namePart !== undefined && elem?.clanPart !== undefined) {
  525. if (!cellProtoOverwritten) {
  526. if (settings.removeAnimations.length) {
  527. elem.constructor.prototype.ge = function(animation) {
  528. if (1 == this.a || settings.removeAnimations.includes(animation.H)) {
  529. return;
  530. }
  531. animation = { H: animation.H, K: animation.K, received: animation.received };
  532. if (this.Ne) {
  533. for (var i = 0; i < this.Ne.length; i++) {
  534. if (this.Ne[i].received > animation.received) {
  535. this.Ne.splice(i, 0, animation);
  536. if (this.Ne.length > settings.maxStackableAnimations) {
  537. this.Ne.splice(this.Ne.length - 2, 1);
  538. }
  539. return;
  540. }
  541. }
  542. if (this.Ne.length < settings.maxStackableAnimations) {
  543. originalPush.call(this.Ne, animation);
  544. } else {
  545. this.Ne[this.Ne.length - 1] = animation;
  546. }
  547. } else {
  548. this.Ne = [animation];
  549. }
  550. }
  551. }
  552. cellProtoOverwritten = true;
  553. }
  554. if (settings.removeFood && elem.Pe) { // elem.a === 1
  555. return 0;
  556. }
  557. }
  558. return originalPush.apply(this, arguments);
  559. }
  560.  
  561. unsafeWindow.setAnimation = (id, value) => settings.removeAnimations[settings.removeAnimations.includes(id) ? 'remove' : 'push'](id);
  562. unsafeWindow.setCustomChatSize = value => settings.chatSize = parseInt(value);
  563. unsafeWindow.setCustomChatMaxRows = value => settings.chatMaxRows = parseInt(value);
  564. unsafeWindow.setMaxStackableAnimations = value => settings.maxStackableAnimations = parseInt(value);
  565. unsafeWindow.setRemoveFood = value => settings.removeFood = value;
  566. unsafeWindow.setDiscordChat = value => {
  567. if (settings.discordChat = value) {
  568. !discordWebSocket && initDiscordWebSocket();
  569. } else if (discordWebSocket != null) {
  570. discordWebSocket.close();
  571. discordWebSocket = null;
  572. }
  573. };
  574. unsafeWindow.setDiscordPresenceChat = value => settings.discordPresence = value;
  575. unsafeWindow.newDiscordTab = (name, channel) => chtTabs && (chtTabs.innerHTML += `<div data-category="2" data-username="discord-${channel}" data-insert data-tooltip="Discord chat: ${name}" class="chat-tab semi-selected">${name}</div>`);
  576. /*
  577. // could make your own messages show up faster but too annoying so no
  578. createDiscordChatMessage({
  579. id: null,
  580. channel_id: channel,
  581. type: 0,
  582. author: discordUser,
  583. content
  584. });
  585. */
  586. unsafeWindow.sendDiscordMessage = (channel, content) => settings.discordChat && discordUsername && fetch(`https://discord.com/api/v9/channels/${channel}/messages`, {
  587. method: 'POST',
  588. headers: {
  589. 'Authorization': discordToken,
  590. 'Content-Type': 'application/json'
  591. },
  592. body: JSON.stringify({
  593. content,
  594. tts: false
  595. })
  596. })
  597. .then(async response => createDiscordChatMessage(await response.json()))
  598. .catch(error => console.error('[🔵] Error while trying to send a message:', error));
  599.  
  600. var loaded = false;
  601. unsafeWindow.addEventListener("load", () => {
  602. if (loaded || typeof swal == "undefined") return;
  603. loaded = true;
  604. const chtCanvas = document.getElementById("chtCanvas");
  605. chtCanvas.addEventListener("dblclick", findUrlToOpen);
  606. chtCanvas.addEventListener("click", event => event.ctrlKey && findUrlToOpen(event));
  607. document.getElementById("chtbox").addEventListener("keydown", function(event) {
  608. if (event.keyCode == 13) {
  609. if (!currentTab && this.value.startsWith("/discord ")) {
  610. const args = this.value.slice(9).split(' ');
  611. const channelName = args.shift();
  612. this.value = args.join(' ').trim();
  613. const channel = settings.discordSavedChannels.find(channel => channel.name == channelName);
  614. if (channel && !chtTabs.querySelector(`div[data-username="discord-${channel.id}"]`)) {
  615. newDiscordTab(channelName, channel.id);
  616. }
  617. if (this.value) {
  618. sendDiscordMessage(channel.id, this.value);
  619. this.value = '';
  620. }
  621. } else if (currentTab.startsWith("discord-")) {
  622. if (this.value = this.value.trim()) {
  623. sendDiscordMessage(currentTab.slice(8), this.value);
  624. this.value = '';
  625. }
  626. } else {
  627. for (const [reg, rep] of wordsToReplace) {
  628. this.value = this.value.replaceAll(reg, rep);
  629. }
  630. }
  631. }
  632. });
  633. // Script devs so annoying can't even use that for settings pages
  634. // typeof GM_info == "undefined" ? Date.now() : GM_info?.script?.position + 3;
  635. const settingPageId = Math.random() * 10e17;
  636. chtTabs = document.getElementById("chtTabs");
  637. chtTabs.addEventListener("click", () => setTimeout(() => currentTab = chtTabs.querySelector("div.chat-tab.selected").dataset.username, 1));
  638. $('#settingTab2').after(`<button id="settingTab${settingPageId}" class="setting-tablink" onclick="openSettingPage(${settingPageId});">Advanced</button>`);
  639. $('#settingPage3').after(`
  640. <div id="settingPage${settingPageId}" class="setting-tabcontent">
  641. <div class="col-md-10 col-md-offset-1 stng" style="padding-left:20px;padding-right:10px;max-height:550px;overflow-y:auto;overflow-x:hidden;margin:0;width:calc(100% - 5px);">
  642. <span style="margin:0;" class="hotkey-paragraph"> Animations</span>
  643. <div class="row stng-row" style="font-size:14px;">
  644. <div style="width:100%;padding:4px;">
  645. <div style="display:flex;flex-wrap:wrap;padding:0px;width:100%;">
  646. ${animationsIds.map(id => `<div style="${animations[id].style}" onclick="setAnimation(${id}, !this.classList.toggle('disabled'));" class="emote${settings.removeAnimations.includes(id) ? " disabled" : ''}">${animations[id].name}</div>`).join('')}
  647. </div>
  648. </div>
  649. </div>
  650. <span style="margin:0;" class="hotkey-paragraph"> Other</span>
  651. <div class="row stng-row" style="font-size:14px;">
  652. <label>
  653. <input type="number" min="1" value="${settings.chatSize}" onchange="setCustomChatSize(this.value);">
  654. <span> Custom Chat Size *</span>
  655. </label>
  656. <br>
  657. <label>
  658. <input type="number" min="1" value="${settings.chatMaxRows}" onchange="setCustomChatMaxRows(this.value);">
  659. <span> Custom Chat Max Rows *</span>
  660. </label>
  661. <br>
  662. <label>
  663. <input type="number" min="0" value="${settings.maxStackableAnimations}" onchange="setMaxStackableAnimations(this.value);">
  664. <span> Stackable Animations *</span>
  665. </label>
  666. <br>
  667. <label>
  668. <input type="checkbox" onchange="setRemoveFood(this.checked);" ${settings.removeFood ? " checked" : ''}>
  669. <span> Remove Food *</span>
  670. </label>
  671. </div>
  672. <span style="margin:0;" class="hotkey-paragraph"> Discord</span>
  673. <div class="row stng-row" style="font-size:14px;">
  674. <label>
  675. <input type="checkbox" onchange="setDiscordChat(this.checked);" ${settings.discordChat ? " checked" : ''}>
  676. <span> Discord Chat *</span>
  677. </label>
  678. <br>
  679. <label>
  680. <input type="checkbox" onchange="setDiscordPresenceChat(this.checked);" ${settings.discordPresence ? " checked" : ''}>
  681. <span> Discord Agma.io Presence *</span>
  682. </label>
  683. <br>
  684. <label>
  685. <span> Discord Saved Channels *</span>
  686. <textarea id="discordSavedChannels" style="resize: vertical; min-height: 100px; width: 100%;" placeholder="agma.io,942193976063197214\nname,channelId">${settings.discordSavedChannels.map(channel => channel.name + ',' + channel.id).join('\n')}</textarea>
  687. </label>
  688. </div>
  689. </div>
  690. </div>
  691. `);
  692. const discordSavedChannelsTextarea = document.getElementById("discordSavedChannels");
  693. discordSavedChannelsTextarea.addEventListener("blur", function(event) {
  694. settings.discordSavedChannels = [];
  695. for (const line of this.value.split('\n')) {
  696. let [name, id] = line.split(',');
  697. name = name?.trim();
  698. id = id?.trim();
  699. if (name && id) {
  700. settings.discordSavedChannels.push({ name, id });
  701. } else {
  702. swal('Invalid channels', 'Please ensure each line has exactly one comma separating the key and value.', 'error');
  703. break;
  704. }
  705. }
  706. });
  707. discordSavedChannelsTextarea.addEventListener("keydown", function(event) {
  708. event.stopPropagation();
  709. });
  710. const style = document.createElement("style");
  711. style.innerHTML = `
  712. #settingPage${settingPageId}#settingPage${settingPageId} input[type="number"]::-webkit-outer-spin-button,
  713. #settingPage${settingPageId} input[type="number"]::-webkit-inner-spin-button {
  714. -webkit-appearance: none;
  715. margin: 0;
  716. }
  717. #settingPage${settingPageId} input[type="number"] {
  718. -moz-appearance: textfield;
  719. }
  720. #settingPage${settingPageId} input[type="number"] {
  721. max-width: 30px;
  722. }
  723. div.chat-tab[data-username^="discord-"] {
  724. background: #4954c5;
  725. }
  726. #settingPage${settingPageId} > div::-webkit-scrollbar {
  727. width: 8px;
  728. height: 8px;
  729. }
  730. #settingPage${settingPageId} > div::-webkit-scrollbar-track {
  731. background: #282934;
  732. border-radius: 10px;
  733. }
  734. #settingPage${settingPageId} > div::-webkit-scrollbar-thumb {
  735. background-color: #df8500;
  736. border-radius: 10px;
  737. border: 2px solid #282934;
  738. }
  739. #settingPage${settingPageId} div.emote {
  740. min-width: 30px;
  741. height: 30px;
  742. padding: 3px;
  743. border-radius: 5px;
  744. border-width: 1px;
  745. border-style: solid;
  746. margin: 4px;
  747. background-size: contain;
  748. background-repeat: no-repeat;
  749. cursor: pointer;
  750. text-overflow: ellipsis;
  751. }
  752. #settingPage${settingPageId} div.emote.disabled {
  753. text-decoration: line-through;
  754. text-decoration-thickness: 3px;
  755. text-decoration-color: red;
  756. }
  757. `;
  758. document.body.appendChild(style);
  759.  
  760. const originalFind = $.prototype.find;
  761. unsafeWindow.$.prototype.find = function() {
  762. const res = originalFind.apply(this, arguments);
  763. if (
  764. discordUserContext
  765. && this.selector == "#contextMenu"
  766. && arguments?.[0] == "li.enabled.hover"
  767. && res?.length
  768. ) {
  769. if (res[0].id == "contextUserProfile") {
  770. swal({
  771. title: `<img src="https://cdn.discordapp.com/avatars/${discordUserContext.id}/${discordUserContext.avatar}" width="128" height="128" style="border-radius:50%;"><br><br><span>${discordUserContext.global_name || discordUserContext.username}</span><br><span style="font-size:12px;">${discordUserContext.username}</span>`,
  772. html: true
  773. });
  774. return [];
  775. } else if (res[0].id == "contextDiscordMention") {
  776. document.getElementById("chtbox").value += `<@${discordUserContext.id}> `;
  777. return [];
  778. }
  779. }
  780. return res;
  781. }
  782. const originalAddClass = $.prototype.addClass;
  783. unsafeWindow.$.prototype.addClass = function() {
  784. if (discordUserContext && this.selector == "#contextMute") {
  785. $("#contextDiscordMention").show();
  786. originalAddClass.call($("#contextUserProfile"), "enabled");
  787. $("#contextPlayerSkin").css({ 'background-image': `url(https://cdn.discordapp.com/avatars/${discordUserContext.id}/${discordUserContext.avatar})` });
  788. $("#contextPartyLeave").hide();
  789. $("#contextPartyMessage").hide();
  790. $("#contextModerate").hide();
  791. $("#contextPartyInvite").hide();
  792. $("#contextFriendAdd").hide();
  793. $("#contextPrivateMessage").hide();
  794. $("#contextSpectate").hide();
  795. $("#contextPickpocket").hide();
  796. $("#contextMute").hide();
  797. return this;
  798. }
  799. return originalAddClass.apply(this, arguments);
  800. }
  801. const originalHide = $.prototype.hide;
  802. unsafeWindow.$.prototype.hide = function() {
  803. if (discordUserContext && this.selector == "#contextMenu") {
  804. $("#contextDiscordMention").hide();
  805. $("#contextPartyLeave").show();
  806. $("#contextPartyMessage").show();
  807. $("#contextModerate").show();
  808. $("#contextPartyInvite").show();
  809. $("#contextFriendAdd").show();
  810. $("#contextPrivateMessage").show();
  811. $("#contextSpectate").show();
  812. $("#contextPickpocket").show();
  813. $("#contextMute").show();
  814. discordUserContext = null;
  815. }
  816. return originalHide.apply(this, arguments);
  817. }
  818. $("#contextUserProfile").after('<li id="contextDiscordMention" class="contextmenu-item enabled" style="display: none;"><div class="fa fa-at fa-2x context-icon"></div><p>Mention</p></li>');
  819.  
  820. let waitingResponse = false;
  821. const originalSwal = unsafeWindow.swal;
  822. unsafeWindow.swal = function() {
  823. if (
  824. !discordUserContext
  825. && typeof arguments[0] == "object"
  826. && "title" in arguments[0]
  827. && arguments[0].title.startsWith("<img src=\"")
  828. ) {
  829. if (waitingResponse) {
  830. return;
  831. }
  832. const username = arguments[0].title.match(/>([^>]+)<\/span>/)?.[1];
  833. if (!username) {
  834. return originalSwal.apply(this, arguments);
  835. }
  836. if (Date.now() - userprofiles[username]?.at < 86400000) {
  837. arguments[0].text += getTable(userprofiles[username].data);
  838. } else {
  839. waitingResponse = true;
  840. $.getJSON("https://agma.io/royale_stats.php?user=" + username, payload => {
  841. userprofiles[username] = { at: Date.now(), data: payload };
  842. arguments[0].text += getTable(payload);
  843. waitingResponse = false;
  844. originalSwal.apply(this, arguments);
  845. }).fail(() => {
  846. userprofiles[username] = { at: Date.now(), data: null };
  847. arguments[0].text += getTable(null);
  848. waitingResponse = false;
  849. originalSwal.apply(this, arguments);
  850. }).always(() => {
  851. waitingResponse = false;
  852. });
  853. return;
  854. }
  855. }
  856. return originalSwal.apply(this, arguments);
  857. }
  858. unsafeWindow.swal.close = originalSwal.close;
  859. });
  860. unsafeWindow.addEventListener("beforeunload", () => {
  861. discordWebSocket && discordWebSocket.close();
  862. GM_setValue("settings", settings);
  863. unsafeWindow.localStorage.userprofiles = JSON.stringify(userprofiles);
  864. });
  865. settings.discordChat && initDiscordWebSocket();
  866. console.log(`%cAdvanced Agma - ${GM_info?.script?.version} Loaded`, "font-weight: bold; font-size: 20pt; color: black;");
  867. })();