Copy Wardle Guesses

Copies a list of your Wardle guesses (spoilered for Discord) to your clipboard

// ==UserScript==
// @name           Copy Wardle Guesses
// @namespace      https://github.com/w4tchdoge
// @version        1.0.0-20240924_214547
// @description    Copies a list of your Wardle guesses (spoilered for Discord) to your clipboard
// @author         w4tchdoge
// @homepage       https://github.com/w4tchdoge/MISC-UserScripts
// @match          *://wardlegame.com/*
// @grant          GM_setClipboard
// @grant          GM.setClipboard
// @grant          GM_registerMenuCommand
// @grant          GM.registerMenuCommand
// @require        https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
// @license        AGPL-3.0-or-later
// @history        1.0.0 — Add button to the share widget that lets you copy your guesses spoilered for discord. Attempt to make the script more compatible with the Single Page Application-ness of Wardle
// @history        0.0.3 — cleanup/redo guesses_arr and guesses_spoilers
// @history        0.0.2 — Initial git commit
// ==/UserScript==

(async function () {
	`use strict`;

	// modified from https://stackoverflow.com/a/61511955/11750206
	function waitForElmXPATH(xpathstr) {
		return new Promise(resolve => {
			if (document.evaluate(xpathstr, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue) {
				return resolve(document.evaluate(xpathstr, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue);
			}

			const observer = new MutationObserver(mutations => {
				if (document.evaluate(xpathstr, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue) {
					observer.disconnect();
					resolve(document.evaluate(xpathstr, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue);
				}
			});

			// If you get "parameter 1 is not of type 'Node'" error, see https://stackoverflow.com/a/77855838/492336
			observer.observe(document.documentElement, {
				childList: true,
				subtree: true
			});
		});
	}

	function MAINfunk() {
		const guess_table = (() => {

			const elm_xp = `.//div[contains(concat(" ",normalize-space(@class)," ")," mb-2 ")]/parent::div[contains(concat(" ",normalize-space(@class)," ")," mt-2 ")][contains(@class,"w-11/12")]`;
			const elm = document.evaluate(elm_xp, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
			return elm;

		})();

		// guess_table;

		// let names_elm_arr = Array.from(guess_table.querySelectorAll(`div.vehicle-name`)).reverse();
		const names_txt_arr = Array.from(guess_table.querySelectorAll(`div.vehicle-name`), elm => elm.textContent.trim()).reverse();

		const nations_txt_arr = (() => {

			let txt_arr = [];
			const elm_arr = Array.from(guess_table.querySelectorAll(`span[class^="fi"]`)).reverse();

			elm_arr.forEach((elm, index, arr) => {

				const nation_code = elm.classList[1].slice(3);
				let nation_name;

				// ISO 3166-1 alpha3 codes from https://en.wikipedia.org/wiki/ISO_3166-1
				switch (nation_code) {
					case `it`:
						nation_name = `ITY`;
						break;

					case `jp`:
						nation_name = `JPN`;
						break;

					case `gb`:
						nation_name = `GBR`;
						break;

					case `de`:
						nation_name = `DEU`;
						break;

					case `se`:
						nation_name = `SWE`;
						break;

					case `us`:
						nation_name = `USA`;
						break;

					case `fr`:
						nation_name = `FRA`;
						break;

					case `ru`:
						nation_name = `RUS`;
						break;

					case `cn`:
						nation_name = `CHN`;
						break;

					case `il`:
						nation_name = `ISR`;
						break;

					default:
						throw new Error("ERROR: Unknown Nation Code!");
				}

				txt_arr.push(nation_name);

			});

			return txt_arr;

		})();

		if (names_txt_arr.length != nations_txt_arr.length) {
			throw new Error("ERROR: Size of nations array and names array do not match!");
		}

		// console.log(`Names arr length: ${names_txt_arr.length}\nNations arr length: ${nations_txt_arr.length}`);

		const guesses_arr = (() => {

			let output_arr = [];

			[...Array(names_txt_arr.length)].forEach((_, index) => {

				const input_obj = { guess_no: (index + 1), name: names_txt_arr.at(index), nation: nations_txt_arr.at(index) };
				output_arr.push(input_obj);

			});

			return output_arr;

		})();

		return guesses_arr;

	}

	function DiscordSpoilerGuessesToClipboard() {

		const guesses_arr = MAINfunk();
		const guesses_spoilers = (() => {

			let output_arr = [];

			guesses_arr.forEach((elm, index, arr) => {

				const spoiler_text = `${elm.guess_no}. ||${elm.name} **(${elm.nation})**||`;
				output_arr.push(spoiler_text);

			});

			return output_arr;

		})();
		const cliptext = `My guesses:\n${guesses_spoilers.join(`\n`)}`;
		GM.setClipboard(cliptext);
		console.log(`
Wardle Guesses have been copied to clipboard!
Guesses were copied in the Discord Spoilers format.
Clipboard should now be:

${cliptext}`);

	}

	const share_row = await waitForElmXPATH(`.//div[contains(concat(" ",normalize-space(@class)," ")," shareWidget ")]/div[contains(concat(" ",normalize-space(@class)," ")," flex-row ")][count(.//button[count(.//p[contains(concat(" ",normalize-space(@class)," ")," font-bold ")]) > 0][count(preceding-sibling::a) > 0 or count(following-sibling::a) > 0]) > 0]`);

	const userscript_share_row = Object.assign(share_row.cloneNode(false), {
		innerHTML: `<button class="flex flex-row items-center justify-center rounded-2xl bg-secondary-background p-3 text-text-color transition delay-100 duration-300 ease-in-out hover:scale-110 hover:bg-blue-600 md:p-4"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" class="mr-3 h-6 w-6 md:h-9 md:w-9 iconify iconify--mdi" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M19 3h-4.18C14.4 1.84 13.3 1 12 1s-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2m-7 0a1 1 0 0 1 1 1a1 1 0 0 1-1 1a1 1 0 0 1-1-1a1 1 0 0 1 1-1M7 7h10V5h2v14H5V5h2z"></path></svg><p class="font-bold tracking-wider md:text-lg">COPY GUESSES\n(DISCORD)</p></button>`
	});
	userscript_share_row.addEventListener(`click`, DiscordSpoilerGuessesToClipboard);

	share_row.after(userscript_share_row);

	GM.registerMenuCommand(`Copy Guesses to Clipboard`, DiscordSpoilerGuessesToClipboard);

})();