Code Injector - Bonk.io

Allows different userscripts to define functions that modify the game's code

// ==UserScript==
// @name         Code Injector - Bonk.io
// @version      1.0.4
// @description  Allows different userscripts to define functions that modify the game's code
// @author       Excigma & kklkkj
// @namespace    https://greasyfork.org/users/416480
// @license      GPL-3.0
// @match        https://bonk.io/gameframe-release.html
// @run-at       document-body
// @grant        none
// ==/UserScript==

// What this does:
// - Finds other userscripts that have defined their injectors in `window.bonkCodeInjectors`, and runs them

// Much of the code below was copied from or inspired by https://github.com/kklkkj/kklee/blob/master/src/runInjectors.js
// Credits to https://github.com/kklkkj for creating this system where multiple
// extensions can edit different parts of Bonk.io' code.

// This script was originally made to inject kklkkj's kklee as a userscript

// Go thank kklkkj for this

(function () {
	// Original `document.head.appendChild` function that we're going to overwrite
	const _appendChild = document.head.appendChild;
	const log = (msg) => console.log(`%c[Injector] ${msg}`, "color: #06c26d");

	// When RequireJS tries to add alpha2s.js to <head> we will intercept it,
	// and patch it using functions defined by other scripts in window.bonkCodeInjectors
	document.head.appendChild = function (...args) {
		// `args?.[0]?.src.includes("alpha2s.js")` is more difficult to read
		if (args[0] && args[0]?.tagName === "SCRIPT" && args[0]?.src?.includes("alpha2s.js")) {
			// Store the url of the original unmodified alpha2s.js
			// Add ? to prevent double injection
			const code_url = args[0].src + "?";

			// Remove the src attribute so it doesn't try to load the original alpha2s.js script
			args[0].removeAttribute("src");

			(async function () {
				// Fetch alpha2s.js
				log("Fetching alpha2s.js...");
				const request = await fetch(code_url).catch(error => console.error(error));

				// Error fetching alpha2s.js (for example http 404)
				if (!request.ok) {
					log("Failed to fetch alpha2s.js");
					alert("An error occurred whilst fetching game code");

					// Fallback to what bonk.io would normally do
					args[0].src = code_url;
					return _appendChild.apply(document.head, args);
				}

				let code = await request.text();

				log("Patching alpha2s.js...");
				const start_time = performance.now();

				// No bonkCodeInjectors found, this might mean that the user does not have any bonk userscripts installed
				// or that they failed to load before this script

				// I removed the alert because people who prefer userscripts over extensions likely
				// enjoy the flexibility of being able to disable userscripts easily
				// and it's possible that they will have all their userscripts disabled at one time

				if (!window.bonkCodeInjectors) {
					// Still log to the console
					log(
						"Did not find any Bonk.io userscripts to load. This may be an error, make sure you have scripts installed."
					);
				} else {
					// Loop through `bonkCodeInjectors` and pass alpha2s.js' code in for them to modify
					let error_notified = false;
					for (const injector of window.bonkCodeInjectors) {
						try {
							// Run injector from other userscripts
							if (typeof injector === "function") code = injector(code);
							else {
								log("Injector was not a function");
								console.log(injector);
							}
							// I could check if code === injector(code); and throw an error if the code
							// did not change, but Salama also does in their userscripts which would caue
							// a double up in alert()s, which is not very nice
						} catch (error) {
							// Only notify the user once if any userscript fails to load
							// helpful to prevent spamming alerts()
							
							if (!error_notified) {
								// An injector from one of the other userscripts failed to load
								alert("One of your Bonk.io userscripts was unable to be loaded");
								error_notified = true;
							}

							console.error(error);
						}
					}
				}

				const end_time = performance.now();
				log(`Patched alpha2s.js successfully (${(end_time - start_time).toFixed(0)}ms)`);

				// Add the new script to the <script>'s contents
				args[0].textContent = code;

				// Make RequireJS think that the script loaded
				args[0].dispatchEvent(new Event("load"));

				// Append the modified <script> tag to document.head
				return _appendChild.apply(document.head, args);
			})().catch(error => {
				// Error patching alpha2s.js somewhere
				log("Failed to inject code into alpha2s.js");
				console.error(error);
				alert("An error occurred whilst patching game code");

				// Fallback to what bonk.io would normally do without this userscript
				args[0].src = code_url;
				return _appendChild.apply(document.head, args);
			});
		} else {
			return _appendChild.apply(document.head, args);
		}
	};
})();