MediaWiki:Gadget-InfoboxChecker.js

From UnderMine Wiki
Jump to navigation Jump to search

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
/* ToDo:
 * - How to compare 'leveling' from Infobox-familiar?
 */
;(function(window, $, mw) {
	'use strict';
	
	const config = mw.config.get([
		'wgCanonicalSpecialPageName',
		'wgContentLanguage',
		'wgTitle'
	]);
	if (window.InfoboxCheckerLoaded || !(config.wgCanonicalSpecialPageName === 'Blankpage' && config.wgTitle.endsWith('/InfoboxChecker'))) return;
	window.InfoboxCheckerLoaded = true;
	var wArea;
	var rArea;
	var paused = true;
	var lastTimeout;
	var buttons;
	var OO;
	var links = {};
	var root = document.getElementById('mw-content-text');
	var sources = {};
	var locLines = {};
	var costs = {};
	var queryParams = {
		action: "query",
		format: "json",
		prop: "langlinks|pageprops",
		titles: "Pilfer",
		formatversion: 2,
		ppprop: "infoboxes",
		origin: "*"
	};
	var wikiApiUrl = 'https://undermine.wiki.gg/$lang/api.php';
	document.getElementById('firstHeading').textContent = 'Infobox checker';
	
	mw.loader.load([
		// main
		'jquery',
		'oojs-ui',
		'oojs-ui-core',
		'oojs-ui-widgets',
		// Icons
		'oojs-ui.styles.icons-alerts', // notice, error
		'oojs-ui.styles.icons-content', // history
		'oojs-ui.styles.icons-editing-advanced', // language
		'oojs-ui.styles.icons-interactions', // check
		'oojs-ui.styles.icons-media', // play, pause
		'oojs-ui.styles.icons-moderation', // trash
	]);
	mw.loader.using(['mediawiki.api']).then(function() {
		return new mw.Api().loadMessagesIfMissing([
			'pagetitle'
		], {amlang: config.wgContentLanguage});
	}).then(function() {
		document.title = mw.msg('pagetitle').replace('$1', 'Infobox checker');
	});
	
	root.innerHTML = '<div id="toolbar">' +
	'</div>' +
	'<div style="display:flex;margin-top:10px;background:rgba(var(--theme-accent-color--rgb),.7)">' +
	'<div id="leftArea"> <!-- Contains the list to process -->' +
	'<textarea id="pageList" rows="30" cols="50" disabled>' +
	'</textarea>' +
	'</div>' +
	'<div id="rightArea" style="display:none;"> <!-- Display logs in listview; Entries toggabled -->' +
	'</div>' +
	'<div id="watermarkArea">' +
	'</div>';
	var pageList = document.getElementById("pageList");
	function getHashCode(str) { // By MoatShrimp
		var h1 = 5381;
		var h2 = h1;
		
		for (var i = 0; i < str.length; i += 2) {
			h1 = (((h1 << 5) + h1)|0) ^ str.charCodeAt(i);
			if (i+1 == str.length) {break;}
			h2 = (((h2 << 5) + h2)|0) ^ str.charCodeAt(i+1);
		}
		
		return (h1 + Math.imul(h2, 1566083941))|0;
	}
	function loadLocFile(event) {
		var file = event.target.result;
		var ignore_next = false;
		var inside_string = false;
		var locdb_cur_string = [];
		var locdb_cur_param = 0;
		var locdb_lines = [[]];
		for (var i = 0; i < file.length; i++) {
			var c = file.substring(i, i + 1);
			if (c === "\"") {
				if (ignore_next) {
					ignore_next = false;
				} else if (inside_string && file.substring(i + 1, i + 2) === "\"") {
					ignore_next = true;
					locdb_cur_string.push(c);
				} else {
					inside_string = !inside_string;
				}
			} else if (!inside_string && (c === "," || c === "\n")) {
				locdb_lines[locdb_lines.length - 1][locdb_cur_param] = locdb_cur_string.join("");
				locdb_cur_string = [];
				locdb_cur_param++;
				if (c === "\n") {
					locLines[String(getHashCode(locdb_lines[locdb_lines.length - 1][0]))] = locdb_lines[locdb_lines.length - 1];
					locdb_lines[locdb_lines.length] = [];
					locdb_cur_param = 0;
				}
			} else {
				locdb_cur_string.push(c);
			}
		}
		buttons.interwikiBtn.setValue(true);
	}
	var sourcesDummy = document.createElement("div");
	function getSources(a) {
		for (var value in a) {
			if (a[value] && typeof(a[value]) === "object") {
				if (a[value].source) {
					sourcesDummy.innerHTML = a[value].value;
					if (sourcesDummy.children.length && a[value].value.substring(0, 1).match("<")) {
						sources[a[value].source] = sourcesDummy.children[0].innerText;
					} else {
						sources[a[value].source] = a[value].value;
					}
					continue;
				}
				getSources(a[value]);
			}
		}
	}
	var statusWeight = {
		progress: 0,
		success: 1,
		failed: 2
	};
	function toggleEntries(type, state) {
		var ele = root.querySelectorAll('div[data-status="' + type + '"]');
		for (var i = 0; i < ele.length; i++) {
			ele[i].style.display = (!state && "none" || "inherit");
		}
		if (document.querySelector('div.eventEntry[style*="inherit"]')) {
			rArea.style.display = "unset";
			wArea.style.display = "none";
		} else {
			rArea.style.display = "none";
			wArea.style.display = "unset";
		}
	}
	function addStatusEntry(ele, name, compare1, compare2, entry) {
		if (typeof(compare2) == "string") {
			console.log("OLD:");
			console.log(compare2);
			compare2 = compare2.replaceAll(/\s+$/gm, "\n").replace(/\n$/,"");
			console.log("NEW:");
			console.log(compare2);
		}
		var stat = String(compare1) === String(compare2) && "success" || "failed";
		if (buttons[stat].getValue() && ele.dataset.status !== stat) {
			ele.style.display = "inherit";
		}
		if (stat === "success") {
			toggleEntries("success", buttons.success.getValue());
		} else {
			toggleEntries("failed", buttons.failed.getValue());
		}
		var stEle = ele.querySelector(".status");
		if (statusWeight[ele.dataset.status] < statusWeight[stat]) ele.dataset.status = stat;
		var ele2 = ele.getElementsByClassName("statusEntry")[0];
		if (ele2.querySelector("div").dataset.temp) stEle.removeChild(ele2); // Remove temporary entry
		var newEle = document.createElement("div");
		newEle.className = "statusEntry";
		var newEle2 = document.createElement("div");
		var newEle3 = document.createElement("span");
		newEle2.innerText = entry;
		newEle3.className = "comparedResult " + stat;
		newEle3.innerText = stat;
		if (stat === "failed") {
			newEle3.innerHTML += "<br/>Got \"" + compare1 + "\"<br/>Expected \"" + compare2 + "\"";
		}
		newEle.appendChild(newEle2);
		newEle2.appendChild(newEle3);
		stEle.appendChild(newEle);
	}
	var sourceTranslations = { // DE, RU
		ability1: ["fähigkeit1"],
		ability2: ["fähigkeit2"],
		ability3: ["fähigkeit3"],
		abilitydesc1: ["fähigkeitdesc1"],
		abilitydesc2: ["fähigkeitdesc2"],
		abilitydesc3: ["fähigkeitdesc3"],
		attack1: ["attacke1"],
		attack2: ["attacke2"],
		attack3: ["attacke3"],
		blessing: ["segen"],
		"craft cost": ["herstellungskosten"],
		damage1: ["schaden1"],
		damage2: ["schaden2"],
		damage3: ["schaden3"],
		damagetype1:	["schadensart1"],
		damagetype2:	["schadensart2"],
		damagetype3:	["schadensart3"],
		description: ["beschreibung"],
		"description extended": ["beschreibung erweitert"],
		difficulty: ["schwierigkeit"],
		effect: ["effekt"],
		"effect extended": ["effekt erweitert"],
		health: ["gesundheit"],
		longdesc: ["lange beschreibung"],
		"major curse": ["starker fluch"],
		"minor curse": ["schwacher fluch"],
		rarity: ["seltenheit"],
		"shop cost": ["ladenkosten"],
		"shop cost othermine": ["ladenkosten othermine"],
		strong: ["stärke"],
		weak: ["schwäche"],
	};
	var rarityTranslations = { // DE, RU
		Common: ["Allgemein", "Обычный"],
		Rare: ["Selten", "Редкий"],
		Legendary: ["Legendär", "Легендарный"],
	};
	var curCostType = "";
	function getCosts(a) {
		for (var value in a) {
			if (a[value] && a[value].MData && a[value].MData.$Ref) {
				var n = links[a[value].MData.$Ref].Name;
				if (curCostType === "Alternate" && n === "ResourceGold") n = "ResourceGoldOthermine";
				costs[n] = a[value].MAmount;
				continue;
			} else if (typeof(a[value]) === "object") {
				if (a[value].Type) {
					curCostType = a[value].Type;
				}
				getCosts(a[value]);
				continue;
			}
		}
	}
	function getTranslatedSources(val, lang) {
		if (lang === "en") return val;
		return sourceTranslations[val] && sourceTranslations[val][Number(lang !== "de")] || val;
	}
	function getTranslatedValue(val, lang) {
		if (typeof(val) !== "object") return val;
		if (lang === "en" || val.Key === 0) return val.Value;
		return locLines[val.Key] && locLines[val.Key][lang === "de" && 6 || 13] || "<Not found>"; // 6: de, 13: ru
	}
	function log(msg) {
		console.log('[%cInfoboxChecker%c] ' + msg, 'color:orange', 'color:inherit');
	}
	function compare(ele, lang) { // values: collection of infobox-data
		if (!links[sources.guid]) {
			addStatusEntry(ele, lang + " " + sources.guid, "No data present", "Present data", "GUID not found");
			return;
		}
		log('Start');
		log('Language: ' + lang);
		console.log(sources);
		console.log(links[sources.guid]);
		log('Done');
		var a = lang + " " + sources.guid;
		var gTS = getTranslatedSources;
		var gTV = getTranslatedValue;
		var ab = links[sources.guid].Abilities || [];
		var at = links[sources.guid].Attacks || [];
		var d = gTS("longdesc", lang);
		if (sources[d]) {
			sources[d] = sources[d].replaceAll(" <br/>", "<br/>").replaceAll("<br/>", "\n");
		}
		costs = {};
		getCosts(links[sources.guid].CostGroups || []);
		var hasLongdesc = typeof(sources[gTS("longdesc", lang)]) == "string";
		var is_Penance = (sources[gTS("guid", lang)] || "") == "e9015ba93a0448daa19dcee8a34bc5da";
		console.log(costs);
		var to_process = [
			["ability1", ab[0] && ab[0].Name || "", "Ability Name #1"],
			["ability2", ab[1] && ab[1].Name || "", "Ability Name #2"],
			["ability3", ab[2] && ab[2].Name || "", "Ability Name #3"],
			["abilitydesc1", ab[0] && ab[0].Description || "", "Ability Description #1"],
			["abilitydesc2", ab[1] && ab[1].Description || "", "Ability Description #2"],
			["abilitydesc3", ab[2] && ab[2].Description || "", "Ability Description #3"],
			["attack1", at[0] && at[0].DisplayName || "", "Damage Name #1"],
			["attack2", at[1] && at[1].DisplayName || "", "Damage Name #2"],
			["attack3", at[2] && at[2].DisplayName || "", "Damage Name #3"],
			["damage1", at[0] && at[0].Amount || "", "Damage #1"],
			["damage2", at[1] && at[1].Amount || "", "Damage #2"],
			["damage3", at[2] && at[2].Amount || "", "Damage #3"],
			["damagetype1", at[0] && at[0].DamageType || "", "Damage Type #1"],
			["damagetype2", at[1] && at[1].DamageType || "", "Damage Type #2"],
			["damagetype3", at[2] && at[2].DamageType || "", "Damage Type #3"],
			[hasLongdesc && "description" || "effect", links[sources.guid].Description || "", "Effect"],
			["description extended", links[sources.guid].ExtendedFlavor, "Effect (Expanded)"],
			["difficulty", links[sources.guid].Difficulty || "", "Difficulty"],
			["health", String(is_Penance && costs.HealthHP || links[sources.guid].Hp || ""), is_Penance && "Health-Cost" || "Health"],
			[hasLongdesc && "longdesc" || "description", links[sources.guid].Flavor || "", "Description"],
			["effect extended", links[sources.guid].ExtendedDescription, "Description (Expanded)"],
			["rarity", links[sources.guid].Rarity || "", "Rarity"],
			["strong", links[sources.guid].Strength || "", "Strength"],
			["weak", links[sources.guid].Weakness || "", "Weakness"],
			["blessing", String(costs.Blessing || ""), "Blessing"],
			["shop cost", String(costs.ResourceGold || ""), "Gold"],
			["shop cost othermine", String(costs.ResourceGoldOthermine || ""), "Gold (Othermine)"],
			["major curse", String(costs.CurseMajor || ""), "Major Curse"],
			["minor curse", String(costs.CurseMinor || ""), "Minor Curse"],
			["craft cost", String(costs.ResourceThorium || ""), "Thorium"],
		];
		for (var i = 0; i < to_process.length; i++) {
			var b = sources[gTS(to_process[i][0], lang)];
			var c = gTV(to_process[i][1], lang);
			if (b || String(c).length) {
				if (lang !== "en" && to_process[i][0] == "rarity") {
					c = rarityTranslations[c] && rarityTranslations[c][Number(lang !== "de")] || c;
				}
				addStatusEntry(ele, a, b, c, to_process[i][2]);
			}
		}
	}
	function checkPage(page, lang) { // Loads from wiki
		rArea.style.display = "unset";
		wArea.style.display = "none";
		var ele = document.createElement("div");
		ele.className = "eventEntry";
		ele.dataset.status = "progress";
		ele.style.display = "inherit";
		var ele2 = document.createElement("div");
		ele2.className = "title";
		ele2.innerHTML = '<a href="https://undermine.wiki.gg/' + (lang && lang + "/" || "") + "wiki/" + page + '">' + page + (lang && " (" + lang + ")" || "") + '</a>';
		var ele3 = document.createElement("div");
		ele3.className = "status";
		var ele4 = document.createElement("div");
		ele4.className = "statusEntry";
		var ele5 = document.createElement("div");
		ele5.innerText = "Loading..";
		ele5.dataset.temp = ".";
		ele.appendChild(ele2);
		ele.appendChild(ele3);
		ele3.appendChild(ele4);
		ele4.appendChild(ele5);
		rArea.appendChild(ele);
		var url = new URL(wikiApiUrl.replace("$lang", lang || ""));
		queryParams.titles = page;
		var qP = Object.keys(queryParams);
		for (var i = 0; i < qP.length; i++) {
			url.searchParams.set(qP[i], queryParams[qP[i]]);
		}
		var request = new XMLHttpRequest();
		request.open('POST', url.href);
		request.responseType = 'json';
		request.onload = function() {
			var response = request.response;
			ele5.innerText = "Comparing..";
			var orgGUID;
			if (lang) {
				orgGUID = sources.guid;
			}
			sources = {};
			var d = JSON.parse(response.query.pages && response.query.pages[0].pageprops && response.query.pages[0].pageprops.infoboxes || "[]");
			console.log(d);
			getSources(d);
			var abcd = "en";
			if (lang) {
				abcd = lang;
			} else if (sources.guid) {
				var g = sources.guid.match(/\w{12}4\w{19}/g);
				if (g && g.length) {
					sources.guid = g[0];
				}
			}
			sources.guid = sources.guid || orgGUID;
			compare(ele, abcd);
			if (buttons.interwikiBtn.getValue() && !lang) {
				var langs = response.query.pages[0].langlinks;
				for (var i = 0; i < langs.length; i++) {
					checkPage(langs[i].title, langs[i].lang);
				}
			}
		};
		request.send();
	}
	function start() {
		if (paused) {
			if (lastTimeout) {
				clearTimeout(lastTimeout);
			}
			lastTimeout = undefined;
			return;
		}
		var content = pageList.value.split("\n");
		if (content.length > 0 && content[0].length > 0) {
			checkPage(content[0]);
			content.shift();
			pageList.value = content.join("\n");
			lastTimeout = setTimeout(start, 1000);
		} else {
			paused = true;
			buttons.playBtn.setIcon("play");
			start();
		}
	}
	function setupToolbar() {
		buttons = [];
		buttons.interwikiBtn = new OO.ui.ToggleButtonWidget( {
			framed: false,
			icon: "language",
			label: "Enables interwiki",
			invisibleLabel: true,
			title: "Enable comparing of interwiki-pages."
		} );
		buttons.trashBtn = new OO.ui.ButtonInputWidget( {
			framed: false,
			icon: "trash",
			label: "Clears the log",
			invisibleLabel: true,
			title: "Clear log-entries."
		} );
		buttons.playBtn = new OO.ui.ButtonInputWidget( {
			framed: false,
			icon: "play",
			label: "Start",
			invisibleLabel: true,
			title: "Start comparing.."
		} );
		buttons.success = new OO.ui.ToggleButtonWidget( {
			framed: false,
			icon: "check",
			label: "Toggle visibility of success log-entries.",
			invisibleLabel: true,
			title: "Toggle success log-entries.",
			value: true
		} );
		buttons.failed = new OO.ui.ToggleButtonWidget( {
			framed: false,
			icon: "error",
			label: "Toggle visibility of failed log-entries.",
			invisibleLabel: true,
			title: "Toggle failed log-entries.",
			value: true
		} );
		buttons.interwikiBtn.on("click", function() {
			if (buttons.interwikiBtn.getValue()) {
				buttons.interwikiBtn.setValue(false);
				lf.click();
			}
		});
		buttons.trashBtn.on("click", function() {
			rArea.innerHTML = "";
			rArea.style.display = "none";
			wArea.style.display = "unset";
		});
		buttons.playBtn.on("click", function() {
			paused = !paused;
			buttons.playBtn.setIcon(paused && "play" || "pause");
			start();
		});
		buttons.success.on("click", function() {
			toggleEntries("success", buttons.success.getValue());
		});
		buttons.failed.on("click", function() {
			toggleEntries("failed", buttons.failed.getValue());
		});
		document.getElementById("toolbar").innerHTML = '<label id="locFileLabel" style="display:none;" for="locFile">Open a localization file</label>' +
		'<input type="file" id="locFile" style="display:none;" autocomplete="off" accept=".bytes"/>';
		var lf = document.getElementById("locFile");
		lf.onchange = function(e) {
			var reader = new FileReader();
			reader.onabort = function() {
				buttons.interwikiBtn.setValue(false);
				lf.value = "";
			};
			reader.onload = function(readerEvent) {
				loadLocFile(readerEvent);
				lf.value = "";
			};
			reader.readAsText(e.target.files[0]);
		};
		
		var group1 = new OO.ui.ButtonGroupWidget({items: [
			buttons.interwikiBtn,
			buttons.trashBtn,
			buttons.playBtn
		]});
		var group2 = new OO.ui.ButtonGroupWidget({items: [
			buttons.success,
			buttons.failed
		]});
		
		document.getElementById("toolbar").appendChild(group1.$element.get(0));
		document.getElementById("toolbar").appendChild(group2.$element.get(0));
	}
	mw.loader.using(['jquery', 'oojs-ui', 'oojs-ui-core', 'oojs-ui-widgets']).then(function(require) {
		OO = require('oojs');
		wArea = document.getElementById("watermarkArea");
		rArea = document.getElementById("rightArea");
		var loadingWarning = new OO.ui.ButtonWidget( {
			icon: 'notice',
			label: 'Downloading required data..',
			disabled: true
		} );
		var watermark = document.createElement("div");
		watermark.style["text-align"] = "center";
		var historyIcn = new OO.ui.IconWidget( {
			icon: 'history',
			label: '',
			title: '',
			invisibleLabel: true,
			disabled: true
		} );
		var hIcn = historyIcn.$element.get(0);
		hIcn.style.transform = "scale(3)";
		hIcn.style["margin-top"] = "20px";
		watermark.appendChild(hIcn);
		wArea.appendChild(watermark);
		document.getElementById("toolbar").appendChild(loadingWarning.$element.get(0));
		$.getJSON("https://knugel.github.io/undermine-data/data/DataObjects.json", function(result) {
			links = {};
			for (var i = 0; i < result.length; i++) {
				links[result[i].Guid] = result[i];
			}
			pageList.disabled = false;
			setupToolbar();
		});
	});
})(window, window.jQuery, window.mediaWiki);